LCOV - code coverage report
Current view: top level - src - skala_gpw_features.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:24d69ee) Lines: 82.5 % 1702 1405
Test Date: 2026-09-03 07:32:15 Functions: 87.0 % 46 40

            Line data    Source code
       1              : !--------------------------------------------------------------------------------------------------!
       2              : !   CP2K: A general program to perform molecular dynamics simulations                              !
       3              : !   Copyright 2000-2026 CP2K developers group <https://cp2k.org>                                   !
       4              : !                                                                                                  !
       5              : !   SPDX-License-Identifier: GPL-2.0-or-later                                                      !
       6              : !--------------------------------------------------------------------------------------------------!
       7              : 
       8              : ! **************************************************************************************************
       9              : !> \brief Build SKALA TorchScript feature dictionaries from CP2K GPW real-space grids.
      10              : ! **************************************************************************************************
      11              : MODULE skala_gpw_features
      12              :    USE cell_types,                      ONLY: cell_type,&
      13              :                                               pbc
      14              :    USE cp_array_utils,                  ONLY: cp_3d_r_cp_type
      15              :    USE kinds,                           ONLY: dp,&
      16              :                                               int_8
      17              :    USE message_passing,                 ONLY: mp_comm_type
      18              :    USE particle_types,                  ONLY: particle_type
      19              :    USE pw_grid_types,                   ONLY: pw_grid_type
      20              :    USE pw_types,                        ONLY: pw_r3d_rs_type
      21              :    USE torch_api,                       ONLY: &
      22              :         torch_dict_clone, torch_dict_create, torch_dict_insert, torch_dict_release, &
      23              :         torch_dict_type, torch_tensor_expand_dim, torch_tensor_from_array, torch_tensor_narrow, &
      24              :         torch_tensor_release, torch_tensor_reset_from_array, torch_tensor_to_device_leaf, &
      25              :         torch_tensor_type
      26              :    USE xc_rho_set_types,                ONLY: xc_rho_set_get,&
      27              :                                               xc_rho_set_type
      28              : #include "./base/base_uses.f90"
      29              : 
      30              :    IMPLICIT NONE
      31              : 
      32              :    PRIVATE
      33              : 
      34              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'skala_gpw_features'
      35              :    REAL(KIND=dp), PARAMETER, PRIVATE    :: layout_tol = 1.0E-12_dp
      36              :    INTEGER, PARAMETER, PRIVATE          :: ndynamic_per_point = 10, nrks_dynamic_per_point = 5, &
      37              :                                            nstatic_per_point = 5, ngrad_per_point = 10
      38              :    INTEGER, PARAMETER, PUBLIC           :: skala_gpw_atom_partition_hard = 1, &
      39              :                                            skala_gpw_atom_partition_smooth = 2
      40              :    REAL(KIND=dp), PARAMETER, PRIVATE    :: smooth_partition_eps = 1.0E-12_dp
      41              :    REAL(KIND=dp), PARAMETER, PRIVATE    :: smooth_partition_taper_end = &
      42              :                                            10.0_dp*smooth_partition_eps
      43              :    INTEGER, PARAMETER, PRIVATE          :: periodic_partition_image_shell = 1
      44              : 
      45              :    PUBLIC :: skala_gpw_atom_subchunk_count, skala_gpw_atom_subchunk_layout, &
      46              :              skala_gpw_feature_build, skala_gpw_feature_build_atom_subchunk, &
      47              :              skala_gpw_feature_build_atom_subchunk_bounds, skala_gpw_feature_release, &
      48              :              skala_gpw_feature_type, skala_gpw_smooth_partition_derivatives, &
      49              :              build_periodic_atom_image_layout, periodic_atom_image_partition, &
      50              :              periodic_atom_image_partition_from_layout, smooth_atom_partition, &
      51              :              smooth_partition_atomic_weight_scale, smooth_partition_atomic_weight_scale_derivative
      52              : 
      53              :    TYPE skala_gpw_layout_cache_type
      54              :       INTEGER                                            :: chunk_atom_begin = 1, chunk_atom_end = 0, &
      55              :                                                             chunk_feature_begin = 1, &
      56              :                                                             chunk_feature_count = 0, chunk_natom = 0, &
      57              :                                                             natom = 0, nflat = 0, nflat_local = 0, &
      58              :                                                             npoint = 0, nproc = 0, &
      59              :                                                             atom_partition = skala_gpw_atom_partition_hard
      60              :       INTEGER, DIMENSION(2, 3)                           :: bo = 0, bounds = 0
      61              :       INTEGER, DIMENSION(3)                              :: npts = 0
      62              :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: dynamic_counts, dynamic_displs, &
      63              :                                                             chunk_feature_counts, chunk_feature_displs, &
      64              :                                                             chunk_grad_counts, chunk_grad_displs, &
      65              :                                                             feature_counts, feature_displs, &
      66              :                                                             feature_source_points, global_to_feature, &
      67              :                                                             local_feature_counts, local_feature_offsets, &
      68              :                                                             local_feature_points, local_feature_rows, &
      69              :                                                             chunk_return_positions, route_chunk_offsets, &
      70              :                                                             route_chunk_rows, route_row_offsets, &
      71              :                                                             route_row_positions, route_send_local_rows, &
      72              :                                                             route_point_recv_counts, &
      73              :                                                             route_point_recv_displs, &
      74              :                                                             route_point_send_counts, &
      75              :                                                             route_point_send_displs
      76              :       INTEGER, ALLOCATABLE, DIMENSION(:, :, :)           :: feature_index
      77              :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:)     :: atomic_grid_sizes, chunk_atomic_grid_sizes, &
      78              :                                                             chunk_feature_indices
      79              :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:)     :: local_feature_indices
      80              :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:, :)  :: atomic_grid_size_bound_shape, &
      81              :                                                             chunk_atomic_grid_size_bound_shape
      82              :       TYPE(torch_dict_type)                              :: chunk_inputs
      83              :       TYPE(torch_dict_type)                              :: chunk_static_inputs
      84              :       TYPE(torch_dict_type)                              :: inputs
      85              :       TYPE(torch_dict_type)                              :: static_inputs
      86              :       TYPE(torch_tensor_type)                            :: atomic_grid_size_bound_shape_t
      87              :       TYPE(torch_tensor_type)                            :: atomic_grid_sizes_t
      88              :       TYPE(torch_tensor_type)                            :: atomic_grid_weights_t
      89              :       TYPE(torch_tensor_type)                            :: chunk_atomic_grid_size_bound_shape_t
      90              :       TYPE(torch_tensor_type)                            :: chunk_atomic_grid_sizes_t
      91              :       TYPE(torch_tensor_type)                            :: chunk_atomic_grid_weights_t
      92              :       TYPE(torch_tensor_type)                            :: chunk_coarse_0_atomic_coords_t
      93              :       TYPE(torch_tensor_type)                            :: chunk_density_t
      94              :       TYPE(torch_tensor_type)                            :: chunk_density_input_t
      95              :       TYPE(torch_tensor_type)                            :: chunk_feature_indices_t
      96              :       TYPE(torch_tensor_type)                            :: chunk_grad_t
      97              :       TYPE(torch_tensor_type)                            :: chunk_grad_input_t
      98              :       TYPE(torch_tensor_type)                            :: chunk_grid_coords_t
      99              :       TYPE(torch_tensor_type)                            :: chunk_grid_weights_t
     100              :       TYPE(torch_tensor_type)                            :: chunk_kin_t
     101              :       TYPE(torch_tensor_type)                            :: chunk_kin_input_t
     102              :       TYPE(torch_tensor_type)                            :: coarse_0_atomic_coords_t
     103              :       TYPE(torch_tensor_type)                            :: density_t
     104              :       TYPE(torch_tensor_type)                            :: grid_coords_t
     105              :       TYPE(torch_tensor_type)                            :: grid_weights_t
     106              :       TYPE(torch_tensor_type)                            :: grad_t
     107              :       TYPE(torch_tensor_type)                            :: kin_t
     108              :       TYPE(torch_tensor_type)                            :: local_feature_indices_t
     109              :       REAL(KIND=dp)                                      :: dvol = 0.0_dp, weight_sum = 0.0_dp, &
     110              :                                                             weight_sumsq = 0.0_dp
     111              :       REAL(KIND=dp), DIMENSION(3, 3)                     :: cell_hmat = 0.0_dp, dh = 0.0_dp
     112              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: atomic_grid_weights, chunk_atomic_grid_weights, &
     113              :                                                             chunk_grid_weights, grid_weights
     114              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: atom_coords, chunk_coarse_0_atomic_coords, &
     115              :                                                             chunk_grid_coords, coarse_0_atomic_coords, &
     116              :                                                             grid_coords
     117              :       LOGICAL                                            :: active = .FALSE., has_weights = .FALSE., &
     118              :                                                             chunk_dynamic_input_views_active = .FALSE., &
     119              :                                                             chunk_dynamic_tensors_active = .FALSE., &
     120              :                                                             chunk_inputs_active = .FALSE., &
     121              :                                                             chunk_inputs_use_collapsed_rks = .FALSE., &
     122              :                                                             chunk_static_tensors_active = .FALSE., &
     123              :                                                             dynamic_tensors_active = .FALSE., &
     124              :                                                             inputs_active = .FALSE., &
     125              :                                                             static_tensors_active = .FALSE.
     126              :    END TYPE skala_gpw_layout_cache_type
     127              : 
     128              :    TYPE skala_gpw_feature_type
     129              :       INTEGER                                            :: chunk_feature_count = 0, nflat = 0, &
     130              :                                                             nflat_local = 0, &
     131              :                                                             atom_partition = skala_gpw_atom_partition_hard
     132              :       TYPE(torch_dict_type)                             :: inputs
     133              :       TYPE(torch_tensor_type)                           :: atomic_grid_size_bound_shape_t
     134              :       TYPE(torch_tensor_type)                           :: atomic_grid_sizes_t
     135              :       TYPE(torch_tensor_type)                           :: atomic_grid_weights_t
     136              :       TYPE(torch_tensor_type)                           :: coarse_0_atomic_coords_t
     137              :       TYPE(torch_tensor_type)                           :: density_input_t
     138              :       TYPE(torch_tensor_type)                           :: density_t
     139              :       TYPE(torch_tensor_type)                           :: grad_t
     140              :       TYPE(torch_tensor_type)                           :: grad_input_t
     141              :       TYPE(torch_tensor_type)                           :: grid_coords_t
     142              :       TYPE(torch_tensor_type)                           :: grid_weights_t
     143              :       TYPE(torch_tensor_type)                           :: kin_input_t
     144              :       TYPE(torch_tensor_type)                           :: kin_t
     145              :       TYPE(torch_tensor_type)                           :: local_feature_indices_t
     146              :       INTEGER, ALLOCATABLE, DIMENSION(:)                :: chunk_grad_counts, chunk_grad_displs, &
     147              :                                                            local_feature_counts, local_feature_offsets, &
     148              :                                                            local_feature_rows
     149              :       INTEGER, POINTER, DIMENSION(:)                    :: route_point_recv_counts => NULL(), &
     150              :                                                            route_point_recv_displs => NULL(), &
     151              :                                                            route_point_send_counts => NULL(), &
     152              :                                                            route_point_send_displs => NULL(), &
     153              :                                                            route_chunk_offsets => NULL(), &
     154              :                                                            route_chunk_rows => NULL(), &
     155              :                                                            route_row_offsets => NULL(), &
     156              :                                                            route_row_positions => NULL(), &
     157              :                                                            route_send_local_rows => NULL()
     158              :       INTEGER, ALLOCATABLE, DIMENSION(:, :, :)          :: feature_index
     159              :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:)    :: atomic_grid_sizes
     160              :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:, :) :: atomic_grid_size_bound_shape
     161              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)          :: atomic_grid_weights, grid_weights
     162              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)       :: chunk_density, chunk_kin, &
     163              :                                                            coarse_0_atomic_coords, density, &
     164              :                                                            grid_coords, kin
     165              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)    :: chunk_grad, grad
     166              :       REAL(KIND=dp)                                      :: electron_count = 0.0_dp, &
     167              :                                                             grid_weight_sum = 0.0_dp, &
     168              :                                                             kinetic_integral = 0.0_dp, &
     169              :                                                             spin_moment = 0.0_dp
     170              :       LOGICAL                                            :: active = .FALSE., owns_coordinate_tensor = .FALSE., &
     171              :                                                             owns_grid_coordinate_tensor = .FALSE., &
     172              :                                                             owns_weight_tensors = .FALSE., &
     173              :                                                             owns_dynamic_tensors = .TRUE., &
     174              :                                                             owns_inputs = .TRUE., &
     175              :                                                             owns_static_tensors = .TRUE., &
     176              :                                                             uses_atom_chunk_routing = .FALSE., &
     177              :                                                             uses_atom_chunks = .FALSE., &
     178              :                                                             uses_collapsed_rks_dynamic = .FALSE.
     179              :    END TYPE skala_gpw_feature_type
     180              : 
     181              :    TYPE(skala_gpw_layout_cache_type), SAVE, TARGET       :: cached_layout
     182              : 
     183              : CONTAINS
     184              : 
     185              : ! **************************************************************************************************
     186              : !> \brief Build a flat SKALA molecular feature dictionary from a local GPW grid.
     187              : !> \param features ...
     188              : !> \param rho_set ...
     189              : !> \param rho_r ...
     190              : !> \param particle_set ...
     191              : !> \param cell ...
     192              : !> \param requires_grad ...
     193              : !> \param weights ...
     194              : !> \param requires_coordinate_grad ...
     195              : !> \param requires_stress_grad ...
     196              : !> \param use_atom_chunks ...
     197              : !> \param route_atom_chunks ...
     198              : !> \param atom_partition ...
     199              : ! **************************************************************************************************
     200           48 :    SUBROUTINE skala_gpw_feature_build(features, rho_set, rho_r, particle_set, cell, &
     201              :                                       requires_grad, weights, requires_coordinate_grad, &
     202              :                                       requires_stress_grad, use_atom_chunks, route_atom_chunks, &
     203              :                                       atom_partition)
     204              :       TYPE(skala_gpw_feature_type), INTENT(INOUT)        :: features
     205              :       TYPE(xc_rho_set_type), INTENT(IN)                  :: rho_set
     206              :       TYPE(pw_r3d_rs_type), DIMENSION(:), INTENT(IN)     :: rho_r
     207              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     208              :       TYPE(cell_type), POINTER                           :: cell
     209              :       LOGICAL, INTENT(IN), OPTIONAL                      :: requires_grad
     210              :       TYPE(pw_r3d_rs_type), OPTIONAL, POINTER            :: weights
     211              :       LOGICAL, INTENT(IN), OPTIONAL                      :: requires_coordinate_grad, &
     212              :                                                             requires_stress_grad, use_atom_chunks, &
     213              :                                                             route_atom_chunks
     214              :       INTEGER, INTENT(IN), OPTIONAL                      :: atom_partition
     215              : 
     216              :       INTEGER :: handle, i, ipt, ispin, j, k, local_row, my_atom_partition, &
     217              :          ndynamic_local_per_point, nflat, nflat_local, nspins, phase_handle, real_base, row
     218              :       INTEGER, DIMENSION(2, 3)                           :: bo
     219              :       LOGICAL :: collapse_spin_dynamics, my_requires_coordinate_grad, my_requires_grad, &
     220              :          my_requires_stress_grad, my_route_atom_chunks, my_use_atom_chunks, &
     221              :          use_atom_chunk_protocol, use_atom_chunk_routing
     222           48 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: global_dynamic, local_dynamic
     223           48 :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: rho, rhoa, rhob, tau_a, tau_b, tau_total
     224          576 :       TYPE(cp_3d_r_cp_type), DIMENSION(3)                :: drho, drhoa, drhob
     225              :       TYPE(pw_grid_type), POINTER                        :: pw_grid
     226              : 
     227           48 :       CALL timeset("skala_gpw_feature_build", handle)
     228              : 
     229           48 :       my_requires_grad = .FALSE.
     230           48 :       IF (PRESENT(requires_grad)) my_requires_grad = requires_grad
     231           48 :       my_requires_coordinate_grad = .FALSE.
     232           48 :       IF (PRESENT(requires_coordinate_grad)) THEN
     233           48 :          my_requires_coordinate_grad = requires_coordinate_grad
     234              :       END IF
     235           48 :       my_requires_stress_grad = .FALSE.
     236           48 :       IF (PRESENT(requires_stress_grad)) my_requires_stress_grad = requires_stress_grad
     237           48 :       my_use_atom_chunks = .FALSE.
     238           48 :       IF (PRESENT(use_atom_chunks)) my_use_atom_chunks = use_atom_chunks
     239           48 :       my_route_atom_chunks = .FALSE.
     240           48 :       IF (PRESENT(route_atom_chunks)) my_route_atom_chunks = route_atom_chunks
     241           48 :       my_atom_partition = skala_gpw_atom_partition_hard
     242           48 :       IF (PRESENT(atom_partition)) my_atom_partition = atom_partition
     243           48 :       IF (my_atom_partition /= skala_gpw_atom_partition_hard .AND. &
     244              :           my_atom_partition /= skala_gpw_atom_partition_smooth) THEN
     245            0 :          CALL cp_abort(__LOCATION__, "Unknown native SKALA atom-partition mode.")
     246              :       END IF
     247           48 :       CPASSERT(ASSOCIATED(cell))
     248           48 :       CPASSERT(ASSOCIATED(particle_set))
     249           48 :       CPASSERT(SIZE(rho_r) == 1 .OR. SIZE(rho_r) == 2)
     250           48 :       CPASSERT(ASSOCIATED(rho_r(1)%pw_grid))
     251           48 :       pw_grid => rho_r(1)%pw_grid
     252              : 
     253           48 :       nspins = SIZE(rho_r)
     254          480 :       bo = pw_grid%bounds_local
     255           48 :       nflat_local = pw_grid%ngpts_local
     256              : 
     257           48 :       CALL timeset("skala_gpw_pre_release", phase_handle)
     258           48 :       CALL skala_gpw_feature_release(features)
     259           48 :       CALL timestop(phase_handle)
     260              : 
     261              :       use_atom_chunk_protocol = my_use_atom_chunks .AND. &
     262           48 :                                 .NOT. (my_requires_coordinate_grad .OR. my_requires_stress_grad)
     263           48 :       use_atom_chunk_routing = use_atom_chunk_protocol .AND. my_route_atom_chunks
     264           48 :       CALL timeset("skala_gpw_layout_cache", phase_handle)
     265              :       CALL ensure_layout_cache(pw_grid, particle_set, cell, weights, my_atom_partition, &
     266           48 :                                needs_full_static_tensors=.NOT. use_atom_chunk_protocol)
     267           48 :       CALL timestop(phase_handle)
     268           48 :       nflat = cached_layout%nflat
     269           48 :       collapse_spin_dynamics = nspins == 1 .AND. use_atom_chunk_routing
     270           48 :       ndynamic_local_per_point = ndynamic_per_point
     271           48 :       IF (collapse_spin_dynamics) ndynamic_local_per_point = nrks_dynamic_per_point
     272          144 :       ALLOCATE (local_dynamic(ndynamic_local_per_point*nflat_local))
     273           48 :       local_dynamic = 0.0_dp
     274              : 
     275           48 :       CALL timeset("skala_gpw_pack_local", phase_handle)
     276           48 :       IF (nspins == 1) THEN
     277           42 :          CALL xc_rho_set_get(rho_set, rho=rho, drho=drho, tau=tau_total)
     278              :       ELSE
     279              :          CALL xc_rho_set_get(rho_set, rhoa=rhoa, rhob=rhob, drhoa=drhoa, drhob=drhob, &
     280            6 :                              tau_a=tau_a, tau_b=tau_b)
     281              :       END IF
     282              : 
     283           48 :       local_row = 0
     284         1496 :       DO k = bo(1, 3), bo(2, 3)
     285        51952 :          DO j = bo(1, 2), bo(2, 2)
     286      1018328 :             DO i = bo(1, 1), bo(2, 1)
     287       966424 :                local_row = local_row + 1
     288       966424 :                real_base = ndynamic_local_per_point*(local_row - 1)
     289              : 
     290      1016880 :                IF (nspins == 1) THEN
     291       693049 :                   IF (collapse_spin_dynamics) THEN
     292       648202 :                      local_dynamic(real_base + 1) = 0.5_dp*rho(i, j, k)
     293       648202 :                      local_dynamic(real_base + 2) = 0.5_dp*drho(1)%array(i, j, k)
     294       648202 :                      local_dynamic(real_base + 3) = 0.5_dp*drho(2)%array(i, j, k)
     295       648202 :                      local_dynamic(real_base + 4) = 0.5_dp*drho(3)%array(i, j, k)
     296       648202 :                      local_dynamic(real_base + 5) = 0.5_dp*tau_total(i, j, k)
     297              :                   ELSE
     298        44847 :                      local_dynamic(real_base + 1) = 0.5_dp*rho(i, j, k)
     299        44847 :                      local_dynamic(real_base + 2) = 0.5_dp*rho(i, j, k)
     300       134541 :                      DO ispin = 1, 2
     301              :                         local_dynamic(real_base + 2 + 3*(ispin - 1) + 1) = &
     302        89694 :                            0.5_dp*drho(1)%array(i, j, k)
     303              :                         local_dynamic(real_base + 2 + 3*(ispin - 1) + 2) = &
     304        89694 :                            0.5_dp*drho(2)%array(i, j, k)
     305              :                         local_dynamic(real_base + 2 + 3*(ispin - 1) + 3) = &
     306        89694 :                            0.5_dp*drho(3)%array(i, j, k)
     307       134541 :                         local_dynamic(real_base + 8 + ispin) = 0.5_dp*tau_total(i, j, k)
     308              :                      END DO
     309              :                   END IF
     310              :                ELSE
     311       273375 :                   local_dynamic(real_base + 1) = rhoa(i, j, k)
     312       273375 :                   local_dynamic(real_base + 2) = rhob(i, j, k)
     313       273375 :                   local_dynamic(real_base + 3) = drhoa(1)%array(i, j, k)
     314       273375 :                   local_dynamic(real_base + 4) = drhoa(2)%array(i, j, k)
     315       273375 :                   local_dynamic(real_base + 5) = drhoa(3)%array(i, j, k)
     316       273375 :                   local_dynamic(real_base + 6) = drhob(1)%array(i, j, k)
     317       273375 :                   local_dynamic(real_base + 7) = drhob(2)%array(i, j, k)
     318       273375 :                   local_dynamic(real_base + 8) = drhob(3)%array(i, j, k)
     319       273375 :                   local_dynamic(real_base + 9) = tau_a(i, j, k)
     320       273375 :                   local_dynamic(real_base + 10) = tau_b(i, j, k)
     321              :                END IF
     322              :             END DO
     323              :          END DO
     324              :       END DO
     325           48 :       CALL timestop(phase_handle)
     326              : 
     327           48 :       CALL timeset("skala_gpw_copy_layout", phase_handle)
     328              :       CALL copy_cached_layout(features, use_atom_chunk_routing, &
     329              :                               my_requires_coordinate_grad .OR. my_requires_stress_grad, &
     330              :                               my_requires_stress_grad .OR. &
     331              :                               (my_atom_partition == skala_gpw_atom_partition_smooth .AND. &
     332           88 :                                (my_requires_coordinate_grad .OR. my_requires_stress_grad)))
     333           48 :       CALL timestop(phase_handle)
     334              : 
     335           48 :       IF (use_atom_chunk_routing) THEN
     336           40 :          CALL timeset("skala_gpw_route_dyn", phase_handle)
     337              :          CALL route_atom_chunk_dynamics(features, local_dynamic, pw_grid%para%group, &
     338           40 :                                         collapse_spin_dynamics)
     339           40 :          features%uses_atom_chunk_routing = .TRUE.
     340           40 :          features%uses_atom_chunks = .TRUE.
     341           40 :          CALL timestop(phase_handle)
     342              :       ELSE
     343           24 :          ALLOCATE (global_dynamic(ndynamic_per_point*cached_layout%npoint))
     344            8 :          CALL timeset("skala_gpw_allgatherv", phase_handle)
     345              :          CALL pw_grid%para%group%allgatherv(local_dynamic, global_dynamic, &
     346              :                                             cached_layout%dynamic_counts, &
     347            8 :                                             cached_layout%dynamic_displs)
     348            8 :          CALL timestop(phase_handle)
     349              : 
     350            8 :          CALL timeset("skala_gpw_reorder_dyn", phase_handle)
     351            0 :          ALLOCATE (features%density(nflat, 2), features%grad(nflat, 3, 2), &
     352           56 :                    features%kin(nflat, 2))
     353       357784 :          features%density = 0.0_dp
     354      1073352 :          features%grad = 0.0_dp
     355       357784 :          features%kin = 0.0_dp
     356              : 
     357       178888 :          DO row = 1, nflat
     358       178880 :             ipt = cached_layout%feature_source_points(row)
     359       178880 :             real_base = ndynamic_per_point*(ipt - 1)
     360       536640 :             features%density(row, :) = global_dynamic(real_base + 1:real_base + 2)
     361       178880 :             features%grad(row, 1, 1) = global_dynamic(real_base + 3)
     362       178880 :             features%grad(row, 2, 1) = global_dynamic(real_base + 4)
     363       178880 :             features%grad(row, 3, 1) = global_dynamic(real_base + 5)
     364       178880 :             features%grad(row, 1, 2) = global_dynamic(real_base + 6)
     365       178880 :             features%grad(row, 2, 2) = global_dynamic(real_base + 7)
     366       178880 :             features%grad(row, 3, 2) = global_dynamic(real_base + 8)
     367       536648 :             features%kin(row, :) = global_dynamic(real_base + 9:real_base + 10)
     368              :          END DO
     369           24 :          CALL timestop(phase_handle)
     370              :       END IF
     371              : 
     372           48 :       CALL timeset("skala_gpw_feature_sums", phase_handle)
     373           48 :       IF (features%uses_atom_chunks) THEN
     374           40 :          features%electron_count = 0.0_dp
     375           40 :          features%kinetic_integral = 0.0_dp
     376           40 :          features%spin_moment = 0.0_dp
     377           40 :          IF (features%chunk_feature_count > 0) THEN
     378           40 :             IF (features%uses_collapsed_rks_dynamic) THEN
     379              :                features%electron_count = SUM(2.0_dp*features%chunk_density(:, 1)* &
     380       719907 :                                              cached_layout%chunk_grid_weights)
     381              :                features%kinetic_integral = SUM(2.0_dp*features%chunk_kin(:, 1)* &
     382       719907 :                                                cached_layout%chunk_grid_weights)
     383              :             ELSE
     384              :                features%electron_count = SUM((features%chunk_density(:, 1) + &
     385              :                                               features%chunk_density(:, 2))* &
     386       273381 :                                              cached_layout%chunk_grid_weights)
     387              :                features%kinetic_integral = SUM((features%chunk_kin(:, 1) + &
     388              :                                                 features%chunk_kin(:, 2))* &
     389       273381 :                                                cached_layout%chunk_grid_weights)
     390              :                features%spin_moment = SUM((features%chunk_density(:, 1) - &
     391              :                                            features%chunk_density(:, 2))* &
     392       273381 :                                           cached_layout%chunk_grid_weights)
     393              :             END IF
     394              :          END IF
     395           40 :          CALL pw_grid%para%group%sum(features%electron_count)
     396           40 :          CALL pw_grid%para%group%sum(features%kinetic_integral)
     397           40 :          CALL pw_grid%para%group%sum(features%spin_moment)
     398              :       ELSE
     399              :          features%electron_count = SUM((features%density(:, 1) + features%density(:, 2))* &
     400       178888 :                                        features%grid_weights)
     401              :          features%kinetic_integral = SUM((features%kin(:, 1) + features%kin(:, 2))* &
     402       178888 :                                          features%grid_weights)
     403              :          features%spin_moment = SUM((features%density(:, 1) - features%density(:, 2))* &
     404       178888 :                                     features%grid_weights)
     405              :       END IF
     406           48 :       IF (use_atom_chunk_routing) THEN
     407      1986536 :          features%grid_weight_sum = SUM(cached_layout%grid_weights)
     408              :       ELSE
     409       178888 :          features%grid_weight_sum = SUM(features%grid_weights)
     410              :       END IF
     411           48 :       CALL timestop(phase_handle)
     412              : 
     413           48 :       CALL timeset("skala_gpw_tensor_update", phase_handle)
     414           48 :       IF (use_atom_chunk_protocol .AND. .NOT. features%uses_atom_chunks) THEN
     415            0 :          IF (features%chunk_feature_count > 0) CALL extract_atom_chunk_dynamics(features)
     416            0 :          features%uses_atom_chunks = .TRUE.
     417              :       END IF
     418           48 :       IF (.NOT. features%uses_atom_chunks .OR. features%chunk_feature_count > 0) THEN
     419              :          CALL add_feature_tensors(features, my_requires_grad, my_requires_coordinate_grad, &
     420              :                                   my_requires_stress_grad, &
     421              :                                   features%uses_atom_chunks, &
     422              :                                   requires_weight_grad= &
     423              :                                   (my_atom_partition == skala_gpw_atom_partition_smooth .AND. &
     424           88 :                                    (my_requires_coordinate_grad .OR. my_requires_stress_grad)))
     425              :       ELSE
     426              :          ! This rank participates in atom-chunk communication but owns no model input rows.
     427            0 :          features%owns_coordinate_tensor = .FALSE.
     428            0 :          features%owns_grid_coordinate_tensor = .FALSE.
     429            0 :          features%owns_weight_tensors = .FALSE.
     430            0 :          features%owns_dynamic_tensors = .FALSE.
     431            0 :          features%owns_inputs = .FALSE.
     432            0 :          features%owns_static_tensors = .FALSE.
     433              :       END IF
     434           48 :       CALL timestop(phase_handle)
     435           48 :       features%active = .TRUE.
     436              : 
     437           48 :       IF (ALLOCATED(global_dynamic)) DEALLOCATE (global_dynamic)
     438           48 :       DEALLOCATE (local_dynamic)
     439           48 :       CALL timestop(handle)
     440              : 
     441          384 :    END SUBROUTINE skala_gpw_feature_build
     442              : 
     443              : ! **************************************************************************************************
     444              : !> \brief Ensure that static grid-to-atom layout data is cached for the current grid/geometry.
     445              : !> \param pw_grid ...
     446              : !> \param particle_set ...
     447              : !> \param cell ...
     448              : !> \param weights ...
     449              : !> \param atom_partition ...
     450              : !> \param needs_full_static_tensors ...
     451              : ! **************************************************************************************************
     452           84 :    SUBROUTINE ensure_layout_cache(pw_grid, particle_set, cell, weights, atom_partition, &
     453              :                                   needs_full_static_tensors)
     454              :       TYPE(pw_grid_type), POINTER                        :: pw_grid
     455              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     456              :       TYPE(cell_type), POINTER                           :: cell
     457              :       TYPE(pw_r3d_rs_type), OPTIONAL, POINTER            :: weights
     458              :       INTEGER, INTENT(IN), OPTIONAL                      :: atom_partition
     459              :       LOGICAL, INTENT(IN)                                :: needs_full_static_tensors
     460              : 
     461              :       INTEGER                                            :: my_atom_partition, phase_handle
     462              :       LOGICAL                                            :: cache_matches
     463              : 
     464           48 :       my_atom_partition = skala_gpw_atom_partition_hard
     465           48 :       IF (PRESENT(atom_partition)) my_atom_partition = atom_partition
     466           48 :       IF (PRESENT(weights)) THEN
     467           48 :          CALL timeset("skala_gpw_layout_match", phase_handle)
     468              :          cache_matches = layout_cache_matches(pw_grid, particle_set, cell, weights, &
     469           48 :                                               my_atom_partition)
     470           48 :          CALL timestop(phase_handle)
     471              :       ELSE
     472            0 :          CALL timeset("skala_gpw_layout_match", phase_handle)
     473              :          cache_matches = layout_cache_matches(pw_grid, particle_set, cell, &
     474            0 :                                               atom_partition=my_atom_partition)
     475            0 :          CALL timestop(phase_handle)
     476              :       END IF
     477              : 
     478           48 :       IF (cache_matches) THEN
     479           12 :          IF (needs_full_static_tensors .AND. .NOT. cached_layout%static_tensors_active) THEN
     480            8 :             CALL timeset("skala_gpw_layout_tensors", phase_handle)
     481            8 :             CALL build_full_static_layout_tensors(cached_layout)
     482            8 :             CALL timestop(phase_handle)
     483              :          END IF
     484           12 :          RETURN
     485              :       END IF
     486              : 
     487           36 :       CALL timeset("skala_gpw_layout_rebuild", phase_handle)
     488           36 :       IF (PRESENT(weights)) THEN
     489              :          CALL rebuild_layout_cache(pw_grid, particle_set, cell, weights, my_atom_partition, &
     490           36 :                                    needs_full_static_tensors)
     491              :       ELSE
     492              :          CALL rebuild_layout_cache(pw_grid, particle_set, cell, &
     493              :                                    atom_partition=my_atom_partition, &
     494            0 :                                    needs_full_static_tensors=needs_full_static_tensors)
     495              :       END IF
     496           36 :       CALL timestop(phase_handle)
     497              : 
     498              :    END SUBROUTINE ensure_layout_cache
     499              : 
     500              : ! **************************************************************************************************
     501              : !> \brief Check whether the current static layout cache can be reused.
     502              : !> \param pw_grid ...
     503              : !> \param particle_set ...
     504              : !> \param cell ...
     505              : !> \param weights ...
     506              : !> \param atom_partition ...
     507              : !> \return ...
     508              : ! **************************************************************************************************
     509           48 :    FUNCTION layout_cache_matches(pw_grid, particle_set, cell, weights, atom_partition) RESULT(matches)
     510              :       TYPE(pw_grid_type), POINTER                        :: pw_grid
     511              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     512              :       TYPE(cell_type), POINTER                           :: cell
     513              :       TYPE(pw_r3d_rs_type), OPTIONAL, POINTER            :: weights
     514              :       INTEGER, INTENT(IN), OPTIONAL                      :: atom_partition
     515              :       LOGICAL                                            :: matches
     516              : 
     517              :       INTEGER                                            :: iatom, my_atom_partition
     518              :       LOGICAL                                            :: weights_match
     519              : 
     520           48 :       my_atom_partition = skala_gpw_atom_partition_hard
     521           48 :       IF (PRESENT(atom_partition)) my_atom_partition = atom_partition
     522           48 :       matches = .FALSE.
     523           48 :       IF (.NOT. cached_layout%active) RETURN
     524           16 :       IF (cached_layout%atom_partition /= my_atom_partition) RETURN
     525           16 :       IF (cached_layout%natom /= SIZE(particle_set)) RETURN
     526           16 :       IF (cached_layout%nflat_local /= pw_grid%ngpts_local) RETURN
     527           16 :       IF (cached_layout%nproc /= pw_grid%para%group%num_pe) RETURN
     528          160 :       IF (ANY(cached_layout%bo /= pw_grid%bounds_local)) RETURN
     529          160 :       IF (ANY(cached_layout%bounds /= pw_grid%bounds)) RETURN
     530           64 :       IF (ANY(cached_layout%npts /= pw_grid%npts)) RETURN
     531           16 :       IF (ABS(cached_layout%dvol - pw_grid%dvol) > layout_tol) RETURN
     532          208 :       IF (ANY(ABS(cached_layout%dh - pw_grid%dh) > layout_tol)) RETURN
     533          208 :       IF (ANY(ABS(cached_layout%cell_hmat - cell%hmat) > layout_tol)) RETURN
     534           16 :       IF (.NOT. ALLOCATED(cached_layout%atom_coords)) RETURN
     535              : 
     536           44 :       DO iatom = 1, SIZE(particle_set)
     537          136 :          IF (ANY(ABS(cached_layout%atom_coords(:, iatom) - particle_set(iatom)%r) > layout_tol)) RETURN
     538              :       END DO
     539              : 
     540           12 :       IF (PRESENT(weights)) THEN
     541           12 :          weights_match = layout_weights_match(pw_grid, weights)
     542              :       ELSE
     543            0 :          weights_match = layout_weights_match(pw_grid)
     544              :       END IF
     545           12 :       IF (.NOT. weights_match) RETURN
     546              : 
     547           48 :       matches = .TRUE.
     548              : 
     549              :    END FUNCTION layout_cache_matches
     550              : 
     551              : ! **************************************************************************************************
     552              : !> \brief Check whether current optional integration weights match the cached static tensors.
     553              : !> \param pw_grid ...
     554              : !> \param weights ...
     555              : !> \return ...
     556              : ! **************************************************************************************************
     557           12 :    FUNCTION layout_weights_match(pw_grid, weights) RESULT(matches)
     558              :       TYPE(pw_grid_type), POINTER                        :: pw_grid
     559              :       TYPE(pw_r3d_rs_type), OPTIONAL, POINTER            :: weights
     560              :       LOGICAL                                            :: matches
     561              : 
     562              :       LOGICAL                                            :: has_weights
     563              :       REAL(KIND=dp)                                      :: weight_sum, weight_sumsq
     564              : 
     565           12 :       matches = .FALSE.
     566              :       MARK_USED(pw_grid)
     567           12 :       IF (PRESENT(weights)) THEN
     568           12 :          CALL weights_signature(weights, has_weights, weight_sum, weight_sumsq)
     569              :       ELSE
     570              :          CALL weights_signature(has_weights=has_weights, weight_sum=weight_sum, &
     571            0 :                                 weight_sumsq=weight_sumsq)
     572              :       END IF
     573              : 
     574           12 :       IF (cached_layout%has_weights .NEQV. has_weights) RETURN
     575           12 :       IF (ABS(cached_layout%weight_sum - weight_sum) > layout_tol) RETURN
     576           12 :       IF (ABS(cached_layout%weight_sumsq - weight_sumsq) > layout_tol) RETURN
     577              : 
     578           12 :       matches = .TRUE.
     579              : 
     580              :    END FUNCTION layout_weights_match
     581              : 
     582              : ! **************************************************************************************************
     583              : !> \brief Build the static SKALA layout cache.
     584              : !> \param pw_grid ...
     585              : !> \param particle_set ...
     586              : !> \param cell ...
     587              : !> \param weights ...
     588              : !> \param atom_partition ...
     589              : !> \param needs_full_static_tensors ...
     590              : ! **************************************************************************************************
     591           36 :    SUBROUTINE rebuild_layout_cache(pw_grid, particle_set, cell, weights, atom_partition, &
     592              :                                    needs_full_static_tensors)
     593              :       TYPE(pw_grid_type), POINTER                        :: pw_grid
     594              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     595              :       TYPE(cell_type), POINTER                           :: cell
     596              :       TYPE(pw_r3d_rs_type), OPTIONAL, POINTER            :: weights
     597              :       INTEGER, INTENT(IN), OPTIONAL                      :: atom_partition
     598              :       LOGICAL, INTENT(IN)                                :: needs_full_static_tensors
     599              : 
     600              :       INTEGER :: feature_local, feature_slot, i, iatom, ipt, j, jatom, k, local_feature, &
     601              :          local_row, max_grid_size, max_local_features, my_atom_partition, natom, nfeature_local, &
     602              :          nflat, nflat_local, npoint, nproc, nx, ny, owner, pe, pe_index, phase_handle, row, &
     603              :          source_global, source_local, static_base
     604           36 :       INTEGER, ALLOCATABLE, DIMENSION(:) :: atom_offset, atom_position, chunk_atom_begin, &
     605           36 :          chunk_atom_end, cursor, feature_counts, feature_displs, global_owner, &
     606           36 :          global_source_points, local_feature_counts_tmp, local_owner, local_source_global, &
     607           36 :          local_source_points, point_counts, point_displs, static_counts, static_displs
     608              :       INTEGER, DIMENSION(2, 3)                           :: bo
     609              :       LOGICAL                                            :: has_weights, nonperiodic
     610              :       REAL(KIND=dp)                                      :: atomic_base_weight, base_weight, &
     611              :                                                             included_sum, partition_weight, &
     612              :                                                             weight_sum, weight_sumsq
     613           36 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: distances, global_static, local_static, &
     614           36 :                                                             partition_weights
     615           36 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: atom_coords_pbc, pair_distances, &
     616           36 :                                                             partition_atom_coords
     617              :       REAL(KIND=dp), DIMENSION(3)                        :: grid_point
     618              : 
     619           36 :       CALL release_layout_cache(cached_layout)
     620              : 
     621           36 :       my_atom_partition = skala_gpw_atom_partition_hard
     622           36 :       IF (PRESENT(atom_partition)) my_atom_partition = atom_partition
     623           36 :       natom = SIZE(particle_set)
     624          360 :       bo = pw_grid%bounds_local
     625           36 :       nflat_local = pw_grid%ngpts_local
     626           36 :       nproc = pw_grid%para%group%num_pe
     627           36 :       pe_index = pw_grid%para%group%mepos + 1
     628              : 
     629           36 :       IF (PRESENT(weights)) THEN
     630           36 :          CALL weights_signature(weights, has_weights, weight_sum, weight_sumsq)
     631              :       ELSE
     632              :          CALL weights_signature(has_weights=has_weights, weight_sum=weight_sum, &
     633            0 :                                 weight_sumsq=weight_sumsq)
     634              :       END IF
     635              : 
     636           36 :       max_local_features = nflat_local
     637           36 :       IF (my_atom_partition == skala_gpw_atom_partition_smooth) THEN
     638           14 :          max_local_features = nflat_local*natom
     639              :       END IF
     640            0 :       ALLOCATE (local_owner(max_local_features), &
     641            0 :                 local_source_points(max_local_features), &
     642            0 :                 local_static(nstatic_per_point*max_local_features), &
     643            0 :                 local_feature_counts_tmp(nflat_local), feature_counts(nproc), &
     644              :                 feature_displs(nproc), point_counts(nproc), point_displs(nproc), &
     645          612 :                 static_counts(nproc), static_displs(nproc), atom_coords_pbc(3, natom))
     646            0 :       ALLOCATE (cached_layout%feature_index(bo(1, 1):bo(2, 1), &
     647              :                                             bo(1, 2):bo(2, 2), &
     648          180 :                                             bo(1, 3):bo(2, 3)))
     649       961727 :       cached_layout%feature_index = 0
     650           36 :       local_feature_counts_tmp = 0
     651          134 :       DO iatom = 1, natom
     652          134 :          atom_coords_pbc(:, iatom) = pbc(particle_set(iatom)%r, cell, positive_range=.TRUE.)
     653              :       END DO
     654           54 :       nonperiodic = ALL(cell%perd == 0)
     655           36 :       IF (my_atom_partition == skala_gpw_atom_partition_smooth .AND. nonperiodic) THEN
     656           24 :          ALLOCATE (pair_distances(natom, natom))
     657            6 :          pair_distances = 0.0_dp
     658           12 :          DO iatom = 1, natom - 1
     659           18 :             DO jatom = iatom + 1, natom
     660              :                pair_distances(iatom, jatom) = &
     661           24 :                   SQRT(SUM((atom_coords_pbc(:, iatom) - atom_coords_pbc(:, jatom))**2))
     662           12 :                pair_distances(jatom, iatom) = pair_distances(iatom, jatom)
     663              :             END DO
     664              :          END DO
     665              :       ELSE
     666           30 :          ALLOCATE (pair_distances(0, 0))
     667              :       END IF
     668              : 
     669           36 :       CALL timeset("skala_gpw_layout_local", phase_handle)
     670           36 :       nx = bo(2, 1) - bo(1, 1) + 1
     671           36 :       ny = bo(2, 2) - bo(1, 2) + 1
     672           36 :       IF (my_atom_partition == skala_gpw_atom_partition_hard) THEN
     673           22 :          nfeature_local = nflat_local
     674              : !$OMP PARALLEL DO DEFAULT(NONE) COLLAPSE(3) &
     675              : !$OMP SHARED(atom_coords_pbc, bo, cached_layout, cell, local_feature_counts_tmp, local_owner, &
     676              : !$OMP        local_source_points, local_static, nx, ny, pw_grid, weights) &
     677           22 : !$OMP PRIVATE(atomic_base_weight, base_weight, grid_point, i, j, k, local_row, owner, static_base)
     678              :          DO k = bo(1, 3), bo(2, 3)
     679              :             DO j = bo(1, 2), bo(2, 2)
     680              :                DO i = bo(1, 1), bo(2, 1)
     681              :                   local_row = i - bo(1, 1) + 1 + &
     682              :                               nx*(j - bo(1, 2) + ny*(k - bo(1, 3)))
     683              :                   grid_point = grid_coordinate(pw_grid, [i, j, k])
     684              :                   atomic_base_weight = pw_grid%dvol
     685              :                   base_weight = pw_grid%dvol
     686              :                   IF (PRESENT(weights)) THEN
     687              :                      IF (ASSOCIATED(weights)) base_weight = base_weight*weights%array(i, j, k)
     688              :                   END IF
     689              :                   cached_layout%feature_index(i, j, k) = local_row
     690              :                   owner = nearest_atom(grid_point, atom_coords_pbc, cell)
     691              :                   local_feature_counts_tmp(local_row) = 1
     692              :                   local_owner(local_row) = owner
     693              :                   local_source_points(local_row) = local_row
     694              :                   static_base = nstatic_per_point*(local_row - 1)
     695              :                   local_static(static_base + 1:static_base + 3) = grid_point
     696              :                   local_static(static_base + 4) = base_weight
     697              :                   local_static(static_base + 5) = atomic_base_weight
     698              :                END DO
     699              :             END DO
     700              :          END DO
     701              : !$OMP END PARALLEL DO
     702              :       ELSE
     703              : !$OMP PARALLEL DEFAULT(NONE) &
     704              : !$OMP SHARED(atom_coords_pbc, bo, cached_layout, cell, local_feature_counts_tmp, local_owner, &
     705              : !$OMP        local_source_points, local_static, natom, nonperiodic, nx, ny, pair_distances, &
     706              : !$OMP        pw_grid, weights) &
     707              : !$OMP PRIVATE(atomic_base_weight, base_weight, distances, feature_slot, grid_point, i, iatom, &
     708              : !$OMP         included_sum, j, k, local_feature, local_row, owner, partition_weight, &
     709           14 : !$OMP         partition_atom_coords, partition_weights, static_base)
     710              :          ! Keep point-local scratch outside the hot loop and private to each thread.
     711              :          ALLOCATE (distances(natom), partition_atom_coords(3, natom), partition_weights(natom))
     712              : !$OMP DO COLLAPSE(3)
     713              :          DO k = bo(1, 3), bo(2, 3)
     714              :             DO j = bo(1, 2), bo(2, 2)
     715              :                DO i = bo(1, 1), bo(2, 1)
     716              :                   local_row = i - bo(1, 1) + 1 + &
     717              :                               nx*(j - bo(1, 2) + ny*(k - bo(1, 3)))
     718              :                   grid_point = grid_coordinate(pw_grid, [i, j, k])
     719              :                   atomic_base_weight = pw_grid%dvol
     720              :                   base_weight = pw_grid%dvol
     721              :                   IF (PRESENT(weights)) THEN
     722              :                      IF (ASSOCIATED(weights)) base_weight = base_weight*weights%array(i, j, k)
     723              :                   END IF
     724              :                   cached_layout%feature_index(i, j, k) = local_row
     725              :                   IF (nonperiodic) THEN
     726              :                      CALL smooth_atom_partition(grid_point, atom_coords_pbc, cell, &
     727              :                                                 partition_weights, partition_atom_coords, distances, &
     728              :                                                 pair_distances)
     729              :                   ELSE
     730              :                      CALL smooth_atom_partition(grid_point, atom_coords_pbc, cell, &
     731              :                                                 partition_weights, partition_atom_coords, distances)
     732              :                   END IF
     733              :                   included_sum = SUM(partition_weights, MASK=partition_weights > smooth_partition_eps)
     734              :                   IF (included_sum <= 0.0_dp) THEN
     735              :                      owner = nearest_atom(grid_point, atom_coords_pbc, cell)
     736              :                      partition_weights = 0.0_dp
     737              :                      partition_weights(owner) = 1.0_dp
     738              :                      included_sum = 1.0_dp
     739              :                   END IF
     740              :                   local_feature = 0
     741              :                   DO iatom = 1, natom
     742              :                      IF (partition_weights(iatom) <= smooth_partition_eps) CYCLE
     743              :                      partition_weight = partition_weights(iatom)/included_sum
     744              :                      local_feature = local_feature + 1
     745              :                      feature_slot = natom*(local_row - 1) + local_feature
     746              :                      local_owner(feature_slot) = iatom
     747              :                      local_source_points(feature_slot) = local_row
     748              :                      static_base = nstatic_per_point*(feature_slot - 1)
     749              :                      local_static(static_base + 1:static_base + 3) = grid_point
     750              :                      local_static(static_base + 4) = base_weight*partition_weight
     751              :                      local_static(static_base + 5) = atomic_base_weight* &
     752              :                                                      smooth_partition_atomic_weight_scale( &
     753              :                                                      partition_weight)
     754              :                   END DO
     755              :                   local_feature_counts_tmp(local_row) = local_feature
     756              :                END DO
     757              :             END DO
     758              :          END DO
     759              : !$OMP END DO
     760              :          DEALLOCATE (distances, partition_atom_coords, partition_weights)
     761              : !$OMP END PARALLEL
     762              : 
     763              :          ! Compact the conflict-free row slots while preserving the serial row/atom order.
     764           14 :          nfeature_local = 0
     765        65435 :          DO local_row = 1, nflat_local
     766        65421 :             CPASSERT(local_feature_counts_tmp(local_row) > 0)
     767       195865 :             DO local_feature = 1, local_feature_counts_tmp(local_row)
     768       130430 :                nfeature_local = nfeature_local + 1
     769       130430 :                feature_slot = natom*(local_row - 1) + local_feature
     770       130430 :                IF (feature_slot == nfeature_local) CYCLE
     771       113810 :                local_owner(nfeature_local) = local_owner(feature_slot)
     772       113810 :                local_source_points(nfeature_local) = local_source_points(feature_slot)
     773              :                local_static(nstatic_per_point*(nfeature_local - 1) + 1: &
     774              :                             nstatic_per_point*nfeature_local) = &
     775              :                   local_static(nstatic_per_point*(feature_slot - 1) + 1: &
     776      1333951 :                                nstatic_per_point*feature_slot)
     777              :             END DO
     778              :          END DO
     779              :       END IF
     780           36 :       CALL timestop(phase_handle)
     781              : 
     782              :       ! SKALA groups all grid points by atom. This ordering is static while the
     783              :       ! grid, cell, atom positions, and optional integration weights are unchanged.
     784           36 :       CALL timeset("skala_gpw_layout_gather", phase_handle)
     785           36 :       CALL pw_grid%para%group%allgather(nflat_local, point_counts)
     786           36 :       CALL counts_to_displs(point_counts, point_displs)
     787          108 :       npoint = SUM(point_counts)
     788           36 :       CALL pw_grid%para%group%allgather(nfeature_local, feature_counts)
     789           36 :       CALL counts_to_displs(feature_counts, feature_displs)
     790          108 :       DO pe = 1, nproc
     791           72 :          static_counts(pe) = nstatic_per_point*feature_counts(pe)
     792          108 :          static_displs(pe) = nstatic_per_point*feature_displs(pe)
     793              :       END DO
     794          108 :       nflat = SUM(feature_counts)
     795              :       ALLOCATE (global_owner(nflat), global_source_points(nflat), &
     796          288 :                 global_static(nstatic_per_point*nflat), local_source_global(nfeature_local))
     797       979872 :       DO feature_local = 1, nfeature_local
     798       979872 :          local_source_global(feature_local) = point_displs(pe_index) + local_source_points(feature_local)
     799              :       END DO
     800              :       CALL pw_grid%para%group%allgatherv(local_owner(1:nfeature_local), global_owner, feature_counts, &
     801           36 :                                          feature_displs)
     802              :       CALL pw_grid%para%group%allgatherv(local_source_global, global_source_points, feature_counts, &
     803           36 :                                          feature_displs)
     804              :       CALL pw_grid%para%group%allgatherv(local_static(1:nstatic_per_point*nfeature_local), &
     805              :                                          global_static, static_counts, &
     806           36 :                                          static_displs)
     807           36 :       CALL timestop(phase_handle)
     808              : 
     809            0 :       ALLOCATE (cached_layout%chunk_feature_counts(nproc), &
     810            0 :                 cached_layout%chunk_feature_displs(nproc), &
     811            0 :                 cached_layout%chunk_grad_counts(nproc), cached_layout%chunk_grad_displs(nproc), &
     812            0 :                 cached_layout%feature_counts(nproc), cached_layout%feature_displs(nproc), &
     813            0 :                 cached_layout%dynamic_counts(nproc), cached_layout%dynamic_displs(nproc), &
     814            0 :                 cached_layout%route_point_recv_counts(nproc), &
     815            0 :                 cached_layout%route_point_recv_displs(nproc), &
     816            0 :                 cached_layout%route_point_send_counts(nproc), &
     817            0 :                 cached_layout%route_point_send_displs(nproc), &
     818            0 :                 cached_layout%feature_source_points(nflat), &
     819            0 :                 cached_layout%global_to_feature(npoint), cached_layout%atomic_grid_sizes(natom), &
     820            0 :                 cached_layout%local_feature_counts(nflat_local), &
     821            0 :                 cached_layout%local_feature_offsets(nflat_local + 1), &
     822            0 :                 cached_layout%local_feature_rows(nfeature_local), &
     823            0 :                 cached_layout%local_feature_points(nfeature_local), &
     824            0 :                 cached_layout%local_feature_indices(nfeature_local), atom_offset(natom + 1), &
     825              :                 atom_position(natom), chunk_atom_begin(nproc), chunk_atom_end(nproc), &
     826         1152 :                 cursor(nflat_local))
     827          108 :       cached_layout%feature_counts(:) = feature_counts
     828          108 :       cached_layout%feature_displs(:) = feature_displs
     829          108 :       cached_layout%dynamic_counts(:) = ndynamic_per_point*point_counts
     830          108 :       cached_layout%dynamic_displs(:) = ndynamic_per_point*point_displs
     831          134 :       cached_layout%atomic_grid_sizes = 0_int_8
     832      1829690 :       cached_layout%global_to_feature = 0
     833       914863 :       cached_layout%local_feature_counts(:) = local_feature_counts_tmp
     834           36 :       cached_layout%local_feature_offsets(1) = 1
     835       914863 :       DO local_row = 1, nflat_local
     836              :          cached_layout%local_feature_offsets(local_row + 1) = &
     837              :             cached_layout%local_feature_offsets(local_row) + &
     838       914863 :             cached_layout%local_feature_counts(local_row)
     839              :       END DO
     840       914863 :       cursor(:) = cached_layout%local_feature_offsets(1:nflat_local)
     841              : 
     842           36 :       CALL timeset("skala_gpw_layout_atom_sort", phase_handle)
     843      1959708 :       DO ipt = 1, nflat
     844              :          cached_layout%atomic_grid_sizes(global_owner(ipt)) = &
     845      1959708 :             cached_layout%atomic_grid_sizes(global_owner(ipt)) + 1_int_8
     846              :       END DO
     847           36 :       atom_offset(1) = 1
     848          134 :       DO iatom = 1, natom
     849          134 :          atom_offset(iatom + 1) = atom_offset(iatom) + INT(cached_layout%atomic_grid_sizes(iatom))
     850              :       END DO
     851          134 :       DO iatom = 1, natom
     852          134 :          atom_position(iatom) = atom_offset(iatom)
     853              :       END DO
     854          134 :       max_grid_size = MAXVAL(INT(cached_layout%atomic_grid_sizes))
     855              :       CALL build_atom_chunks(cached_layout%atomic_grid_sizes, atom_offset, nproc, &
     856              :                              chunk_atom_begin, chunk_atom_end, &
     857              :                              cached_layout%chunk_feature_counts, &
     858           36 :                              cached_layout%chunk_feature_displs)
     859          108 :       cached_layout%chunk_grad_counts(:) = ngrad_per_point*cached_layout%chunk_feature_counts
     860          108 :       cached_layout%chunk_grad_displs(:) = ngrad_per_point*cached_layout%chunk_feature_displs
     861           36 :       cached_layout%chunk_atom_begin = chunk_atom_begin(pe_index)
     862           36 :       cached_layout%chunk_atom_end = chunk_atom_end(pe_index)
     863           36 :       cached_layout%chunk_feature_begin = cached_layout%chunk_feature_displs(pe_index) + 1
     864           36 :       cached_layout%chunk_feature_count = cached_layout%chunk_feature_counts(pe_index)
     865              :       cached_layout%chunk_natom = cached_layout%chunk_atom_end - &
     866           36 :                                   cached_layout%chunk_atom_begin + 1
     867              : 
     868            0 :       ALLOCATE (cached_layout%grid_coords(3, nflat), cached_layout%grid_weights(nflat), &
     869            0 :                 cached_layout%atomic_grid_weights(nflat), &
     870            0 :                 cached_layout%coarse_0_atomic_coords(3, natom), &
     871            0 :                 cached_layout%atomic_grid_size_bound_shape(0, max_grid_size), &
     872          324 :                 cached_layout%atom_coords(3, natom))
     873      7838724 :       cached_layout%grid_coords = 0.0_dp
     874      1959708 :       cached_layout%grid_weights = 0.0_dp
     875      1959708 :       cached_layout%atomic_grid_weights = 0.0_dp
     876       781292 :       cached_layout%atomic_grid_size_bound_shape = 0_int_8
     877              : 
     878          134 :       DO iatom = 1, natom
     879          686 :          cached_layout%atom_coords(:, iatom) = particle_set(iatom)%r
     880          428 :          cached_layout%coarse_0_atomic_coords(:, iatom) = atom_coords_pbc(:, iatom)
     881              :       END DO
     882              : 
     883      1959708 :       DO ipt = 1, nflat
     884      1959672 :          owner = global_owner(ipt)
     885      1959672 :          row = atom_position(owner)
     886      1959672 :          atom_position(owner) = atom_position(owner) + 1
     887      1959672 :          source_global = global_source_points(ipt)
     888      1959672 :          cached_layout%feature_source_points(row) = source_global
     889      1959672 :          IF (cached_layout%global_to_feature(source_global) == 0) THEN
     890      1829654 :             cached_layout%global_to_feature(source_global) = row
     891              :          END IF
     892      1959672 :          static_base = nstatic_per_point*(ipt - 1)
     893      7838688 :          cached_layout%grid_coords(:, row) = global_static(static_base + 1:static_base + 3)
     894      1959672 :          cached_layout%grid_weights(row) = global_static(static_base + 4)
     895      1959672 :          cached_layout%atomic_grid_weights(row) = global_static(static_base + 5)
     896      1959672 :          source_local = source_global - point_displs(pe_index)
     897      1959708 :          IF (source_local >= 1 .AND. source_local <= nflat_local) THEN
     898       979836 :             feature_local = cursor(source_local)
     899       979836 :             cursor(source_local) = cursor(source_local) + 1
     900       979836 :             cached_layout%local_feature_rows(feature_local) = row
     901       979836 :             cached_layout%local_feature_points(feature_local) = source_local
     902              :          END IF
     903              :       END DO
     904              : 
     905      1829690 :       CPASSERT(ALL(cached_layout%global_to_feature > 0))
     906       979872 :       CPASSERT(ALL(cached_layout%local_feature_rows > 0))
     907       979872 :       CPASSERT(ALL(cached_layout%local_feature_points > 0))
     908         1250 :       DO k = bo(1, 3), bo(2, 3)
     909        46900 :          DO j = bo(1, 2), bo(2, 2)
     910       961691 :             DO i = bo(1, 1), bo(2, 1)
     911       914827 :                local_row = cached_layout%feature_index(i, j, k)
     912              :                cached_layout%feature_index(i, j, k) = &
     913       960477 :                   cached_layout%local_feature_rows(cached_layout%local_feature_offsets(local_row))
     914              :             END DO
     915              :          END DO
     916              :       END DO
     917       979872 :       DO feature_local = 1, nfeature_local
     918              :          cached_layout%local_feature_indices(feature_local) = &
     919       979872 :             INT(cached_layout%local_feature_rows(feature_local) - 1, KIND=int_8)
     920              :       END DO
     921           36 :       CALL timestop(phase_handle)
     922           36 :       CALL timeset("skala_gpw_layout_chunk_routes", phase_handle)
     923              :       CALL build_atom_chunk_routes(cached_layout, cached_layout%local_feature_rows, &
     924           36 :                                    pw_grid%para%group)
     925           36 :       CALL build_atom_chunk_layout(cached_layout)
     926           36 :       CALL timestop(phase_handle)
     927              : 
     928           36 :       cached_layout%natom = natom
     929           36 :       cached_layout%nflat = nflat
     930           36 :       cached_layout%nflat_local = nflat_local
     931           36 :       cached_layout%npoint = npoint
     932           36 :       cached_layout%nproc = nproc
     933           36 :       cached_layout%atom_partition = my_atom_partition
     934          360 :       cached_layout%bo = bo
     935          684 :       cached_layout%bounds = pw_grid%bounds
     936          252 :       cached_layout%npts = pw_grid%npts
     937           36 :       cached_layout%dvol = pw_grid%dvol
     938          900 :       cached_layout%dh = pw_grid%dh
     939          900 :       cached_layout%cell_hmat = cell%hmat
     940           36 :       cached_layout%weight_sum = weight_sum
     941           36 :       cached_layout%weight_sumsq = weight_sumsq
     942           36 :       cached_layout%has_weights = has_weights
     943           36 :       CALL timeset("skala_gpw_layout_tensors", phase_handle)
     944           36 :       IF (needs_full_static_tensors) CALL build_full_static_layout_tensors(cached_layout)
     945           36 :       IF (cached_layout%chunk_feature_count > 0) THEN
     946           36 :          CALL build_chunk_static_layout_tensors(cached_layout)
     947              :       END IF
     948           36 :       CALL timestop(phase_handle)
     949           36 :       cached_layout%active = .TRUE.
     950              : 
     951            0 :       DEALLOCATE (atom_coords_pbc, atom_offset, atom_position, &
     952            0 :                   chunk_atom_begin, chunk_atom_end, cursor, feature_counts, feature_displs, &
     953            0 :                   global_owner, global_source_points, global_static, local_feature_counts_tmp, &
     954            0 :                   local_owner, local_source_global, local_source_points, &
     955            0 :                   local_static, pair_distances, point_counts, point_displs, static_counts, &
     956           36 :                   static_displs)
     957              : 
     958          216 :    END SUBROUTINE rebuild_layout_cache
     959              : 
     960              : ! **************************************************************************************************
     961              : !> \brief Build cached full-layout Torch tensors for static SKALA inputs.
     962              : !> \param cache ...
     963              : ! **************************************************************************************************
     964            8 :    SUBROUTINE build_full_static_layout_tensors(cache)
     965              :       TYPE(skala_gpw_layout_cache_type), INTENT(INOUT)   :: cache
     966              : 
     967            8 :       CPASSERT(.NOT. cache%static_tensors_active)
     968              : 
     969            8 :       CALL torch_tensor_from_array(cache%grid_coords_t, cache%grid_coords)
     970            8 :       CALL torch_tensor_to_device_leaf(cache%grid_coords_t, .FALSE.)
     971            8 :       CALL torch_tensor_from_array(cache%grid_weights_t, cache%grid_weights)
     972            8 :       CALL torch_tensor_to_device_leaf(cache%grid_weights_t, .FALSE.)
     973            8 :       CALL torch_tensor_from_array(cache%atomic_grid_weights_t, cache%atomic_grid_weights)
     974            8 :       CALL torch_tensor_to_device_leaf(cache%atomic_grid_weights_t, .FALSE.)
     975            8 :       CALL torch_tensor_from_array(cache%atomic_grid_sizes_t, cache%atomic_grid_sizes)
     976            8 :       CALL torch_tensor_to_device_leaf(cache%atomic_grid_sizes_t, .FALSE.)
     977            8 :       CALL torch_tensor_from_array(cache%coarse_0_atomic_coords_t, cache%coarse_0_atomic_coords)
     978            8 :       CALL torch_tensor_to_device_leaf(cache%coarse_0_atomic_coords_t, .FALSE.)
     979              :       CALL torch_tensor_from_array(cache%atomic_grid_size_bound_shape_t, &
     980            8 :                                    cache%atomic_grid_size_bound_shape)
     981            8 :       CALL torch_tensor_to_device_leaf(cache%atomic_grid_size_bound_shape_t, .FALSE.)
     982            8 :       CALL torch_tensor_from_array(cache%local_feature_indices_t, cache%local_feature_indices)
     983            8 :       CALL torch_tensor_to_device_leaf(cache%local_feature_indices_t, .FALSE.)
     984              : 
     985            8 :       CALL torch_dict_create(cache%static_inputs)
     986            8 :       CALL torch_dict_insert(cache%static_inputs, "grid_coords", cache%grid_coords_t)
     987            8 :       CALL torch_dict_insert(cache%static_inputs, "grid_weights", cache%grid_weights_t)
     988              :       CALL torch_dict_insert(cache%static_inputs, "atomic_grid_weights", &
     989            8 :                              cache%atomic_grid_weights_t)
     990              :       CALL torch_dict_insert(cache%static_inputs, "atomic_grid_sizes", &
     991            8 :                              cache%atomic_grid_sizes_t)
     992              :       CALL torch_dict_insert(cache%static_inputs, "atomic_grid_size_bound_shape", &
     993            8 :                              cache%atomic_grid_size_bound_shape_t)
     994            8 :       cache%static_tensors_active = .TRUE.
     995              : 
     996            8 :    END SUBROUTINE build_full_static_layout_tensors
     997              : 
     998              : ! **************************************************************************************************
     999              : !> \brief Build cached atom-chunk Torch tensors for static SKALA inputs.
    1000              : !> \param cache ...
    1001              : ! **************************************************************************************************
    1002           36 :    SUBROUTINE build_chunk_static_layout_tensors(cache)
    1003              :       TYPE(skala_gpw_layout_cache_type), INTENT(INOUT)   :: cache
    1004              : 
    1005           36 :       CPASSERT(.NOT. cache%chunk_static_tensors_active)
    1006           36 :       CALL torch_tensor_from_array(cache%chunk_grid_coords_t, cache%chunk_grid_coords)
    1007           36 :       CALL torch_tensor_to_device_leaf(cache%chunk_grid_coords_t, .FALSE.)
    1008           36 :       CALL torch_tensor_from_array(cache%chunk_grid_weights_t, cache%chunk_grid_weights)
    1009           36 :       CALL torch_tensor_to_device_leaf(cache%chunk_grid_weights_t, .FALSE.)
    1010              :       CALL torch_tensor_from_array(cache%chunk_atomic_grid_weights_t, &
    1011           36 :                                    cache%chunk_atomic_grid_weights)
    1012           36 :       CALL torch_tensor_to_device_leaf(cache%chunk_atomic_grid_weights_t, .FALSE.)
    1013              :       CALL torch_tensor_from_array(cache%chunk_atomic_grid_sizes_t, &
    1014           36 :                                    cache%chunk_atomic_grid_sizes)
    1015           36 :       CALL torch_tensor_to_device_leaf(cache%chunk_atomic_grid_sizes_t, .FALSE.)
    1016              :       CALL torch_tensor_from_array(cache%chunk_coarse_0_atomic_coords_t, &
    1017           36 :                                    cache%chunk_coarse_0_atomic_coords)
    1018           36 :       CALL torch_tensor_to_device_leaf(cache%chunk_coarse_0_atomic_coords_t, .FALSE.)
    1019              :       CALL torch_tensor_from_array(cache%chunk_atomic_grid_size_bound_shape_t, &
    1020           36 :                                    cache%chunk_atomic_grid_size_bound_shape)
    1021           36 :       CALL torch_tensor_to_device_leaf(cache%chunk_atomic_grid_size_bound_shape_t, .FALSE.)
    1022           36 :       CALL torch_tensor_from_array(cache%chunk_feature_indices_t, cache%chunk_feature_indices)
    1023           36 :       CALL torch_tensor_to_device_leaf(cache%chunk_feature_indices_t, .FALSE.)
    1024              : 
    1025           36 :       CALL torch_dict_create(cache%chunk_static_inputs)
    1026              :       CALL torch_dict_insert(cache%chunk_static_inputs, "grid_coords", &
    1027           36 :                              cache%chunk_grid_coords_t)
    1028              :       CALL torch_dict_insert(cache%chunk_static_inputs, "grid_weights", &
    1029           36 :                              cache%chunk_grid_weights_t)
    1030              :       CALL torch_dict_insert(cache%chunk_static_inputs, "atomic_grid_weights", &
    1031           36 :                              cache%chunk_atomic_grid_weights_t)
    1032              :       CALL torch_dict_insert(cache%chunk_static_inputs, "atomic_grid_sizes", &
    1033           36 :                              cache%chunk_atomic_grid_sizes_t)
    1034              :       CALL torch_dict_insert(cache%chunk_static_inputs, "atomic_grid_size_bound_shape", &
    1035           36 :                              cache%chunk_atomic_grid_size_bound_shape_t)
    1036           36 :       cache%chunk_static_tensors_active = .TRUE.
    1037              : 
    1038           36 :    END SUBROUTINE build_chunk_static_layout_tensors
    1039              : 
    1040              : ! **************************************************************************************************
    1041              : !> \brief Copy static cached layout arrays into a feature bundle.
    1042              : !> \param features ...
    1043              : !> \param use_atom_chunk_routing ...
    1044              : !> \param needs_coordinate_array ...
    1045              : !> \param needs_grid_coordinate_array ...
    1046              : ! **************************************************************************************************
    1047           48 :    SUBROUTINE copy_cached_layout(features, use_atom_chunk_routing, needs_coordinate_array, &
    1048              :                                  needs_grid_coordinate_array)
    1049              :       TYPE(skala_gpw_feature_type), INTENT(INOUT)        :: features
    1050              :       LOGICAL, INTENT(IN)                                :: use_atom_chunk_routing, &
    1051              :                                                             needs_coordinate_array, &
    1052              :                                                             needs_grid_coordinate_array
    1053              : 
    1054           48 :       CPASSERT(cached_layout%active)
    1055              : 
    1056           48 :       IF (.NOT. use_atom_chunk_routing) THEN
    1057            0 :          ALLOCATE (features%feature_index(LBOUND(cached_layout%feature_index, 1): &
    1058              :                                           UBOUND(cached_layout%feature_index, 1), &
    1059              :                                           LBOUND(cached_layout%feature_index, 2): &
    1060              :                                           UBOUND(cached_layout%feature_index, 2), &
    1061              :                                           LBOUND(cached_layout%feature_index, 3): &
    1062           40 :                                           UBOUND(cached_layout%feature_index, 3)))
    1063           24 :          ALLOCATE (features%grid_weights(cached_layout%nflat))
    1064            0 :          ALLOCATE (features%local_feature_counts(cached_layout%nflat_local), &
    1065            0 :                    features%local_feature_offsets(cached_layout%nflat_local + 1), &
    1066           56 :                    features%local_feature_rows(SIZE(cached_layout%local_feature_rows)))
    1067              : 
    1068        48935 :          features%feature_index(:, :, :) = cached_layout%feature_index
    1069       178888 :          features%grid_weights(:) = cached_layout%grid_weights
    1070        44855 :          features%local_feature_counts(:) = cached_layout%local_feature_counts
    1071        44863 :          features%local_feature_offsets(:) = cached_layout%local_feature_offsets
    1072        89448 :          features%local_feature_rows(:) = cached_layout%local_feature_rows
    1073              :       END IF
    1074           48 :       features%nflat = cached_layout%nflat
    1075           48 :       features%nflat_local = cached_layout%nflat_local
    1076           48 :       features%chunk_feature_count = cached_layout%chunk_feature_count
    1077           48 :       features%atom_partition = cached_layout%atom_partition
    1078          144 :       ALLOCATE (features%atomic_grid_sizes(cached_layout%natom))
    1079          170 :       features%atomic_grid_sizes(:) = cached_layout%atomic_grid_sizes
    1080           48 :       IF (needs_grid_coordinate_array) THEN
    1081           24 :          ALLOCATE (features%grid_coords(3, cached_layout%nflat))
    1082           24 :          ALLOCATE (features%atomic_grid_weights(cached_layout%nflat))
    1083       715528 :          features%grid_coords(:, :) = cached_layout%grid_coords
    1084       178888 :          features%atomic_grid_weights(:) = cached_layout%atomic_grid_weights
    1085              :       END IF
    1086            0 :       ALLOCATE (features%chunk_grad_counts(cached_layout%nproc), &
    1087          192 :                 features%chunk_grad_displs(cached_layout%nproc))
    1088          144 :       features%chunk_grad_counts(:) = cached_layout%chunk_grad_counts
    1089          144 :       features%chunk_grad_displs(:) = cached_layout%chunk_grad_displs
    1090           48 :       IF (use_atom_chunk_routing) THEN
    1091           40 :          features%route_point_recv_counts => cached_layout%route_point_recv_counts
    1092           40 :          features%route_point_recv_displs => cached_layout%route_point_recv_displs
    1093           40 :          features%route_point_send_counts => cached_layout%route_point_send_counts
    1094           40 :          features%route_point_send_displs => cached_layout%route_point_send_displs
    1095           40 :          features%route_chunk_offsets => cached_layout%route_chunk_offsets
    1096           40 :          features%route_chunk_rows => cached_layout%route_chunk_rows
    1097           40 :          features%route_row_offsets => cached_layout%route_row_offsets
    1098           40 :          features%route_row_positions => cached_layout%route_row_positions
    1099           40 :          features%route_send_local_rows => cached_layout%route_send_local_rows
    1100              :       END IF
    1101           48 :       IF (needs_coordinate_array) THEN
    1102           24 :          ALLOCATE (features%coarse_0_atomic_coords(3, cached_layout%natom))
    1103           72 :          features%coarse_0_atomic_coords(:, :) = cached_layout%coarse_0_atomic_coords
    1104              :       END IF
    1105              : 
    1106           48 :    END SUBROUTINE copy_cached_layout
    1107              : 
    1108              : ! **************************************************************************************************
    1109              : !> \brief Split the atom-ordered feature rows into contiguous atom chunks.
    1110              : !> \param atomic_grid_sizes ...
    1111              : !> \param atom_offset ...
    1112              : !> \param nproc ...
    1113              : !> \param chunk_atom_begin ...
    1114              : !> \param chunk_atom_end ...
    1115              : !> \param chunk_feature_counts ...
    1116              : !> \param chunk_feature_displs ...
    1117              : ! **************************************************************************************************
    1118           36 :    SUBROUTINE build_atom_chunks(atomic_grid_sizes, atom_offset, nproc, chunk_atom_begin, &
    1119           36 :                                 chunk_atom_end, chunk_feature_counts, chunk_feature_displs)
    1120              :       INTEGER(KIND=int_8), DIMENSION(:), INTENT(IN)      :: atomic_grid_sizes
    1121              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: atom_offset
    1122              :       INTEGER, INTENT(IN)                                :: nproc
    1123              :       INTEGER, DIMENSION(:), INTENT(OUT)                 :: chunk_atom_begin, chunk_atom_end, &
    1124              :                                                             chunk_feature_counts, &
    1125              :                                                             chunk_feature_displs
    1126              : 
    1127              :       INTEGER :: best_limit, count, displ, end_atom, lower_limit, max_end_atom, midpoint, natom, &
    1128              :          next_atom, next_count, pe, ranks_left, target_chunks, total_count, upper_limit
    1129              : 
    1130           36 :       natom = SIZE(atomic_grid_sizes)
    1131          108 :       chunk_atom_begin = natom + 1
    1132          108 :       chunk_atom_end = natom
    1133          108 :       chunk_feature_counts = 0
    1134          108 :       chunk_feature_displs = 0
    1135           36 :       IF (natom == 0) RETURN
    1136              : 
    1137           36 :       target_chunks = MIN(nproc, natom)
    1138           36 :       total_count = atom_offset(natom + 1) - 1
    1139          134 :       lower_limit = MAXVAL(INT(atomic_grid_sizes))
    1140           36 :       lower_limit = MAX(lower_limit, (total_count + target_chunks - 1)/target_chunks)
    1141           36 :       upper_limit = total_count
    1142           36 :       best_limit = upper_limit
    1143          538 :       DO WHILE (lower_limit <= upper_limit)
    1144          502 :          midpoint = (lower_limit + upper_limit)/2
    1145          538 :          IF (atom_chunks_fit_limit(atomic_grid_sizes, midpoint, target_chunks)) THEN
    1146          404 :             best_limit = midpoint
    1147          404 :             upper_limit = midpoint - 1
    1148              :          ELSE
    1149           98 :             lower_limit = midpoint + 1
    1150              :          END IF
    1151              :       END DO
    1152              : 
    1153              :       displ = 0
    1154              :       next_atom = 1
    1155          108 :       DO pe = 1, nproc
    1156           72 :          chunk_feature_displs(pe) = displ
    1157           72 :          IF (pe > target_chunks .OR. next_atom > natom) CYCLE
    1158              : 
    1159           72 :          ranks_left = target_chunks - pe + 1
    1160           72 :          chunk_atom_begin(pe) = next_atom
    1161           72 :          max_end_atom = natom - ranks_left + 1
    1162           72 :          end_atom = next_atom
    1163           72 :          count = INT(atomic_grid_sizes(end_atom))
    1164           98 :          DO WHILE (end_atom < max_end_atom)
    1165           36 :             next_count = count + INT(atomic_grid_sizes(end_atom + 1))
    1166           36 :             IF (next_count > best_limit) EXIT
    1167              :             end_atom = end_atom + 1
    1168           72 :             count = next_count
    1169              :          END DO
    1170              : 
    1171           72 :          chunk_atom_end(pe) = end_atom
    1172           72 :          chunk_feature_counts(pe) = atom_offset(end_atom + 1) - atom_offset(next_atom)
    1173           72 :          displ = displ + chunk_feature_counts(pe)
    1174          108 :          next_atom = end_atom + 1
    1175              :       END DO
    1176              : 
    1177           36 :       CPASSERT(displ == atom_offset(natom + 1) - 1)
    1178              : 
    1179              :    END SUBROUTINE build_atom_chunks
    1180              : 
    1181              : ! **************************************************************************************************
    1182              : !> \brief Check if contiguous atom chunks can stay below a feature-count limit.
    1183              : !> \param atomic_grid_sizes ...
    1184              : !> \param limit ...
    1185              : !> \param nchunks ...
    1186              : !> \return ...
    1187              : ! **************************************************************************************************
    1188          502 :    FUNCTION atom_chunks_fit_limit(atomic_grid_sizes, limit, nchunks) RESULT(fits)
    1189              :       INTEGER(KIND=int_8), DIMENSION(:), INTENT(IN)      :: atomic_grid_sizes
    1190              :       INTEGER, INTENT(IN)                                :: limit, nchunks
    1191              :       LOGICAL                                            :: fits
    1192              : 
    1193              :       INTEGER                                            :: atom_count, chunk_count, iatom, &
    1194              :                                                             used_chunks
    1195              : 
    1196          502 :       fits = .FALSE.
    1197          502 :       IF (SIZE(atomic_grid_sizes) == 0) THEN
    1198          502 :          fits = .TRUE.
    1199              :          RETURN
    1200              :       END IF
    1201              : 
    1202         1896 :       used_chunks = 1
    1203         1896 :       chunk_count = 0
    1204         1896 :       DO iatom = 1, SIZE(atomic_grid_sizes)
    1205         1394 :          atom_count = INT(atomic_grid_sizes(iatom))
    1206         1394 :          IF (atom_count > limit) RETURN
    1207         1896 :          IF (chunk_count + atom_count > limit) THEN
    1208          600 :             used_chunks = used_chunks + 1
    1209          600 :             chunk_count = atom_count
    1210              :          ELSE
    1211              :             chunk_count = chunk_count + atom_count
    1212              :          END IF
    1213              :       END DO
    1214          502 :       fits = used_chunks <= nchunks
    1215              : 
    1216          502 :    END FUNCTION atom_chunks_fit_limit
    1217              : 
    1218              : ! **************************************************************************************************
    1219              : !> \brief Return the MPI rank owning an atom-ordered feature row.
    1220              : !> \param row ...
    1221              : !> \param counts ...
    1222              : !> \param displs ...
    1223              : !> \return ...
    1224              : ! **************************************************************************************************
    1225       979836 :    FUNCTION feature_row_chunk_owner(row, counts, displs) RESULT(owner)
    1226              :       INTEGER, INTENT(IN)                                :: row
    1227              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: counts, displs
    1228              :       INTEGER                                            :: owner
    1229              : 
    1230              :       INTEGER                                            :: pe
    1231              : 
    1232       979836 :       owner = 0
    1233      1434145 :       DO pe = 1, SIZE(counts)
    1234      1434145 :          IF (row > displs(pe) .AND. row <= displs(pe) + counts(pe)) THEN
    1235       979836 :             owner = pe
    1236              :             RETURN
    1237              :          END IF
    1238              :       END DO
    1239              : 
    1240              :    END FUNCTION feature_row_chunk_owner
    1241              : 
    1242              : ! **************************************************************************************************
    1243              : !> \brief Build zero-based displacement arrays from per-rank counts.
    1244              : !> \param counts ...
    1245              : !> \param displs ...
    1246              : ! **************************************************************************************************
    1247          216 :    SUBROUTINE counts_to_displs(counts, displs)
    1248              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: counts
    1249              :       INTEGER, DIMENSION(:), INTENT(OUT)                 :: displs
    1250              : 
    1251              :       INTEGER                                            :: pe
    1252              : 
    1253          216 :       displs(1) = 0
    1254          432 :       DO pe = 2, SIZE(counts)
    1255          432 :          displs(pe) = displs(pe - 1) + counts(pe - 1)
    1256              :       END DO
    1257              : 
    1258          216 :    END SUBROUTINE counts_to_displs
    1259              : 
    1260              : ! **************************************************************************************************
    1261              : !> \brief Precompute all-to-all routing between local grid rows and atom chunks.
    1262              : !> \param cache ...
    1263              : !> \param local_to_global ...
    1264              : !> \param group ...
    1265              : ! **************************************************************************************************
    1266           36 :    SUBROUTINE build_atom_chunk_routes(cache, local_to_global, group)
    1267              :       TYPE(skala_gpw_layout_cache_type), INTENT(INOUT)   :: cache
    1268              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: local_to_global
    1269              : 
    1270              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    1271              : 
    1272              :       INTEGER                                            :: chunk_row, dest, feature_begin, feature_end, &
    1273              :                                                             feature_pos, local_feature, local_row, meta_pos, &
    1274              :                                                             nflat_local, nproc, nrecv, nsend, pe, point_pos, &
    1275              :                                                             route_index, row, row_position
    1276           36 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: chunk_cursor, chunk_route_counts, &
    1277           36 :                                                             feature_cursor, feature_recv_counts, &
    1278              :                                                             feature_recv_displs, feature_send_counts, &
    1279           36 :                                                             feature_send_displs, meta_recv_counts, &
    1280           36 :                                                             meta_recv_displs, meta_send_counts, &
    1281           36 :                                                             meta_send_displs, recv_meta, route_cursor, &
    1282           36 :                                                             route_feature_dest, route_last_row, &
    1283           36 :                                                             route_last_position, send_meta
    1284              : 
    1285           36 :       nflat_local = SIZE(cache%local_feature_offsets) - 1
    1286           36 :       nproc = SIZE(cache%route_point_send_counts)
    1287              :       ALLOCATE (route_feature_dest(SIZE(local_to_global)), &
    1288              :                 feature_send_counts(nproc), feature_send_displs(nproc), &
    1289              :                 feature_recv_counts(nproc), feature_recv_displs(nproc), &
    1290          360 :                 route_last_row(nproc), route_last_position(nproc))
    1291          108 :       cache%route_point_send_counts = 0
    1292           36 :       feature_send_counts = 0
    1293           36 :       route_feature_dest = 0
    1294           36 :       route_last_row = 0
    1295              :       ! Route each local grid row only once to each atom-chunk owner.
    1296       914863 :       DO local_row = 1, nflat_local
    1297       914827 :          feature_begin = cache%local_feature_offsets(local_row)
    1298       914827 :          feature_end = cache%local_feature_offsets(local_row + 1) - 1
    1299      1894699 :          DO local_feature = feature_begin, feature_end
    1300              :             dest = feature_row_chunk_owner(local_to_global(local_feature), &
    1301              :                                            cache%chunk_feature_counts, &
    1302       979836 :                                            cache%chunk_feature_displs)
    1303       979836 :             CPASSERT(dest > 0)
    1304       979836 :             route_feature_dest(local_feature) = dest
    1305       979836 :             feature_send_counts(dest) = feature_send_counts(dest) + 1
    1306      1894663 :             IF (route_last_row(dest) /= local_row) THEN
    1307       979836 :                cache%route_point_send_counts(dest) = cache%route_point_send_counts(dest) + 1
    1308       979836 :                route_last_row(dest) = local_row
    1309              :             END IF
    1310              :          END DO
    1311              :       END DO
    1312           36 :       CALL counts_to_displs(cache%route_point_send_counts, cache%route_point_send_displs)
    1313           36 :       CALL counts_to_displs(feature_send_counts, feature_send_displs)
    1314           36 :       CALL group%alltoall(cache%route_point_send_counts, cache%route_point_recv_counts, 1)
    1315           36 :       CALL counts_to_displs(cache%route_point_recv_counts, cache%route_point_recv_displs)
    1316           36 :       CALL group%alltoall(feature_send_counts, feature_recv_counts, 1)
    1317           36 :       CALL counts_to_displs(feature_recv_counts, feature_recv_displs)
    1318              : 
    1319          108 :       nsend = SUM(cache%route_point_send_counts)
    1320          108 :       nrecv = SUM(cache%route_point_recv_counts)
    1321            0 :       ALLOCATE (cache%route_send_local_rows(nsend), &
    1322            0 :                 cache%route_row_offsets(nflat_local + 1), &
    1323            0 :                 cache%route_row_positions(nsend), &
    1324              :                 route_cursor(nproc), feature_cursor(nproc), &
    1325              :                 send_meta(2*SIZE(local_to_global)), &
    1326              :                 recv_meta(2*cache%chunk_feature_count), &
    1327              :                 meta_send_counts(nproc), meta_send_displs(nproc), &
    1328          612 :                 meta_recv_counts(nproc), meta_recv_displs(nproc))
    1329       979872 :       cache%route_send_local_rows = 0
    1330       979872 :       cache%route_row_positions = 0
    1331           36 :       send_meta = 0
    1332          108 :       route_cursor(:) = cache%route_point_send_displs + 1
    1333          108 :       feature_cursor(:) = feature_send_displs + 1
    1334           36 :       route_last_row = 0
    1335           36 :       route_last_position = 0
    1336           36 :       cache%route_row_offsets(1) = 1
    1337           36 :       row_position = 1
    1338       914863 :       DO local_row = 1, nflat_local
    1339       914827 :          feature_begin = cache%local_feature_offsets(local_row)
    1340       914827 :          feature_end = cache%local_feature_offsets(local_row + 1) - 1
    1341      1894663 :          DO local_feature = feature_begin, feature_end
    1342       979836 :             dest = route_feature_dest(local_feature)
    1343       979836 :             IF (route_last_row(dest) /= local_row) THEN
    1344       979836 :                point_pos = route_cursor(dest)
    1345       979836 :                route_cursor(dest) = route_cursor(dest) + 1
    1346       979836 :                route_last_row(dest) = local_row
    1347       979836 :                route_last_position(dest) = point_pos
    1348       979836 :                cache%route_send_local_rows(point_pos) = local_row
    1349       979836 :                cache%route_row_positions(row_position) = point_pos
    1350       979836 :                row_position = row_position + 1
    1351              :             ELSE
    1352            0 :                point_pos = route_last_position(dest)
    1353              :             END IF
    1354       979836 :             feature_pos = feature_cursor(dest)
    1355       979836 :             feature_cursor(dest) = feature_cursor(dest) + 1
    1356       979836 :             send_meta(2*feature_pos - 1) = local_to_global(local_feature)
    1357      1894663 :             send_meta(2*feature_pos) = point_pos - cache%route_point_send_displs(dest)
    1358              :          END DO
    1359       914863 :          cache%route_row_offsets(local_row + 1) = row_position
    1360              :       END DO
    1361           36 :       CPASSERT(row_position == nsend + 1)
    1362              : 
    1363              :       ! Tell each chunk owner which feature rows share a compressed route point.
    1364          108 :       meta_send_counts(:) = 2*feature_send_counts
    1365          108 :       meta_send_displs(:) = 2*feature_send_displs
    1366          108 :       meta_recv_counts(:) = 2*feature_recv_counts
    1367          108 :       meta_recv_displs(:) = 2*feature_recv_displs
    1368              :       CALL group%alltoall(send_meta, meta_send_counts, meta_send_displs, recv_meta, &
    1369           36 :                           meta_recv_counts, meta_recv_displs)
    1370              : 
    1371              :       ! Store the route-to-feature expansion in CSR form for gradient reduction.
    1372            0 :       ALLOCATE (cache%chunk_return_positions(cache%chunk_feature_count), &
    1373            0 :                 cache%route_chunk_offsets(nrecv + 1), &
    1374            0 :                 cache%route_chunk_rows(cache%chunk_feature_count), &
    1375          324 :                 chunk_route_counts(nrecv), chunk_cursor(nrecv))
    1376       979872 :       cache%chunk_return_positions = 0
    1377       979872 :       cache%route_chunk_rows = 0
    1378           36 :       chunk_route_counts = 0
    1379          108 :       DO pe = 1, nproc
    1380       979908 :          DO feature_pos = feature_recv_displs(pe) + 1, &
    1381          108 :             feature_recv_displs(pe) + feature_recv_counts(pe)
    1382       979836 :             meta_pos = 2*feature_pos
    1383       979836 :             row = recv_meta(meta_pos - 1)
    1384       979836 :             route_index = recv_meta(meta_pos)
    1385       979836 :             point_pos = cache%route_point_recv_displs(pe) + route_index
    1386       979836 :             CPASSERT(point_pos >= 1 .AND. point_pos <= nrecv)
    1387       979836 :             chunk_row = row - cache%chunk_feature_begin + 1
    1388       979836 :             CPASSERT(chunk_row >= 1 .AND. chunk_row <= cache%chunk_feature_count)
    1389       979836 :             cache%chunk_return_positions(chunk_row) = point_pos
    1390       979908 :             chunk_route_counts(point_pos) = chunk_route_counts(point_pos) + 1
    1391              :          END DO
    1392              :       END DO
    1393           36 :       cache%route_chunk_offsets(1) = 1
    1394       979872 :       DO point_pos = 1, nrecv
    1395              :          cache%route_chunk_offsets(point_pos + 1) = &
    1396       979872 :             cache%route_chunk_offsets(point_pos) + chunk_route_counts(point_pos)
    1397              :       END DO
    1398       979872 :       chunk_cursor(:) = cache%route_chunk_offsets(1:nrecv)
    1399          108 :       DO pe = 1, nproc
    1400       979908 :          DO feature_pos = feature_recv_displs(pe) + 1, &
    1401          108 :             feature_recv_displs(pe) + feature_recv_counts(pe)
    1402       979836 :             meta_pos = 2*feature_pos
    1403       979836 :             row = recv_meta(meta_pos - 1)
    1404       979836 :             chunk_row = row - cache%chunk_feature_begin + 1
    1405       979836 :             point_pos = cache%chunk_return_positions(chunk_row)
    1406       979836 :             cache%route_chunk_rows(chunk_cursor(point_pos)) = chunk_row
    1407       979908 :             chunk_cursor(point_pos) = chunk_cursor(point_pos) + 1
    1408              :          END DO
    1409              :       END DO
    1410              : 
    1411          108 :       CPASSERT(SUM(feature_send_counts) == SIZE(local_to_global))
    1412          108 :       CPASSERT(SUM(feature_recv_counts) == cache%chunk_feature_count)
    1413          108 :       CPASSERT(SUM(cache%route_point_send_counts) == nsend)
    1414          108 :       CPASSERT(SUM(cache%route_point_recv_counts) == nrecv)
    1415       979872 :       CPASSERT(ALL(cache%chunk_return_positions > 0))
    1416       979872 :       CPASSERT(ALL(cache%route_chunk_rows > 0))
    1417       979872 :       CPASSERT(ALL(cache%route_row_positions > 0))
    1418       979872 :       CPASSERT(ALL(cache%route_send_local_rows > 0))
    1419              : 
    1420            0 :       DEALLOCATE (chunk_cursor, chunk_route_counts, feature_cursor, feature_recv_counts, &
    1421            0 :                   feature_recv_displs, feature_send_counts, feature_send_displs, &
    1422            0 :                   meta_recv_counts, meta_recv_displs, meta_send_counts, meta_send_displs, &
    1423            0 :                   recv_meta, route_cursor, route_feature_dest, route_last_position, route_last_row, &
    1424           36 :                   send_meta)
    1425              : 
    1426           36 :    END SUBROUTINE build_atom_chunk_routes
    1427              : 
    1428              : ! **************************************************************************************************
    1429              : !> \brief Materialize the current rank's atom chunk static layout.
    1430              : !> \param cache ...
    1431              : ! **************************************************************************************************
    1432           36 :    SUBROUTINE build_atom_chunk_layout(cache)
    1433              :       TYPE(skala_gpw_layout_cache_type), INTENT(INOUT)   :: cache
    1434              : 
    1435              :       INTEGER                                            :: irow, max_grid_size, row_begin, row_end
    1436              : 
    1437           36 :       IF (cache%chunk_feature_count <= 0 .OR. cache%chunk_natom <= 0) RETURN
    1438              : 
    1439           36 :       row_begin = cache%chunk_feature_begin
    1440           36 :       row_end = row_begin + cache%chunk_feature_count - 1
    1441            0 :       ALLOCATE (cache%chunk_grid_coords(3, cache%chunk_feature_count), &
    1442            0 :                 cache%chunk_grid_weights(cache%chunk_feature_count), &
    1443            0 :                 cache%chunk_atomic_grid_weights(cache%chunk_feature_count), &
    1444            0 :                 cache%chunk_atomic_grid_sizes(cache%chunk_natom), &
    1445            0 :                 cache%chunk_coarse_0_atomic_coords(3, cache%chunk_natom), &
    1446          396 :                 cache%chunk_feature_indices(cache%chunk_feature_count))
    1447      3919380 :       cache%chunk_grid_coords(:, :) = cache%grid_coords(:, row_begin:row_end)
    1448       979872 :       cache%chunk_grid_weights(:) = cache%grid_weights(row_begin:row_end)
    1449       979872 :       cache%chunk_atomic_grid_weights(:) = cache%atomic_grid_weights(row_begin:row_end)
    1450              :       cache%chunk_atomic_grid_sizes(:) = &
    1451           85 :          cache%atomic_grid_sizes(cache%chunk_atom_begin:cache%chunk_atom_end)
    1452              :       cache%chunk_coarse_0_atomic_coords(:, :) = &
    1453          232 :          cache%coarse_0_atomic_coords(:, cache%chunk_atom_begin:cache%chunk_atom_end)
    1454              : 
    1455           85 :       max_grid_size = MAXVAL(INT(cache%chunk_atomic_grid_sizes))
    1456           72 :       ALLOCATE (cache%chunk_atomic_grid_size_bound_shape(0, max_grid_size))
    1457       769141 :       cache%chunk_atomic_grid_size_bound_shape = 0_int_8
    1458       979872 :       DO irow = 1, cache%chunk_feature_count
    1459       979872 :          cache%chunk_feature_indices(irow) = INT(irow - 1, KIND=int_8)
    1460              :       END DO
    1461              : 
    1462              :    END SUBROUTINE build_atom_chunk_layout
    1463              : 
    1464              : ! **************************************************************************************************
    1465              : !> \brief Send local dynamic feature rows to their atom-chunk owner ranks.
    1466              : !> \param features ...
    1467              : !> \param local_dynamic ...
    1468              : !> \param group ...
    1469              : !> \param collapse_spin_dynamics ...
    1470              : ! **************************************************************************************************
    1471           40 :    SUBROUTINE route_atom_chunk_dynamics(features, local_dynamic, group, collapse_spin_dynamics)
    1472              :       TYPE(skala_gpw_feature_type), INTENT(INOUT)        :: features
    1473              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: local_dynamic
    1474              : 
    1475              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    1476              :       LOGICAL, INTENT(IN)                                :: collapse_spin_dynamics
    1477              : 
    1478              :       INTEGER                                            :: chunk_row, dyn_base, local_row, &
    1479              :                                                             ndynamic_route_per_point, nrecv, nsend, &
    1480              :                                                             point_pos, src_base
    1481              :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: recv_counts, recv_displs, send_counts, send_displs
    1482           40 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: recv_dynamic, send_dynamic
    1483              : 
    1484           40 :       nsend = SIZE(cached_layout%route_send_local_rows)
    1485          120 :       nrecv = SUM(cached_layout%route_point_recv_counts)
    1486          120 :       CPASSERT(nsend == SUM(cached_layout%route_point_send_counts))
    1487           40 :       ndynamic_route_per_point = ndynamic_per_point
    1488           40 :       IF (collapse_spin_dynamics) ndynamic_route_per_point = nrks_dynamic_per_point
    1489              : 
    1490            0 :       ALLOCATE (send_dynamic(MAX(1, ndynamic_route_per_point*nsend)), &
    1491            0 :                 recv_dynamic(MAX(1, ndynamic_route_per_point*nrecv)), &
    1492              :                 send_counts(cached_layout%nproc), send_displs(cached_layout%nproc), &
    1493              :                 recv_counts(cached_layout%nproc), &
    1494          400 :                 recv_displs(cached_layout%nproc))
    1495          120 :       send_counts(:) = ndynamic_route_per_point*cached_layout%route_point_send_counts
    1496          120 :       send_displs(:) = ndynamic_route_per_point*cached_layout%route_point_send_displs
    1497          120 :       recv_counts(:) = ndynamic_route_per_point*cached_layout%route_point_recv_counts
    1498          120 :       recv_displs(:) = ndynamic_route_per_point*cached_layout%route_point_recv_displs
    1499              : !$OMP PARALLEL DO DEFAULT(NONE) &
    1500              : !$OMP SHARED(cached_layout, local_dynamic, ndynamic_route_per_point, nsend, send_dynamic) &
    1501           40 : !$OMP PRIVATE(dyn_base, local_row, point_pos, src_base)
    1502              :       DO point_pos = 1, nsend
    1503              :          dyn_base = ndynamic_route_per_point*(point_pos - 1)
    1504              :          local_row = cached_layout%route_send_local_rows(point_pos)
    1505              :          src_base = ndynamic_route_per_point*(local_row - 1)
    1506              :          send_dynamic(dyn_base + 1:dyn_base + ndynamic_route_per_point) = &
    1507              :             local_dynamic(src_base + 1:src_base + ndynamic_route_per_point)
    1508              :       END DO
    1509              : !$OMP END PARALLEL DO
    1510              : 
    1511              :       CALL group%alltoall(send_dynamic, send_counts, send_displs, recv_dynamic, recv_counts, &
    1512           40 :                           recv_displs)
    1513              : 
    1514           40 :       features%uses_collapsed_rks_dynamic = collapse_spin_dynamics
    1515           40 :       IF (cached_layout%chunk_feature_count > 0) THEN
    1516           40 :          IF (collapse_spin_dynamics) THEN
    1517            0 :             ALLOCATE (features%chunk_density(cached_layout%chunk_feature_count, 1), &
    1518            0 :                       features%chunk_grad(cached_layout%chunk_feature_count, 3, 1), &
    1519          204 :                       features%chunk_kin(cached_layout%chunk_feature_count, 1))
    1520              :          ELSE
    1521            0 :             ALLOCATE (features%chunk_density(cached_layout%chunk_feature_count, 2), &
    1522            0 :                       features%chunk_grad(cached_layout%chunk_feature_count, 3, 2), &
    1523           42 :                       features%chunk_kin(cached_layout%chunk_feature_count, 2))
    1524              :          END IF
    1525              : 
    1526              : !$OMP PARALLEL DO DEFAULT(NONE) &
    1527              : !$OMP SHARED(cached_layout, collapse_spin_dynamics, features, ndynamic_route_per_point, nrecv, recv_dynamic) &
    1528           40 : !$OMP PRIVATE(chunk_row, dyn_base, point_pos)
    1529              :          DO chunk_row = 1, cached_layout%chunk_feature_count
    1530              :             point_pos = cached_layout%chunk_return_positions(chunk_row)
    1531              :             CPASSERT(point_pos >= 1 .AND. point_pos <= nrecv)
    1532              :             dyn_base = ndynamic_route_per_point*(point_pos - 1)
    1533              :             IF (collapse_spin_dynamics) THEN
    1534              :                features%chunk_density(chunk_row, 1) = recv_dynamic(dyn_base + 1)
    1535              :                features%chunk_grad(chunk_row, 1, 1) = recv_dynamic(dyn_base + 2)
    1536              :                features%chunk_grad(chunk_row, 2, 1) = recv_dynamic(dyn_base + 3)
    1537              :                features%chunk_grad(chunk_row, 3, 1) = recv_dynamic(dyn_base + 4)
    1538              :                features%chunk_kin(chunk_row, 1) = recv_dynamic(dyn_base + 5)
    1539              :             ELSE
    1540              :                features%chunk_density(chunk_row, :) = recv_dynamic(dyn_base + 1:dyn_base + 2)
    1541              :                features%chunk_grad(chunk_row, 1, 1) = recv_dynamic(dyn_base + 3)
    1542              :                features%chunk_grad(chunk_row, 2, 1) = recv_dynamic(dyn_base + 4)
    1543              :                features%chunk_grad(chunk_row, 3, 1) = recv_dynamic(dyn_base + 5)
    1544              :                features%chunk_grad(chunk_row, 1, 2) = recv_dynamic(dyn_base + 6)
    1545              :                features%chunk_grad(chunk_row, 2, 2) = recv_dynamic(dyn_base + 7)
    1546              :                features%chunk_grad(chunk_row, 3, 2) = recv_dynamic(dyn_base + 8)
    1547              :                features%chunk_kin(chunk_row, :) = recv_dynamic(dyn_base + 9:dyn_base + 10)
    1548              :             END IF
    1549              :          END DO
    1550              : !$OMP END PARALLEL DO
    1551              :       END IF
    1552              : 
    1553           40 :       DEALLOCATE (recv_counts, recv_displs, recv_dynamic, send_counts, send_displs, send_dynamic)
    1554              : 
    1555           40 :    END SUBROUTINE route_atom_chunk_dynamics
    1556              : 
    1557              : ! **************************************************************************************************
    1558              : !> \brief Extract the current rank's atom chunk from the global dynamic feature arrays.
    1559              : !> \param features ...
    1560              : ! **************************************************************************************************
    1561            0 :    SUBROUTINE extract_atom_chunk_dynamics(features)
    1562              :       TYPE(skala_gpw_feature_type), INTENT(INOUT)        :: features
    1563              : 
    1564              :       INTEGER                                            :: row_begin, row_end
    1565              : 
    1566            0 :       CPASSERT(cached_layout%chunk_feature_count > 0)
    1567            0 :       row_begin = cached_layout%chunk_feature_begin
    1568            0 :       row_end = row_begin + cached_layout%chunk_feature_count - 1
    1569            0 :       ALLOCATE (features%chunk_density(cached_layout%chunk_feature_count, 2), &
    1570            0 :                 features%chunk_grad(cached_layout%chunk_feature_count, 3, 2), &
    1571            0 :                 features%chunk_kin(cached_layout%chunk_feature_count, 2))
    1572            0 :       features%chunk_density(:, :) = features%density(row_begin:row_end, :)
    1573            0 :       features%chunk_grad(:, :, :) = features%grad(row_begin:row_end, :, :)
    1574            0 :       features%chunk_kin(:, :) = features%kin(row_begin:row_end, :)
    1575              : 
    1576            0 :    END SUBROUTINE extract_atom_chunk_dynamics
    1577              : 
    1578              : ! **************************************************************************************************
    1579              : !> \brief Compute a local signature for optional integration weights.
    1580              : !> \param weights ...
    1581              : !> \param has_weights ...
    1582              : !> \param weight_sum ...
    1583              : !> \param weight_sumsq ...
    1584              : ! **************************************************************************************************
    1585           48 :    SUBROUTINE weights_signature(weights, has_weights, weight_sum, weight_sumsq)
    1586              :       TYPE(pw_r3d_rs_type), OPTIONAL, POINTER            :: weights
    1587              :       LOGICAL, INTENT(OUT)                               :: has_weights
    1588              :       REAL(KIND=dp), INTENT(OUT)                         :: weight_sum, weight_sumsq
    1589              : 
    1590           48 :       has_weights = .FALSE.
    1591           48 :       weight_sum = 0.0_dp
    1592           48 :       weight_sumsq = 0.0_dp
    1593           48 :       IF (PRESENT(weights)) THEN
    1594           48 :          IF (ASSOCIATED(weights)) THEN
    1595            0 :             has_weights = .TRUE.
    1596            0 :             weight_sum = SUM(weights%array)
    1597            0 :             weight_sumsq = SUM(weights%array*weights%array)
    1598              :          END IF
    1599              :       END IF
    1600              : 
    1601           48 :    END SUBROUTINE weights_signature
    1602              : 
    1603              : ! **************************************************************************************************
    1604              : !> \brief Release cached layout arrays.
    1605              : !> \param cache ...
    1606              : ! **************************************************************************************************
    1607           36 :    SUBROUTINE release_layout_cache(cache)
    1608              :       TYPE(skala_gpw_layout_cache_type), INTENT(INOUT)   :: cache
    1609              : 
    1610           36 :       IF (cache%inputs_active) THEN
    1611            0 :          CALL torch_dict_release(cache%inputs)
    1612            0 :          cache%inputs_active = .FALSE.
    1613              :       END IF
    1614              : 
    1615           36 :       IF (cache%chunk_inputs_active) THEN
    1616            4 :          CALL torch_dict_release(cache%chunk_inputs)
    1617            4 :          cache%chunk_inputs_active = .FALSE.
    1618              :       END IF
    1619              : 
    1620           36 :       IF (cache%dynamic_tensors_active) THEN
    1621            2 :          CALL torch_tensor_release(cache%density_t)
    1622            2 :          CALL torch_tensor_release(cache%grad_t)
    1623            2 :          CALL torch_tensor_release(cache%kin_t)
    1624            2 :          cache%dynamic_tensors_active = .FALSE.
    1625              :       END IF
    1626              : 
    1627           36 :       IF (cache%chunk_dynamic_tensors_active) THEN
    1628            4 :          IF (cache%chunk_dynamic_input_views_active) THEN
    1629            4 :             CALL torch_tensor_release(cache%chunk_density_input_t)
    1630            4 :             CALL torch_tensor_release(cache%chunk_grad_input_t)
    1631            4 :             CALL torch_tensor_release(cache%chunk_kin_input_t)
    1632            4 :             cache%chunk_dynamic_input_views_active = .FALSE.
    1633              :          END IF
    1634            4 :          CALL torch_tensor_release(cache%chunk_density_t)
    1635            4 :          CALL torch_tensor_release(cache%chunk_grad_t)
    1636            4 :          CALL torch_tensor_release(cache%chunk_kin_t)
    1637            4 :          cache%chunk_dynamic_tensors_active = .FALSE.
    1638              :       END IF
    1639              : 
    1640           36 :       IF (cache%static_tensors_active) THEN
    1641            2 :          CALL torch_tensor_release(cache%grid_coords_t)
    1642            2 :          CALL torch_tensor_release(cache%grid_weights_t)
    1643            2 :          CALL torch_tensor_release(cache%atomic_grid_weights_t)
    1644            2 :          CALL torch_tensor_release(cache%atomic_grid_sizes_t)
    1645            2 :          CALL torch_tensor_release(cache%coarse_0_atomic_coords_t)
    1646            2 :          CALL torch_tensor_release(cache%atomic_grid_size_bound_shape_t)
    1647            2 :          CALL torch_tensor_release(cache%local_feature_indices_t)
    1648            2 :          CALL torch_dict_release(cache%static_inputs)
    1649            2 :          cache%static_tensors_active = .FALSE.
    1650              :       END IF
    1651              : 
    1652           36 :       IF (cache%chunk_static_tensors_active) THEN
    1653            4 :          CALL torch_tensor_release(cache%chunk_grid_coords_t)
    1654            4 :          CALL torch_tensor_release(cache%chunk_grid_weights_t)
    1655            4 :          CALL torch_tensor_release(cache%chunk_atomic_grid_weights_t)
    1656            4 :          CALL torch_tensor_release(cache%chunk_atomic_grid_sizes_t)
    1657            4 :          CALL torch_tensor_release(cache%chunk_coarse_0_atomic_coords_t)
    1658            4 :          CALL torch_tensor_release(cache%chunk_atomic_grid_size_bound_shape_t)
    1659            4 :          CALL torch_tensor_release(cache%chunk_feature_indices_t)
    1660            4 :          CALL torch_dict_release(cache%chunk_static_inputs)
    1661              :          cache%chunk_static_tensors_active = .FALSE.
    1662              :       END IF
    1663              : 
    1664           36 :       IF (ALLOCATED(cache%chunk_feature_counts)) DEALLOCATE (cache%chunk_feature_counts)
    1665           36 :       IF (ALLOCATED(cache%chunk_feature_displs)) DEALLOCATE (cache%chunk_feature_displs)
    1666           36 :       IF (ALLOCATED(cache%chunk_grad_counts)) DEALLOCATE (cache%chunk_grad_counts)
    1667           36 :       IF (ALLOCATED(cache%chunk_grad_displs)) DEALLOCATE (cache%chunk_grad_displs)
    1668           36 :       IF (ALLOCATED(cache%chunk_return_positions)) DEALLOCATE (cache%chunk_return_positions)
    1669           36 :       IF (ALLOCATED(cache%route_chunk_offsets)) DEALLOCATE (cache%route_chunk_offsets)
    1670           36 :       IF (ALLOCATED(cache%route_chunk_rows)) DEALLOCATE (cache%route_chunk_rows)
    1671           36 :       IF (ALLOCATED(cache%route_point_recv_counts)) DEALLOCATE (cache%route_point_recv_counts)
    1672           36 :       IF (ALLOCATED(cache%route_point_recv_displs)) DEALLOCATE (cache%route_point_recv_displs)
    1673           36 :       IF (ALLOCATED(cache%route_point_send_counts)) DEALLOCATE (cache%route_point_send_counts)
    1674           36 :       IF (ALLOCATED(cache%route_point_send_displs)) DEALLOCATE (cache%route_point_send_displs)
    1675           36 :       IF (ALLOCATED(cache%route_row_offsets)) DEALLOCATE (cache%route_row_offsets)
    1676           36 :       IF (ALLOCATED(cache%route_row_positions)) DEALLOCATE (cache%route_row_positions)
    1677           36 :       IF (ALLOCATED(cache%route_send_local_rows)) DEALLOCATE (cache%route_send_local_rows)
    1678           36 :       IF (ALLOCATED(cache%dynamic_counts)) DEALLOCATE (cache%dynamic_counts)
    1679           36 :       IF (ALLOCATED(cache%dynamic_displs)) DEALLOCATE (cache%dynamic_displs)
    1680           36 :       IF (ALLOCATED(cache%feature_counts)) DEALLOCATE (cache%feature_counts)
    1681           36 :       IF (ALLOCATED(cache%feature_displs)) DEALLOCATE (cache%feature_displs)
    1682           36 :       IF (ALLOCATED(cache%feature_source_points)) DEALLOCATE (cache%feature_source_points)
    1683           36 :       IF (ALLOCATED(cache%global_to_feature)) DEALLOCATE (cache%global_to_feature)
    1684           36 :       IF (ALLOCATED(cache%feature_index)) DEALLOCATE (cache%feature_index)
    1685           36 :       IF (ALLOCATED(cache%atomic_grid_sizes)) DEALLOCATE (cache%atomic_grid_sizes)
    1686           36 :       IF (ALLOCATED(cache%chunk_atomic_grid_sizes)) DEALLOCATE (cache%chunk_atomic_grid_sizes)
    1687           36 :       IF (ALLOCATED(cache%chunk_feature_indices)) DEALLOCATE (cache%chunk_feature_indices)
    1688           36 :       IF (ALLOCATED(cache%local_feature_counts)) DEALLOCATE (cache%local_feature_counts)
    1689           36 :       IF (ALLOCATED(cache%local_feature_indices)) DEALLOCATE (cache%local_feature_indices)
    1690           36 :       IF (ALLOCATED(cache%local_feature_offsets)) DEALLOCATE (cache%local_feature_offsets)
    1691           36 :       IF (ALLOCATED(cache%local_feature_points)) DEALLOCATE (cache%local_feature_points)
    1692           36 :       IF (ALLOCATED(cache%local_feature_rows)) DEALLOCATE (cache%local_feature_rows)
    1693           36 :       IF (ALLOCATED(cache%atomic_grid_size_bound_shape)) THEN
    1694            4 :          DEALLOCATE (cache%atomic_grid_size_bound_shape)
    1695              :       END IF
    1696           36 :       IF (ALLOCATED(cache%chunk_atomic_grid_size_bound_shape)) THEN
    1697            4 :          DEALLOCATE (cache%chunk_atomic_grid_size_bound_shape)
    1698              :       END IF
    1699           36 :       IF (ALLOCATED(cache%atomic_grid_weights)) DEALLOCATE (cache%atomic_grid_weights)
    1700           36 :       IF (ALLOCATED(cache%chunk_atomic_grid_weights)) DEALLOCATE (cache%chunk_atomic_grid_weights)
    1701           36 :       IF (ALLOCATED(cache%chunk_grid_weights)) DEALLOCATE (cache%chunk_grid_weights)
    1702           36 :       IF (ALLOCATED(cache%grid_weights)) DEALLOCATE (cache%grid_weights)
    1703           36 :       IF (ALLOCATED(cache%atom_coords)) DEALLOCATE (cache%atom_coords)
    1704           36 :       IF (ALLOCATED(cache%chunk_coarse_0_atomic_coords)) THEN
    1705            4 :          DEALLOCATE (cache%chunk_coarse_0_atomic_coords)
    1706              :       END IF
    1707           36 :       IF (ALLOCATED(cache%coarse_0_atomic_coords)) DEALLOCATE (cache%coarse_0_atomic_coords)
    1708           36 :       IF (ALLOCATED(cache%chunk_grid_coords)) DEALLOCATE (cache%chunk_grid_coords)
    1709           36 :       IF (ALLOCATED(cache%grid_coords)) DEALLOCATE (cache%grid_coords)
    1710              : 
    1711           36 :       cache%chunk_atom_begin = 1
    1712           36 :       cache%chunk_atom_end = 0
    1713           36 :       cache%chunk_feature_begin = 1
    1714           36 :       cache%chunk_feature_count = 0
    1715           36 :       cache%chunk_natom = 0
    1716           36 :       cache%natom = 0
    1717           36 :       cache%nflat = 0
    1718           36 :       cache%nflat_local = 0
    1719           36 :       cache%npoint = 0
    1720           36 :       cache%nproc = 0
    1721           36 :       cache%atom_partition = skala_gpw_atom_partition_hard
    1722          360 :       cache%bo = 0
    1723          360 :       cache%bounds = 0
    1724          144 :       cache%npts = 0
    1725           36 :       cache%dvol = 0.0_dp
    1726           36 :       cache%weight_sum = 0.0_dp
    1727           36 :       cache%weight_sumsq = 0.0_dp
    1728          468 :       cache%cell_hmat = 0.0_dp
    1729          468 :       cache%dh = 0.0_dp
    1730           36 :       cache%active = .FALSE.
    1731           36 :       cache%has_weights = .FALSE.
    1732           36 :       cache%chunk_dynamic_tensors_active = .FALSE.
    1733           36 :       cache%chunk_dynamic_input_views_active = .FALSE.
    1734           36 :       cache%chunk_inputs_active = .FALSE.
    1735           36 :       cache%chunk_inputs_use_collapsed_rks = .FALSE.
    1736           36 :       cache%chunk_static_tensors_active = .FALSE.
    1737           36 :       cache%dynamic_tensors_active = .FALSE.
    1738           36 :       cache%inputs_active = .FALSE.
    1739           36 :       cache%static_tensors_active = .FALSE.
    1740              : 
    1741           36 :    END SUBROUTINE release_layout_cache
    1742              : 
    1743              : ! **************************************************************************************************
    1744              : !> \brief Release Torch objects and backing arrays owned by a feature bundle.
    1745              : !> \param features ...
    1746              : ! **************************************************************************************************
    1747          104 :    SUBROUTINE skala_gpw_feature_release(features)
    1748              :       TYPE(skala_gpw_feature_type), INTENT(INOUT)        :: features
    1749              : 
    1750          104 :       IF (features%active) THEN
    1751           52 :          IF (features%owns_dynamic_tensors) THEN
    1752            4 :             IF (features%uses_collapsed_rks_dynamic) THEN
    1753            4 :                CALL torch_tensor_release(features%density_input_t)
    1754            4 :                CALL torch_tensor_release(features%grad_input_t)
    1755            4 :                CALL torch_tensor_release(features%kin_input_t)
    1756              :             END IF
    1757            4 :             CALL torch_tensor_release(features%density_t)
    1758            4 :             CALL torch_tensor_release(features%grad_t)
    1759            4 :             CALL torch_tensor_release(features%kin_t)
    1760              :          END IF
    1761           52 :          IF (features%owns_static_tensors) THEN
    1762            4 :             CALL torch_tensor_release(features%grid_coords_t)
    1763            4 :             CALL torch_tensor_release(features%grid_weights_t)
    1764            4 :             CALL torch_tensor_release(features%atomic_grid_weights_t)
    1765            4 :             CALL torch_tensor_release(features%atomic_grid_sizes_t)
    1766            4 :             CALL torch_tensor_release(features%atomic_grid_size_bound_shape_t)
    1767              :          END IF
    1768           52 :          IF (features%owns_grid_coordinate_tensor) THEN
    1769            6 :             CALL torch_tensor_release(features%grid_coords_t)
    1770              :          END IF
    1771           52 :          IF (features%owns_weight_tensors) THEN
    1772            8 :             CALL torch_tensor_release(features%grid_weights_t)
    1773            8 :             CALL torch_tensor_release(features%atomic_grid_weights_t)
    1774              :          END IF
    1775           52 :          IF (features%owns_static_tensors .OR. features%owns_coordinate_tensor) THEN
    1776           12 :             CALL torch_tensor_release(features%coarse_0_atomic_coords_t)
    1777              :          END IF
    1778           52 :          IF (features%owns_inputs) CALL torch_dict_release(features%inputs)
    1779           52 :          features%active = .FALSE.
    1780           52 :          features%owns_coordinate_tensor = .FALSE.
    1781           52 :          features%owns_grid_coordinate_tensor = .FALSE.
    1782           52 :          features%owns_weight_tensors = .FALSE.
    1783           52 :          features%owns_dynamic_tensors = .TRUE.
    1784           52 :          features%owns_inputs = .TRUE.
    1785           52 :          features%owns_static_tensors = .TRUE.
    1786              :          features%uses_atom_chunk_routing = .FALSE.
    1787           52 :          features%uses_atom_chunks = .FALSE.
    1788              :          features%uses_collapsed_rks_dynamic = .FALSE.
    1789              :       END IF
    1790              : 
    1791          104 :       IF (ALLOCATED(features%chunk_density)) DEALLOCATE (features%chunk_density)
    1792          104 :       IF (ALLOCATED(features%chunk_grad)) DEALLOCATE (features%chunk_grad)
    1793          104 :       IF (ALLOCATED(features%chunk_kin)) DEALLOCATE (features%chunk_kin)
    1794          104 :       IF (ALLOCATED(features%density)) DEALLOCATE (features%density)
    1795          104 :       IF (ALLOCATED(features%grad)) DEALLOCATE (features%grad)
    1796          104 :       IF (ALLOCATED(features%kin)) DEALLOCATE (features%kin)
    1797          104 :       IF (ALLOCATED(features%chunk_grad_counts)) DEALLOCATE (features%chunk_grad_counts)
    1798          104 :       IF (ALLOCATED(features%chunk_grad_displs)) DEALLOCATE (features%chunk_grad_displs)
    1799          104 :       NULLIFY (features%route_point_recv_counts, features%route_point_recv_displs, &
    1800          104 :                features%route_point_send_counts, features%route_point_send_displs, &
    1801          104 :                features%route_chunk_offsets, features%route_chunk_rows, &
    1802          104 :                features%route_row_offsets, features%route_row_positions, &
    1803          104 :                features%route_send_local_rows)
    1804          104 :       IF (ALLOCATED(features%feature_index)) DEALLOCATE (features%feature_index)
    1805          104 :       IF (ALLOCATED(features%local_feature_counts)) DEALLOCATE (features%local_feature_counts)
    1806          104 :       IF (ALLOCATED(features%local_feature_offsets)) DEALLOCATE (features%local_feature_offsets)
    1807          104 :       IF (ALLOCATED(features%local_feature_rows)) DEALLOCATE (features%local_feature_rows)
    1808          104 :       IF (ALLOCATED(features%grid_coords)) DEALLOCATE (features%grid_coords)
    1809          104 :       IF (ALLOCATED(features%grid_weights)) DEALLOCATE (features%grid_weights)
    1810          104 :       IF (ALLOCATED(features%atomic_grid_weights)) DEALLOCATE (features%atomic_grid_weights)
    1811          104 :       IF (ALLOCATED(features%atomic_grid_sizes)) DEALLOCATE (features%atomic_grid_sizes)
    1812          104 :       IF (ALLOCATED(features%coarse_0_atomic_coords)) DEALLOCATE (features%coarse_0_atomic_coords)
    1813          104 :       IF (ALLOCATED(features%atomic_grid_size_bound_shape)) THEN
    1814            4 :          DEALLOCATE (features%atomic_grid_size_bound_shape)
    1815              :       END IF
    1816          104 :       features%chunk_feature_count = 0
    1817          104 :       features%nflat = 0
    1818          104 :       features%nflat_local = 0
    1819          104 :       features%atom_partition = skala_gpw_atom_partition_hard
    1820          104 :       features%uses_atom_chunk_routing = .FALSE.
    1821          104 :       features%uses_collapsed_rks_dynamic = .FALSE.
    1822              : 
    1823          104 :    END SUBROUTINE skala_gpw_feature_release
    1824              : 
    1825              : ! **************************************************************************************************
    1826              : !> \brief Return how many atom-contiguous subchunks the cached rank chunk needs.
    1827              : !> \param max_rows ...
    1828              : !> \return ...
    1829              : ! **************************************************************************************************
    1830           42 :    FUNCTION skala_gpw_atom_subchunk_count(max_rows) RESULT(nsubchunks)
    1831              :       INTEGER, INTENT(IN)                                :: max_rows
    1832              :       INTEGER                                            :: nsubchunks
    1833              : 
    1834              :       INTEGER                                            :: atom_rows, iatom, rows
    1835              : 
    1836           42 :       nsubchunks = 0
    1837           42 :       IF (.NOT. cached_layout%active) RETURN
    1838           42 :       IF (cached_layout%chunk_natom <= 0) RETURN
    1839           42 :       IF (max_rows <= 0) THEN
    1840           42 :          nsubchunks = 1
    1841              :          RETURN
    1842              :       END IF
    1843              : 
    1844              :       rows = 0
    1845           99 :       DO iatom = 1, cached_layout%chunk_natom
    1846           57 :          atom_rows = INT(cached_layout%chunk_atomic_grid_sizes(iatom))
    1847           57 :          IF (rows > 0 .AND. rows + atom_rows > max_rows) THEN
    1848            4 :             nsubchunks = nsubchunks + 1
    1849            4 :             rows = 0
    1850              :          END IF
    1851           99 :          rows = rows + atom_rows
    1852              :       END DO
    1853           42 :       IF (rows > 0) nsubchunks = nsubchunks + 1
    1854           42 :       nsubchunks = MAX(1, nsubchunks)
    1855              : 
    1856           42 :    END FUNCTION skala_gpw_atom_subchunk_count
    1857              : 
    1858              : ! **************************************************************************************************
    1859              : !> \brief Build atom and row bounds for all atom-contiguous subchunks in one pass.
    1860              : !> \param max_rows ...
    1861              : !> \param atom_begin ...
    1862              : !> \param atom_count ...
    1863              : !> \param row_begin ...
    1864              : !> \param row_count ...
    1865              : ! **************************************************************************************************
    1866            2 :    SUBROUTINE skala_gpw_atom_subchunk_layout(max_rows, atom_begin, atom_count, row_begin, &
    1867              :                                              row_count)
    1868              :       INTEGER, INTENT(IN)                                :: max_rows
    1869              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: atom_begin, atom_count, row_begin, &
    1870              :                                                             row_count
    1871              : 
    1872              :       INTEGER :: atom_begin_tmp, atom_count_tmp, atom_rows, iatom, insert_at, nsubchunks, &
    1873              :          row_begin_tmp, row_count_tmp, rows, subchunk
    1874              :       INTEGER(KIND=int_8)                                :: padded_rows_tmp
    1875            2 :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:)     :: padded_rows
    1876              : 
    1877            2 :       nsubchunks = skala_gpw_atom_subchunk_count(max_rows)
    1878            0 :       ALLOCATE (atom_begin(nsubchunks), atom_count(nsubchunks), row_begin(nsubchunks), &
    1879           12 :                 row_count(nsubchunks))
    1880            2 :       IF (nsubchunks == 0) RETURN
    1881              : 
    1882            2 :       CPASSERT(cached_layout%active)
    1883            2 :       CPASSERT(cached_layout%chunk_natom > 0)
    1884              : 
    1885            2 :       atom_begin = 0
    1886            2 :       atom_count = 0
    1887            2 :       row_begin = 0
    1888            2 :       row_count = 0
    1889              : 
    1890            2 :       IF (max_rows <= 0) THEN
    1891            0 :          atom_begin(1) = 1
    1892            0 :          atom_count(1) = cached_layout%chunk_natom
    1893            0 :          row_begin(1) = 1
    1894            0 :          row_count(1) = cached_layout%chunk_feature_count
    1895            0 :          RETURN
    1896              :       END IF
    1897              : 
    1898            2 :       subchunk = 1
    1899            2 :       atom_begin(subchunk) = 1
    1900            2 :       row_begin(subchunk) = 1
    1901            2 :       rows = 0
    1902            6 :       DO iatom = 1, cached_layout%chunk_natom
    1903            4 :          atom_rows = INT(cached_layout%chunk_atomic_grid_sizes(iatom))
    1904            4 :          IF (rows > 0 .AND. rows + atom_rows > max_rows) THEN
    1905            2 :             atom_count(subchunk) = iatom - atom_begin(subchunk)
    1906            2 :             row_count(subchunk) = rows
    1907            2 :             subchunk = subchunk + 1
    1908            2 :             atom_begin(subchunk) = iatom
    1909            2 :             row_begin(subchunk) = row_begin(subchunk - 1) + row_count(subchunk - 1)
    1910            2 :             rows = 0
    1911              :          END IF
    1912            6 :          rows = rows + atom_rows
    1913              :       END DO
    1914            2 :       atom_count(subchunk) = cached_layout%chunk_natom - atom_begin(subchunk) + 1
    1915            2 :       row_count(subchunk) = rows
    1916              : 
    1917            2 :       CPASSERT(subchunk == nsubchunks)
    1918              : 
    1919            6 :       ALLOCATE (padded_rows(nsubchunks))
    1920            6 :       DO subchunk = 1, nsubchunks
    1921            4 :          iatom = atom_begin(subchunk) + atom_count(subchunk) - 1
    1922              :          padded_rows(subchunk) = INT(atom_count(subchunk), KIND=int_8)* &
    1923              :                                  MAXVAL(cached_layout%chunk_atomic_grid_sizes( &
    1924           10 :                                         atom_begin(subchunk):iatom))
    1925              :       END DO
    1926              : 
    1927              :       ! Evaluate the largest padded block first so Torch can reuse its CUDA allocations.
    1928            4 :       DO subchunk = 2, nsubchunks
    1929            2 :          padded_rows_tmp = padded_rows(subchunk)
    1930            2 :          atom_begin_tmp = atom_begin(subchunk)
    1931            2 :          atom_count_tmp = atom_count(subchunk)
    1932            2 :          row_begin_tmp = row_begin(subchunk)
    1933            2 :          row_count_tmp = row_count(subchunk)
    1934            2 :          insert_at = subchunk
    1935            2 :          DO WHILE (insert_at > 1 .AND. padded_rows(insert_at - 1) < padded_rows_tmp)
    1936            0 :             padded_rows(insert_at) = padded_rows(insert_at - 1)
    1937            0 :             atom_begin(insert_at) = atom_begin(insert_at - 1)
    1938            0 :             atom_count(insert_at) = atom_count(insert_at - 1)
    1939            0 :             row_begin(insert_at) = row_begin(insert_at - 1)
    1940            0 :             row_count(insert_at) = row_count(insert_at - 1)
    1941            2 :             insert_at = insert_at - 1
    1942              :          END DO
    1943            2 :          padded_rows(insert_at) = padded_rows_tmp
    1944            2 :          atom_begin(insert_at) = atom_begin_tmp
    1945            2 :          atom_count(insert_at) = atom_count_tmp
    1946            2 :          row_begin(insert_at) = row_begin_tmp
    1947            4 :          row_count(insert_at) = row_count_tmp
    1948              :       END DO
    1949            2 :       DEALLOCATE (padded_rows)
    1950              : 
    1951              :    END SUBROUTINE skala_gpw_atom_subchunk_layout
    1952              : 
    1953              : ! **************************************************************************************************
    1954              : !> \brief Build an atom-contiguous subchunk feature bundle from a rank-local atom chunk.
    1955              : !> \param parent ...
    1956              : !> \param features ...
    1957              : !> \param subchunk_index ...
    1958              : !> \param max_rows ...
    1959              : !> \param requires_grad ...
    1960              : ! **************************************************************************************************
    1961            0 :    SUBROUTINE skala_gpw_feature_build_atom_subchunk(parent, features, subchunk_index, &
    1962              :                                                     max_rows, requires_grad)
    1963              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: parent
    1964              :       TYPE(skala_gpw_feature_type), INTENT(INOUT)        :: features
    1965              :       INTEGER, INTENT(IN)                                :: subchunk_index, max_rows
    1966              :       LOGICAL, INTENT(IN)                                :: requires_grad
    1967              : 
    1968              :       INTEGER                                            :: atom_begin, atom_end, row_begin, row_end
    1969              : 
    1970            0 :       CPASSERT(parent%uses_atom_chunks)
    1971              :       CALL atom_subchunk_bounds(subchunk_index, max_rows, atom_begin, atom_end, &
    1972            0 :                                 row_begin, row_end)
    1973              :       CALL skala_gpw_feature_build_atom_subchunk_bounds(parent, features, atom_begin, &
    1974              :                                                         atom_end - atom_begin + 1, row_begin, &
    1975            0 :                                                         row_end - row_begin + 1, requires_grad)
    1976              : 
    1977            0 :    END SUBROUTINE skala_gpw_feature_build_atom_subchunk
    1978              : 
    1979              : ! **************************************************************************************************
    1980              : !> \brief Build an atom-contiguous subchunk feature bundle from precomputed bounds.
    1981              : !> \param parent ...
    1982              : !> \param features ...
    1983              : !> \param atom_begin ...
    1984              : !> \param atom_count ...
    1985              : !> \param row_begin ...
    1986              : !> \param row_count ...
    1987              : !> \param requires_grad ...
    1988              : ! **************************************************************************************************
    1989            4 :    SUBROUTINE skala_gpw_feature_build_atom_subchunk_bounds(parent, features, atom_begin, &
    1990              :                                                            atom_count, row_begin, row_count, &
    1991              :                                                            requires_grad)
    1992              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: parent
    1993              :       TYPE(skala_gpw_feature_type), INTENT(INOUT)        :: features
    1994              :       INTEGER, INTENT(IN)                                :: atom_begin, atom_count, row_begin, &
    1995              :                                                             row_count
    1996              :       LOGICAL, INTENT(IN)                                :: requires_grad
    1997              : 
    1998              :       INTEGER                                            :: atom_end, max_grid_size, row_end
    1999              : 
    2000            4 :       CALL skala_gpw_feature_release(features)
    2001            4 :       CPASSERT(parent%uses_atom_chunks)
    2002            4 :       atom_end = atom_begin + atom_count - 1
    2003            4 :       row_end = row_begin + row_count - 1
    2004            4 :       CPASSERT(atom_count > 0)
    2005            4 :       CPASSERT(row_count > 0)
    2006              :       MARK_USED(requires_grad)
    2007            8 :       max_grid_size = MAXVAL(INT(cached_layout%chunk_atomic_grid_sizes(atom_begin:atom_end)))
    2008              : 
    2009            8 :       ALLOCATE (features%atomic_grid_size_bound_shape(0, max_grid_size))
    2010        64004 :       features%atomic_grid_size_bound_shape = 0_int_8
    2011              : 
    2012            4 :       features%chunk_feature_count = row_count
    2013            4 :       features%nflat = parent%nflat
    2014            4 :       features%nflat_local = parent%nflat_local
    2015        64004 :       features%grid_weight_sum = SUM(cached_layout%chunk_grid_weights(row_begin:row_end))
    2016            4 :       features%uses_atom_chunks = .TRUE.
    2017            4 :       features%uses_atom_chunk_routing = parent%uses_atom_chunk_routing
    2018              :       CALL add_subchunk_feature_tensors(parent, features, atom_begin, atom_count, row_begin, &
    2019            4 :                                         row_count)
    2020            4 :       features%active = .TRUE.
    2021              : 
    2022            4 :    END SUBROUTINE skala_gpw_feature_build_atom_subchunk_bounds
    2023              : 
    2024              : ! **************************************************************************************************
    2025              : !> \brief Return atom and row bounds for an atom-contiguous rank-local subchunk.
    2026              : !> \param subchunk_index ...
    2027              : !> \param max_rows ...
    2028              : !> \param atom_begin ...
    2029              : !> \param atom_end ...
    2030              : !> \param row_begin ...
    2031              : !> \param row_end ...
    2032              : ! **************************************************************************************************
    2033            0 :    SUBROUTINE atom_subchunk_bounds(subchunk_index, max_rows, atom_begin, atom_end, &
    2034              :                                    row_begin, row_end)
    2035              :       INTEGER, INTENT(IN)                                :: subchunk_index, max_rows
    2036              :       INTEGER, INTENT(OUT)                               :: atom_begin, atom_end, row_begin, row_end
    2037              : 
    2038              :       INTEGER                                            :: atom_rows, current_subchunk, iatom, &
    2039              :                                                             row_cursor, rows
    2040              : 
    2041            0 :       CPASSERT(subchunk_index > 0)
    2042            0 :       CPASSERT(max_rows > 0)
    2043            0 :       CPASSERT(cached_layout%chunk_natom > 0)
    2044              : 
    2045            0 :       atom_begin = 1
    2046            0 :       atom_end = 0
    2047            0 :       row_begin = 1
    2048            0 :       row_end = 0
    2049            0 :       current_subchunk = 1
    2050            0 :       row_cursor = 1
    2051            0 :       rows = 0
    2052            0 :       DO iatom = 1, cached_layout%chunk_natom
    2053            0 :          atom_rows = INT(cached_layout%chunk_atomic_grid_sizes(iatom))
    2054            0 :          IF (rows > 0 .AND. rows + atom_rows > max_rows) THEN
    2055            0 :             IF (current_subchunk == subchunk_index) THEN
    2056            0 :                atom_end = iatom - 1
    2057            0 :                row_end = row_cursor - 1
    2058            0 :                RETURN
    2059              :             END IF
    2060            0 :             current_subchunk = current_subchunk + 1
    2061            0 :             atom_begin = iatom
    2062            0 :             row_begin = row_cursor
    2063            0 :             rows = 0
    2064              :          END IF
    2065            0 :          rows = rows + atom_rows
    2066            0 :          row_cursor = row_cursor + atom_rows
    2067              :       END DO
    2068              : 
    2069            0 :       IF (current_subchunk == subchunk_index) THEN
    2070            0 :          atom_end = cached_layout%chunk_natom
    2071            0 :          row_end = row_cursor - 1
    2072            0 :          RETURN
    2073              :       END IF
    2074              : 
    2075            0 :       CPABORT("Requested native SKALA atom subchunk does not exist.")
    2076              : 
    2077              :    END SUBROUTINE atom_subchunk_bounds
    2078              : 
    2079              : ! **************************************************************************************************
    2080              : !> \brief Insert a subchunk into a Torch dictionary using static views of the cached chunk tensors.
    2081              : !> \param parent ...
    2082              : !> \param features ...
    2083              : !> \param atom_begin ...
    2084              : !> \param atom_count ...
    2085              : !> \param row_begin ...
    2086              : !> \param row_count ...
    2087              : ! **************************************************************************************************
    2088            4 :    SUBROUTINE add_subchunk_feature_tensors(parent, features, atom_begin, atom_count, row_begin, &
    2089              :                                            row_count)
    2090              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: parent
    2091              :       TYPE(skala_gpw_feature_type), INTENT(INOUT)        :: features
    2092              :       INTEGER, INTENT(IN)                                :: atom_begin, atom_count, row_begin, &
    2093              :                                                             row_count
    2094              : 
    2095            4 :       CPASSERT(cached_layout%chunk_static_tensors_active)
    2096            4 :       CPASSERT(parent%active)
    2097            4 :       CPASSERT(ALLOCATED(features%atomic_grid_size_bound_shape))
    2098              : 
    2099            4 :       features%owns_coordinate_tensor = .FALSE.
    2100            4 :       features%owns_dynamic_tensors = .TRUE.
    2101            4 :       features%owns_inputs = .TRUE.
    2102            4 :       features%owns_static_tensors = .TRUE.
    2103            4 :       features%uses_collapsed_rks_dynamic = parent%uses_collapsed_rks_dynamic
    2104              : 
    2105              :       CALL torch_tensor_narrow(cached_layout%chunk_grid_coords_t, 0, row_begin - 1, &
    2106            4 :                                row_count, features%grid_coords_t)
    2107              :       CALL torch_tensor_narrow(cached_layout%chunk_grid_weights_t, 0, row_begin - 1, &
    2108            4 :                                row_count, features%grid_weights_t)
    2109              :       CALL torch_tensor_narrow(cached_layout%chunk_atomic_grid_weights_t, 0, row_begin - 1, &
    2110            4 :                                row_count, features%atomic_grid_weights_t)
    2111              :       CALL torch_tensor_narrow(cached_layout%chunk_atomic_grid_sizes_t, 0, atom_begin - 1, &
    2112            4 :                                atom_count, features%atomic_grid_sizes_t)
    2113              :       CALL torch_tensor_narrow(cached_layout%chunk_coarse_0_atomic_coords_t, 0, &
    2114            4 :                                atom_begin - 1, atom_count, features%coarse_0_atomic_coords_t)
    2115              :       CALL torch_tensor_from_array(features%atomic_grid_size_bound_shape_t, &
    2116            4 :                                    features%atomic_grid_size_bound_shape)
    2117            4 :       CALL torch_tensor_to_device_leaf(features%atomic_grid_size_bound_shape_t, .FALSE.)
    2118              :       CALL torch_tensor_narrow(parent%density_t, 1, row_begin - 1, row_count, &
    2119            4 :                                features%density_t)
    2120            4 :       CALL torch_tensor_narrow(parent%grad_t, 2, row_begin - 1, row_count, features%grad_t)
    2121            4 :       CALL torch_tensor_narrow(parent%kin_t, 1, row_begin - 1, row_count, features%kin_t)
    2122            4 :       IF (features%uses_collapsed_rks_dynamic) THEN
    2123            4 :          CALL torch_tensor_expand_dim(features%density_t, 0, 2, features%density_input_t)
    2124            4 :          CALL torch_tensor_expand_dim(features%grad_t, 0, 2, features%grad_input_t)
    2125            4 :          CALL torch_tensor_expand_dim(features%kin_t, 0, 2, features%kin_input_t)
    2126              :       END IF
    2127              : 
    2128            4 :       CALL torch_dict_create(features%inputs)
    2129            4 :       CALL torch_dict_insert(features%inputs, "grid_coords", features%grid_coords_t)
    2130            4 :       CALL torch_dict_insert(features%inputs, "grid_weights", features%grid_weights_t)
    2131              :       CALL torch_dict_insert(features%inputs, "atomic_grid_weights", &
    2132            4 :                              features%atomic_grid_weights_t)
    2133              :       CALL torch_dict_insert(features%inputs, "atomic_grid_sizes", &
    2134            4 :                              features%atomic_grid_sizes_t)
    2135              :       CALL torch_dict_insert(features%inputs, "atomic_grid_size_bound_shape", &
    2136            4 :                              features%atomic_grid_size_bound_shape_t)
    2137            4 :       IF (features%uses_collapsed_rks_dynamic) THEN
    2138            4 :          CALL torch_dict_insert(features%inputs, "density", features%density_input_t)
    2139            4 :          CALL torch_dict_insert(features%inputs, "grad", features%grad_input_t)
    2140            4 :          CALL torch_dict_insert(features%inputs, "kin", features%kin_input_t)
    2141              :       ELSE
    2142            0 :          CALL torch_dict_insert(features%inputs, "density", features%density_t)
    2143            0 :          CALL torch_dict_insert(features%inputs, "grad", features%grad_t)
    2144            0 :          CALL torch_dict_insert(features%inputs, "kin", features%kin_t)
    2145              :       END IF
    2146              :       CALL torch_dict_insert(features%inputs, "coarse_0_atomic_coords", &
    2147            4 :                              features%coarse_0_atomic_coords_t)
    2148              : 
    2149            4 :    END SUBROUTINE add_subchunk_feature_tensors
    2150              : 
    2151              : ! **************************************************************************************************
    2152              : !> \brief Insert owned subchunk arrays into a Torch dictionary.
    2153              : !> \param features ...
    2154              : !> \param requires_grad ...
    2155              : ! **************************************************************************************************
    2156            0 :    SUBROUTINE add_owned_feature_tensors(features, requires_grad)
    2157              :       TYPE(skala_gpw_feature_type), INTENT(INOUT)        :: features
    2158              :       LOGICAL, INTENT(IN)                                :: requires_grad
    2159              : 
    2160            0 :       CPASSERT(ALLOCATED(features%chunk_density))
    2161            0 :       CPASSERT(ALLOCATED(features%chunk_grad))
    2162            0 :       CPASSERT(ALLOCATED(features%chunk_kin))
    2163            0 :       CPASSERT(ALLOCATED(features%grid_coords))
    2164            0 :       CPASSERT(ALLOCATED(features%grid_weights))
    2165            0 :       CPASSERT(ALLOCATED(features%atomic_grid_weights))
    2166            0 :       CPASSERT(ALLOCATED(features%atomic_grid_sizes))
    2167            0 :       CPASSERT(ALLOCATED(features%atomic_grid_size_bound_shape))
    2168            0 :       CPASSERT(ALLOCATED(features%coarse_0_atomic_coords))
    2169              : 
    2170            0 :       features%owns_coordinate_tensor = .FALSE.
    2171            0 :       features%owns_dynamic_tensors = .TRUE.
    2172            0 :       features%owns_inputs = .TRUE.
    2173            0 :       features%owns_static_tensors = .TRUE.
    2174              : 
    2175            0 :       CALL torch_tensor_from_array(features%grid_coords_t, features%grid_coords)
    2176            0 :       CALL torch_tensor_to_device_leaf(features%grid_coords_t, .FALSE.)
    2177            0 :       CALL torch_tensor_from_array(features%grid_weights_t, features%grid_weights)
    2178            0 :       CALL torch_tensor_to_device_leaf(features%grid_weights_t, .FALSE.)
    2179            0 :       CALL torch_tensor_from_array(features%atomic_grid_weights_t, features%atomic_grid_weights)
    2180            0 :       CALL torch_tensor_to_device_leaf(features%atomic_grid_weights_t, .FALSE.)
    2181            0 :       CALL torch_tensor_from_array(features%atomic_grid_sizes_t, features%atomic_grid_sizes)
    2182            0 :       CALL torch_tensor_to_device_leaf(features%atomic_grid_sizes_t, .FALSE.)
    2183              :       CALL torch_tensor_from_array(features%coarse_0_atomic_coords_t, &
    2184            0 :                                    features%coarse_0_atomic_coords)
    2185            0 :       CALL torch_tensor_to_device_leaf(features%coarse_0_atomic_coords_t, .FALSE.)
    2186              :       CALL torch_tensor_from_array(features%atomic_grid_size_bound_shape_t, &
    2187            0 :                                    features%atomic_grid_size_bound_shape)
    2188            0 :       CALL torch_tensor_to_device_leaf(features%atomic_grid_size_bound_shape_t, .FALSE.)
    2189            0 :       CALL torch_tensor_from_array(features%density_t, features%chunk_density)
    2190            0 :       CALL torch_tensor_to_device_leaf(features%density_t, requires_grad)
    2191            0 :       CALL torch_tensor_from_array(features%grad_t, features%chunk_grad)
    2192            0 :       CALL torch_tensor_to_device_leaf(features%grad_t, requires_grad)
    2193            0 :       CALL torch_tensor_from_array(features%kin_t, features%chunk_kin)
    2194            0 :       CALL torch_tensor_to_device_leaf(features%kin_t, requires_grad)
    2195              : 
    2196            0 :       CALL torch_dict_create(features%inputs)
    2197            0 :       CALL torch_dict_insert(features%inputs, "grid_coords", features%grid_coords_t)
    2198            0 :       CALL torch_dict_insert(features%inputs, "grid_weights", features%grid_weights_t)
    2199              :       CALL torch_dict_insert(features%inputs, "atomic_grid_weights", &
    2200            0 :                              features%atomic_grid_weights_t)
    2201              :       CALL torch_dict_insert(features%inputs, "atomic_grid_sizes", &
    2202            0 :                              features%atomic_grid_sizes_t)
    2203              :       CALL torch_dict_insert(features%inputs, "atomic_grid_size_bound_shape", &
    2204            0 :                              features%atomic_grid_size_bound_shape_t)
    2205            0 :       CALL torch_dict_insert(features%inputs, "density", features%density_t)
    2206            0 :       CALL torch_dict_insert(features%inputs, "grad", features%grad_t)
    2207            0 :       CALL torch_dict_insert(features%inputs, "kin", features%kin_t)
    2208              :       CALL torch_dict_insert(features%inputs, "coarse_0_atomic_coords", &
    2209            0 :                              features%coarse_0_atomic_coords_t)
    2210              : 
    2211            0 :    END SUBROUTINE add_owned_feature_tensors
    2212              : 
    2213              : ! **************************************************************************************************
    2214              : !> \brief Insert all SKALA feature tensors into the Torch dictionary.
    2215              : !> \param features ...
    2216              : !> \param requires_grad ...
    2217              : !> \param requires_coordinate_grad ...
    2218              : !> \param requires_stress_grad ...
    2219              : !> \param use_atom_chunks ...
    2220              : !> \param requires_weight_grad ...
    2221              : ! **************************************************************************************************
    2222           48 :    SUBROUTINE add_feature_tensors(features, requires_grad, requires_coordinate_grad, &
    2223              :                                   requires_stress_grad, use_atom_chunks, requires_weight_grad)
    2224              :       TYPE(skala_gpw_feature_type), INTENT(INOUT)        :: features
    2225              :       LOGICAL, INTENT(IN)                                :: requires_grad, requires_coordinate_grad, &
    2226              :                                                             requires_stress_grad, use_atom_chunks
    2227              :       LOGICAL, INTENT(IN), OPTIONAL                      :: requires_weight_grad
    2228              : 
    2229              :       LOGICAL                                            :: my_requires_weight_grad
    2230              : 
    2231           48 :       my_requires_weight_grad = .FALSE.
    2232           48 :       IF (PRESENT(requires_weight_grad)) my_requires_weight_grad = requires_weight_grad
    2233              : 
    2234           48 :       features%owns_static_tensors = .FALSE.
    2235           48 :       features%owns_coordinate_tensor = .FALSE.
    2236           48 :       features%owns_grid_coordinate_tensor = .FALSE.
    2237           48 :       features%owns_weight_tensors = .FALSE.
    2238           48 :       features%owns_dynamic_tensors = .FALSE.
    2239           48 :       features%owns_inputs = .TRUE.
    2240           48 :       IF (use_atom_chunks) THEN
    2241           40 :          CPASSERT(.NOT. requires_coordinate_grad)
    2242           40 :          CPASSERT(.NOT. requires_stress_grad)
    2243           40 :          CPASSERT(.NOT. my_requires_weight_grad)
    2244           40 :          CPASSERT(cached_layout%chunk_static_tensors_active)
    2245           40 :          features%grid_coords_t = cached_layout%chunk_grid_coords_t
    2246           40 :          features%grid_weights_t = cached_layout%chunk_grid_weights_t
    2247           40 :          features%atomic_grid_weights_t = cached_layout%chunk_atomic_grid_weights_t
    2248           40 :          features%atomic_grid_sizes_t = cached_layout%chunk_atomic_grid_sizes_t
    2249              :          features%atomic_grid_size_bound_shape_t = &
    2250           40 :             cached_layout%chunk_atomic_grid_size_bound_shape_t
    2251           40 :          features%local_feature_indices_t = cached_layout%chunk_feature_indices_t
    2252              : 
    2253           40 :          IF (cached_layout%chunk_inputs_active .AND. &
    2254              :              (cached_layout%chunk_inputs_use_collapsed_rks .NEQV. &
    2255              :               features%uses_collapsed_rks_dynamic)) THEN
    2256            0 :             CALL torch_dict_release(cached_layout%chunk_inputs)
    2257            0 :             cached_layout%chunk_inputs_active = .FALSE.
    2258              :          END IF
    2259           40 :          IF (.NOT. features%uses_collapsed_rks_dynamic .AND. &
    2260              :              cached_layout%chunk_dynamic_input_views_active) THEN
    2261            0 :             CALL torch_tensor_release(cached_layout%chunk_density_input_t)
    2262            0 :             CALL torch_tensor_release(cached_layout%chunk_grad_input_t)
    2263            0 :             CALL torch_tensor_release(cached_layout%chunk_kin_input_t)
    2264            0 :             cached_layout%chunk_dynamic_input_views_active = .FALSE.
    2265              :          END IF
    2266              : 
    2267              :          CALL torch_tensor_reset_from_array(cached_layout%chunk_density_t, &
    2268           40 :                                             features%chunk_density, requires_grad=requires_grad)
    2269           40 :          features%density_t = cached_layout%chunk_density_t
    2270              :          CALL torch_tensor_reset_from_array(cached_layout%chunk_grad_t, features%chunk_grad, &
    2271           40 :                                             requires_grad=requires_grad)
    2272           40 :          features%grad_t = cached_layout%chunk_grad_t
    2273              :          CALL torch_tensor_reset_from_array(cached_layout%chunk_kin_t, features%chunk_kin, &
    2274           40 :                                             requires_grad=requires_grad)
    2275           40 :          features%kin_t = cached_layout%chunk_kin_t
    2276           40 :          cached_layout%chunk_dynamic_tensors_active = .TRUE.
    2277              : 
    2278           40 :          IF (features%uses_collapsed_rks_dynamic .AND. &
    2279              :              .NOT. cached_layout%chunk_dynamic_input_views_active) THEN
    2280              :             CALL torch_tensor_expand_dim(cached_layout%chunk_density_t, 0, 2, &
    2281           30 :                                          cached_layout%chunk_density_input_t)
    2282              :             CALL torch_tensor_expand_dim(cached_layout%chunk_grad_t, 0, 2, &
    2283           30 :                                          cached_layout%chunk_grad_input_t)
    2284              :             CALL torch_tensor_expand_dim(cached_layout%chunk_kin_t, 0, 2, &
    2285           30 :                                          cached_layout%chunk_kin_input_t)
    2286           30 :             cached_layout%chunk_dynamic_input_views_active = .TRUE.
    2287              :          END IF
    2288           40 :          IF (features%uses_collapsed_rks_dynamic) THEN
    2289           34 :             features%density_input_t = cached_layout%chunk_density_input_t
    2290           34 :             features%grad_input_t = cached_layout%chunk_grad_input_t
    2291           34 :             features%kin_input_t = cached_layout%chunk_kin_input_t
    2292              :          END IF
    2293              : 
    2294           40 :          IF (.NOT. cached_layout%chunk_inputs_active) THEN
    2295           36 :             CALL torch_dict_clone(cached_layout%chunk_static_inputs, cached_layout%chunk_inputs)
    2296           36 :             IF (features%uses_collapsed_rks_dynamic) THEN
    2297              :                CALL torch_dict_insert(cached_layout%chunk_inputs, "density", &
    2298           30 :                                       features%density_input_t)
    2299              :                CALL torch_dict_insert(cached_layout%chunk_inputs, "grad", &
    2300           30 :                                       features%grad_input_t)
    2301              :                CALL torch_dict_insert(cached_layout%chunk_inputs, "kin", &
    2302           30 :                                       features%kin_input_t)
    2303              :             ELSE
    2304              :                CALL torch_dict_insert(cached_layout%chunk_inputs, "density", &
    2305            6 :                                       cached_layout%chunk_density_t)
    2306              :                CALL torch_dict_insert(cached_layout%chunk_inputs, "grad", &
    2307            6 :                                       cached_layout%chunk_grad_t)
    2308              :                CALL torch_dict_insert(cached_layout%chunk_inputs, "kin", &
    2309            6 :                                       cached_layout%chunk_kin_t)
    2310              :             END IF
    2311              :             CALL torch_dict_insert(cached_layout%chunk_inputs, "coarse_0_atomic_coords", &
    2312           36 :                                    cached_layout%chunk_coarse_0_atomic_coords_t)
    2313           36 :             cached_layout%chunk_inputs_use_collapsed_rks = features%uses_collapsed_rks_dynamic
    2314           36 :             cached_layout%chunk_inputs_active = .TRUE.
    2315              :          END IF
    2316           40 :          features%inputs = cached_layout%chunk_inputs
    2317           40 :          features%owns_inputs = .FALSE.
    2318           40 :          features%coarse_0_atomic_coords_t = cached_layout%chunk_coarse_0_atomic_coords_t
    2319              :       ELSE
    2320            8 :          CPASSERT(cached_layout%static_tensors_active)
    2321            8 :          IF (.NOT. requires_stress_grad .AND. .NOT. my_requires_weight_grad) THEN
    2322            0 :             features%grid_coords_t = cached_layout%grid_coords_t
    2323            0 :             features%grid_weights_t = cached_layout%grid_weights_t
    2324            0 :             features%atomic_grid_weights_t = cached_layout%atomic_grid_weights_t
    2325              :          END IF
    2326            8 :          features%atomic_grid_sizes_t = cached_layout%atomic_grid_sizes_t
    2327            8 :          features%atomic_grid_size_bound_shape_t = cached_layout%atomic_grid_size_bound_shape_t
    2328            8 :          features%local_feature_indices_t = cached_layout%local_feature_indices_t
    2329              : 
    2330              :          CALL torch_tensor_reset_from_array(cached_layout%density_t, features%density, &
    2331            8 :                                             requires_grad=requires_grad)
    2332            8 :          features%density_t = cached_layout%density_t
    2333              :          CALL torch_tensor_reset_from_array(cached_layout%grad_t, features%grad, &
    2334            8 :                                             requires_grad=requires_grad)
    2335            8 :          features%grad_t = cached_layout%grad_t
    2336              :          CALL torch_tensor_reset_from_array(cached_layout%kin_t, features%kin, &
    2337            8 :                                             requires_grad=requires_grad)
    2338            8 :          features%kin_t = cached_layout%kin_t
    2339            8 :          cached_layout%dynamic_tensors_active = .TRUE.
    2340              : 
    2341            8 :          IF (requires_coordinate_grad .OR. requires_stress_grad .OR. my_requires_weight_grad) THEN
    2342            8 :             IF (requires_stress_grad .OR. my_requires_weight_grad) THEN
    2343            8 :                CALL torch_dict_create(features%inputs)
    2344            8 :                IF (requires_stress_grad) THEN
    2345            6 :                   CALL torch_tensor_from_array(features%grid_coords_t, features%grid_coords)
    2346            6 :                   CALL torch_tensor_to_device_leaf(features%grid_coords_t, .TRUE.)
    2347            6 :                   CALL torch_dict_insert(features%inputs, "grid_coords", features%grid_coords_t)
    2348            6 :                   features%owns_grid_coordinate_tensor = .TRUE.
    2349              :                ELSE
    2350            2 :                   features%grid_coords_t = cached_layout%grid_coords_t
    2351            2 :                   CALL torch_dict_insert(features%inputs, "grid_coords", features%grid_coords_t)
    2352              :                END IF
    2353            8 :                CALL torch_tensor_from_array(features%grid_weights_t, features%grid_weights)
    2354            8 :                CALL torch_tensor_to_device_leaf(features%grid_weights_t, .TRUE.)
    2355              :                CALL torch_tensor_from_array(features%atomic_grid_weights_t, &
    2356            8 :                                             features%atomic_grid_weights)
    2357            8 :                CALL torch_tensor_to_device_leaf(features%atomic_grid_weights_t, .TRUE.)
    2358            8 :                CALL torch_dict_insert(features%inputs, "grid_weights", features%grid_weights_t)
    2359              :                CALL torch_dict_insert(features%inputs, "atomic_grid_weights", &
    2360            8 :                                       features%atomic_grid_weights_t)
    2361              :                CALL torch_dict_insert(features%inputs, "atomic_grid_sizes", &
    2362            8 :                                       features%atomic_grid_sizes_t)
    2363              :                CALL torch_dict_insert(features%inputs, "atomic_grid_size_bound_shape", &
    2364            8 :                                       features%atomic_grid_size_bound_shape_t)
    2365            8 :                features%owns_weight_tensors = .TRUE.
    2366              :             ELSE
    2367            0 :                CALL torch_dict_clone(cached_layout%static_inputs, features%inputs)
    2368              :             END IF
    2369            8 :             CALL torch_dict_insert(features%inputs, "density", features%density_t)
    2370            8 :             CALL torch_dict_insert(features%inputs, "grad", features%grad_t)
    2371            8 :             CALL torch_dict_insert(features%inputs, "kin", features%kin_t)
    2372              :          ELSE
    2373            0 :             IF (.NOT. cached_layout%inputs_active) THEN
    2374            0 :                CALL torch_dict_clone(cached_layout%static_inputs, cached_layout%inputs)
    2375            0 :                CALL torch_dict_insert(cached_layout%inputs, "density", cached_layout%density_t)
    2376            0 :                CALL torch_dict_insert(cached_layout%inputs, "grad", cached_layout%grad_t)
    2377            0 :                CALL torch_dict_insert(cached_layout%inputs, "kin", cached_layout%kin_t)
    2378              :                CALL torch_dict_insert(cached_layout%inputs, "coarse_0_atomic_coords", &
    2379            0 :                                       cached_layout%coarse_0_atomic_coords_t)
    2380            0 :                cached_layout%inputs_active = .TRUE.
    2381              :             END IF
    2382            0 :             features%inputs = cached_layout%inputs
    2383            0 :             features%owns_inputs = .FALSE.
    2384            0 :             features%coarse_0_atomic_coords_t = cached_layout%coarse_0_atomic_coords_t
    2385              :          END IF
    2386              :       END IF
    2387              : 
    2388           48 :       IF (requires_coordinate_grad .OR. requires_stress_grad) THEN
    2389            8 :          CPASSERT(.NOT. use_atom_chunks)
    2390              :          CALL torch_tensor_from_array(features%coarse_0_atomic_coords_t, &
    2391            8 :                                       features%coarse_0_atomic_coords)
    2392            8 :          CALL torch_tensor_to_device_leaf(features%coarse_0_atomic_coords_t, .TRUE.)
    2393              :          CALL torch_dict_insert(features%inputs, "coarse_0_atomic_coords", &
    2394            8 :                                 features%coarse_0_atomic_coords_t)
    2395            8 :          features%owns_coordinate_tensor = .TRUE.
    2396              :       END IF
    2397              : 
    2398           48 :    END SUBROUTINE add_feature_tensors
    2399              : 
    2400              : ! **************************************************************************************************
    2401              : !> \brief Return the Cartesian coordinate of a regular GPW grid point.
    2402              : !> \param pw_grid ...
    2403              : !> \param index ...
    2404              : !> \return ...
    2405              : ! **************************************************************************************************
    2406       914827 :    FUNCTION grid_coordinate(pw_grid, index) RESULT(coord)
    2407              :       TYPE(pw_grid_type), POINTER                        :: pw_grid
    2408              :       INTEGER, DIMENSION(3), INTENT(IN)                  :: index
    2409              :       REAL(KIND=dp), DIMENSION(3)                        :: coord
    2410              : 
    2411              :       INTEGER, DIMENSION(3)                              :: relative_index
    2412              : 
    2413      3659308 :       relative_index = index - pw_grid%bounds(1, :)
    2414              :       coord = REAL(relative_index(1), KIND=dp)*pw_grid%dh(:, 1) + &
    2415              :               REAL(relative_index(2), KIND=dp)*pw_grid%dh(:, 2) + &
    2416      3659308 :               REAL(relative_index(3), KIND=dp)*pw_grid%dh(:, 3)
    2417              : 
    2418       914827 :    END FUNCTION grid_coordinate
    2419              : 
    2420              : ! **************************************************************************************************
    2421              : !> \brief Build Becke-like smooth atom weights for one native-grid point.
    2422              : !> \param grid_point ...
    2423              : !> \param atom_coords ...
    2424              : !> \param cell ...
    2425              : !> \param weights ...
    2426              : !> \param partition_atom_coords ...
    2427              : !> \param distances ...
    2428              : !> \param pair_distances ...
    2429              : ! **************************************************************************************************
    2430        65421 :    SUBROUTINE smooth_atom_partition(grid_point, atom_coords, cell, weights, partition_atom_coords, &
    2431        65421 :                                     distances, pair_distances)
    2432              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: grid_point
    2433              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: atom_coords
    2434              :       TYPE(cell_type), POINTER                           :: cell
    2435              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: weights
    2436              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: partition_atom_coords
    2437              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: distances
    2438              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN), &
    2439              :          OPTIONAL                                        :: pair_distances
    2440              : 
    2441              :       INTEGER                                            :: iatom, jatom, natom
    2442              :       REAL(KIND=dp)                                      :: mu, rab, rsum, switch, total
    2443              :       REAL(KIND=dp), DIMENSION(3)                        :: rij
    2444              : 
    2445        65421 :       natom = SIZE(atom_coords, 2)
    2446        65421 :       CPASSERT(SIZE(weights) == natom)
    2447        65421 :       CPASSERT(SIZE(partition_atom_coords, 1) == 3)
    2448        65421 :       CPASSERT(SIZE(partition_atom_coords, 2) == natom)
    2449        65421 :       CPASSERT(SIZE(distances) == natom)
    2450              : 
    2451        65421 :       IF (PRESENT(pair_distances)) THEN
    2452        30375 :          DO iatom = 1, natom
    2453        81000 :             rij = grid_point - atom_coords(:, iatom)
    2454        91125 :             distances(iatom) = SQRT(SUM(rij**2))
    2455              :          END DO
    2456              :       ELSE
    2457       165888 :          DO iatom = 1, natom
    2458              :             partition_atom_coords(:, iatom) = &
    2459       110592 :                nearest_atom_image_coordinate(atom_coords(:, iatom), grid_point, cell)
    2460       442368 :             rij = grid_point - partition_atom_coords(:, iatom)
    2461       497664 :             distances(iatom) = SQRT(SUM(rij**2))
    2462              :          END DO
    2463              :       END IF
    2464              : 
    2465       196263 :       weights = 1.0_dp
    2466       130842 :       DO iatom = 1, natom - 1
    2467       196263 :          DO jatom = iatom + 1, natom
    2468        65421 :             IF (PRESENT(pair_distances)) THEN
    2469        10125 :                rab = pair_distances(iatom, jatom)
    2470              :             ELSE
    2471       221184 :                rij = partition_atom_coords(:, iatom) - partition_atom_coords(:, jatom)
    2472       221184 :                rab = SQRT(SUM(rij**2))
    2473              :             END IF
    2474        65421 :             IF (rab <= layout_tol) CYCLE
    2475        65421 :             mu = (distances(iatom) - distances(jatom))/rab
    2476        65421 :             mu = MAX(-1.0_dp, MIN(1.0_dp, mu))
    2477        65421 :             switch = 0.5_dp*(1.0_dp - becke_shape(mu))
    2478        65421 :             weights(iatom) = weights(iatom)*switch
    2479       130842 :             weights(jatom) = weights(jatom)*(1.0_dp - switch)
    2480              :          END DO
    2481              :       END DO
    2482              : 
    2483       196263 :       total = SUM(weights)
    2484        65421 :       IF (total > 0.0_dp) THEN
    2485       196263 :          weights = weights/total
    2486              :       ELSE
    2487              :          rsum = HUGE(1.0_dp)
    2488              :          jatom = 1
    2489            0 :          DO iatom = 1, natom
    2490            0 :             IF (distances(iatom) < rsum) THEN
    2491            0 :                rsum = distances(iatom)
    2492            0 :                jatom = iatom
    2493              :             END IF
    2494              :          END DO
    2495            0 :          weights = 0.0_dp
    2496            0 :          weights(jatom) = 1.0_dp
    2497              :       END IF
    2498              : 
    2499        65421 :    END SUBROUTINE smooth_atom_partition
    2500              : 
    2501              : ! **************************************************************************************************
    2502              : !> \brief Build screened logarithmic Becke weights for a set of periodic atom images.
    2503              : !> \param image_coords ...
    2504              : !> \param distances ...
    2505              : !> \param log_weights ...
    2506              : ! **************************************************************************************************
    2507            0 :    SUBROUTINE periodic_image_log_weights(image_coords, distances, log_weights)
    2508              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: image_coords
    2509              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: distances
    2510              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: log_weights
    2511              : 
    2512              :       INTEGER                                            :: iimage, image_count, jimage, &
    2513              :                                                             nearest_image
    2514              :       REAL(KIND=dp)                                      :: factor, mu, rab, reference_log_weight, &
    2515              :                                                             screen_log_weight
    2516              :       REAL(KIND=dp), DIMENSION(3)                        :: pair
    2517              : 
    2518            0 :       image_count = SIZE(distances)
    2519            0 :       CPASSERT(SIZE(image_coords, 1) == 3)
    2520            0 :       CPASSERT(SIZE(image_coords, 2) == image_count)
    2521            0 :       CPASSERT(SIZE(log_weights) == image_count)
    2522              : 
    2523            0 :       nearest_image = MINLOC(distances, DIM=1)
    2524            0 :       log_weights = -HUGE(1.0_dp)
    2525              :       reference_log_weight = 0.0_dp
    2526            0 :       DO jimage = 1, image_count
    2527            0 :          IF (jimage == nearest_image) CYCLE
    2528            0 :          pair = image_coords(:, nearest_image) - image_coords(:, jimage)
    2529            0 :          rab = SQRT(SUM(pair**2))
    2530            0 :          IF (rab <= layout_tol) CYCLE
    2531              :          mu = MAX(-1.0_dp, MIN(1.0_dp, &
    2532            0 :                                (distances(nearest_image) - distances(jimage))/rab))
    2533            0 :          factor = 0.5_dp*(1.0_dp - becke_shape(mu))
    2534            0 :          CPASSERT(factor > TINY(1.0_dp))
    2535            0 :          reference_log_weight = reference_log_weight + LOG(factor)
    2536              :       END DO
    2537            0 :       log_weights(nearest_image) = reference_log_weight
    2538              : 
    2539              :       ! Every omitted factor is at most one. A partial log product is therefore an upper bound
    2540              :       ! on the final unnormalized weight. The normalization contains at least the nearest-image
    2541              :       ! reference weight, so screening below this limit changes the complete partition by no more
    2542              :       ! than one floating-point epsilon in total.
    2543              :       screen_log_weight = reference_log_weight + &
    2544            0 :                           LOG(EPSILON(1.0_dp)/REAL(image_count, KIND=dp))
    2545            0 :       DO iimage = 1, image_count
    2546            0 :          IF (iimage == nearest_image) CYCLE
    2547            0 :          pair = image_coords(:, iimage) - image_coords(:, nearest_image)
    2548            0 :          rab = SQRT(SUM(pair**2))
    2549            0 :          IF (rab <= layout_tol) CYCLE
    2550              :          mu = MAX(-1.0_dp, MIN(1.0_dp, &
    2551            0 :                                (distances(iimage) - distances(nearest_image))/rab))
    2552            0 :          factor = 0.5_dp*(1.0_dp - becke_shape(mu))
    2553            0 :          IF (factor <= TINY(1.0_dp)) CYCLE
    2554            0 :          log_weights(iimage) = LOG(factor)
    2555            0 :          IF (log_weights(iimage) <= screen_log_weight) THEN
    2556            0 :             log_weights(iimage) = -HUGE(1.0_dp)
    2557            0 :             CYCLE
    2558              :          END IF
    2559              : 
    2560            0 :          DO jimage = 1, image_count
    2561            0 :             IF (jimage == iimage .OR. jimage == nearest_image) CYCLE
    2562            0 :             pair = image_coords(:, iimage) - image_coords(:, jimage)
    2563            0 :             rab = SQRT(SUM(pair**2))
    2564            0 :             IF (rab <= layout_tol) CYCLE
    2565              :             mu = MAX(-1.0_dp, MIN(1.0_dp, &
    2566            0 :                                   (distances(iimage) - distances(jimage))/rab))
    2567            0 :             factor = 0.5_dp*(1.0_dp - becke_shape(mu))
    2568            0 :             IF (factor <= TINY(1.0_dp)) THEN
    2569            0 :                log_weights(iimage) = -HUGE(1.0_dp)
    2570            0 :                EXIT
    2571              :             END IF
    2572            0 :             log_weights(iimage) = log_weights(iimage) + LOG(factor)
    2573            0 :             IF (log_weights(iimage) <= screen_log_weight) THEN
    2574            0 :                log_weights(iimage) = -HUGE(1.0_dp)
    2575            0 :                EXIT
    2576              :             END IF
    2577              :          END DO
    2578              :       END DO
    2579              : 
    2580            0 :    END SUBROUTINE periodic_image_log_weights
    2581              : 
    2582              : ! **************************************************************************************************
    2583              : !> \brief Build screened normalized Becke weights, using direct products whenever they are safe.
    2584              : !> \param image_coords ...
    2585              : !> \param distances ...
    2586              : !> \param normalized_weights ...
    2587              : ! **************************************************************************************************
    2588      1156600 :    SUBROUTINE periodic_image_normalized_weights(image_coords, distances, normalized_weights)
    2589              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: image_coords
    2590              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: distances
    2591              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: normalized_weights
    2592              : 
    2593              :       INTEGER                                            :: iimage, image_count, jimage, &
    2594              :                                                             nearest_image
    2595              :       LOGICAL                                            :: use_log_fallback
    2596              :       REAL(KIND=dp)                                      :: factor, max_log_weight, mu, &
    2597              :                                                             normalization, product, rab, &
    2598              :                                                             reference_weight, screen_factor, &
    2599              :                                                             screen_weight
    2600              :       REAL(KIND=dp), DIMENSION(3)                        :: pair
    2601      1156600 :       REAL(KIND=dp), DIMENSION(SIZE(distances))          :: log_weights, raw_weights
    2602              : 
    2603      1156600 :       image_count = SIZE(distances)
    2604      1156600 :       CPASSERT(SIZE(image_coords, 1) == 3)
    2605      1156600 :       CPASSERT(SIZE(image_coords, 2) == image_count)
    2606      1156600 :       CPASSERT(SIZE(normalized_weights) == image_count)
    2607      1156600 :       IF (image_count == 1) THEN
    2608            0 :          normalized_weights(1) = 1.0_dp
    2609            0 :          RETURN
    2610              :       END IF
    2611              : 
    2612     48201400 :       nearest_image = MINLOC(distances, DIM=1)
    2613      1156600 :       reference_weight = 1.0_dp
    2614      1156600 :       use_log_fallback = .FALSE.
    2615     48201400 :       DO jimage = 1, image_count
    2616     47044800 :          IF (jimage == nearest_image) CYCLE
    2617    183552800 :          pair = image_coords(:, nearest_image) - image_coords(:, jimage)
    2618    183552800 :          rab = SQRT(SUM(pair**2))
    2619     45888200 :          IF (rab <= layout_tol) CYCLE
    2620              :          mu = MAX(-1.0_dp, MIN(1.0_dp, &
    2621     45888200 :                                (distances(nearest_image) - distances(jimage))/rab))
    2622     45888200 :          factor = 0.5_dp*(1.0_dp - becke_shape(mu))
    2623     45888200 :          CPASSERT(factor > TINY(1.0_dp))
    2624     45888200 :          IF (reference_weight <= TINY(1.0_dp)/factor) THEN
    2625              :             use_log_fallback = .TRUE.
    2626              :             EXIT
    2627              :          END IF
    2628     48201400 :          reference_weight = reference_weight*factor
    2629              :       END DO
    2630              : 
    2631      1156600 :       screen_factor = EPSILON(1.0_dp)/REAL(image_count, KIND=dp)
    2632      1156600 :       IF (.NOT. use_log_fallback) THEN
    2633      1156600 :          use_log_fallback = reference_weight <= TINY(1.0_dp)/screen_factor
    2634              :       END IF
    2635      1156600 :       IF (use_log_fallback) THEN
    2636            0 :          CALL periodic_image_log_weights(image_coords, distances, log_weights)
    2637            0 :          max_log_weight = MAXVAL(log_weights)
    2638            0 :          CPASSERT(max_log_weight > -0.5_dp*HUGE(1.0_dp))
    2639            0 :          normalized_weights = 0.0_dp
    2640            0 :          WHERE (log_weights > -0.5_dp*HUGE(1.0_dp))
    2641              :             normalized_weights = EXP(log_weights - max_log_weight)
    2642              :          END WHERE
    2643            0 :          normalization = SUM(normalized_weights)
    2644            0 :          CPASSERT(normalization > 0.0_dp)
    2645            0 :          normalized_weights = normalized_weights/normalization
    2646              :          RETURN
    2647              :       END IF
    2648              : 
    2649      1156600 :       screen_weight = reference_weight*screen_factor
    2650     48201400 :       raw_weights = 0.0_dp
    2651      1156600 :       raw_weights(nearest_image) = reference_weight
    2652     48201400 :       DO iimage = 1, image_count
    2653     47044800 :          IF (iimage == nearest_image) CYCLE
    2654    183552800 :          pair = image_coords(:, iimage) - image_coords(:, nearest_image)
    2655    183552800 :          rab = SQRT(SUM(pair**2))
    2656     45888200 :          IF (rab <= layout_tol) CYCLE
    2657              :          mu = MAX(-1.0_dp, MIN(1.0_dp, &
    2658     45888200 :                                (distances(iimage) - distances(nearest_image))/rab))
    2659     45888200 :          factor = 0.5_dp*(1.0_dp - becke_shape(mu))
    2660     45888200 :          IF (factor <= TINY(1.0_dp) .OR. factor <= screen_weight) CYCLE
    2661              :          product = factor
    2662              : 
    2663    750077382 :          DO jimage = 1, image_count
    2664    742379673 :             IF (jimage == iimage .OR. jimage == nearest_image) CYCLE
    2665   2833429716 :             pair = image_coords(:, iimage) - image_coords(:, jimage)
    2666   2833429716 :             rab = SQRT(SUM(pair**2))
    2667    708357429 :             IF (rab <= layout_tol) CYCLE
    2668              :             mu = MAX(-1.0_dp, MIN(1.0_dp, &
    2669    708357429 :                                   (distances(iimage) - distances(jimage))/rab))
    2670    708357429 :             factor = 0.5_dp*(1.0_dp - becke_shape(mu))
    2671    708357429 :             IF (factor <= TINY(1.0_dp) .OR. product <= screen_weight/factor) THEN
    2672              :                product = 0.0_dp
    2673              :                EXIT
    2674              :             END IF
    2675    722305026 :             product = product*factor
    2676              :          END DO
    2677     48201400 :          raw_weights(iimage) = product
    2678              :       END DO
    2679              : 
    2680     48201400 :       normalization = SUM(raw_weights)
    2681      1156600 :       CPASSERT(normalization > 0.0_dp)
    2682     48201400 :       normalized_weights = raw_weights/normalization
    2683              : 
    2684              :    END SUBROUTINE periodic_image_normalized_weights
    2685              : 
    2686              : ! **************************************************************************************************
    2687              : !> \brief Build the image coordinates shared by all points of one target-atom block.
    2688              : !> \param atom_coords ...
    2689              : !> \param cell ...
    2690              : !> \param target_atom ...
    2691              : !> \param image_periodicity optional image directions used by the auxiliary-grid partition
    2692              : !> \param image_coords ...
    2693              : !> \param target_image ...
    2694              : ! **************************************************************************************************
    2695          530 :    SUBROUTINE build_periodic_atom_image_layout(atom_coords, cell, target_atom, image_periodicity, &
    2696              :                                                image_coords, target_image)
    2697              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: atom_coords
    2698              :       TYPE(cell_type), POINTER                           :: cell
    2699              :       INTEGER, INTENT(IN)                                :: target_atom
    2700              :       INTEGER, DIMENSION(3), INTENT(IN), OPTIONAL        :: image_periodicity
    2701              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2702              :          INTENT(OUT)                                     :: image_coords
    2703              :       INTEGER, INTENT(OUT)                               :: target_image
    2704              : 
    2705              :       INTEGER                                            :: base_shift(3), iatom, idir, image_count, &
    2706              :                                                             image_index, ix, ix_max, ix_min, iy, &
    2707              :                                                             iy_max, iy_min, iz, iz_max, iz_min, &
    2708              :                                                             natom
    2709              :       INTEGER, DIMENSION(3)                              :: partition_periodicity
    2710              :       REAL(KIND=dp), DIMENSION(3)                        :: fractional
    2711              : 
    2712          530 :       natom = SIZE(atom_coords, 2)
    2713          530 :       CPASSERT(ASSOCIATED(cell))
    2714          530 :       CPASSERT(target_atom >= 1 .AND. target_atom <= natom)
    2715          530 :       CPASSERT(SIZE(atom_coords, 1) == 3)
    2716         2120 :       partition_periodicity = cell%perd
    2717          530 :       IF (PRESENT(image_periodicity)) partition_periodicity = image_periodicity
    2718         2120 :       CPASSERT(ALL(partition_periodicity == 0 .OR. partition_periodicity == 1))
    2719              : 
    2720          530 :       image_count = natom
    2721         2120 :       DO idir = 1, 3
    2722         2120 :          IF (partition_periodicity(idir) == 1) THEN
    2723         1590 :             image_count = image_count*(2*periodic_partition_image_shell + 1)
    2724              :          END IF
    2725              :       END DO
    2726         1590 :       ALLOCATE (image_coords(3, image_count))
    2727              : 
    2728          530 :       image_index = 0
    2729          530 :       target_image = 0
    2730         1328 :       DO iatom = 1, natom
    2731              :          fractional = MATMUL(cell%h_inv, &
    2732        12768 :                              atom_coords(:, target_atom) - atom_coords(:, iatom))
    2733         3192 :          DO idir = 1, 3
    2734         3192 :             base_shift(idir) = partition_periodicity(idir)*NINT(fractional(idir))
    2735              :          END DO
    2736          798 :          ix_min = base_shift(1) - periodic_partition_image_shell*partition_periodicity(1)
    2737          798 :          ix_max = base_shift(1) + periodic_partition_image_shell*partition_periodicity(1)
    2738          798 :          iy_min = base_shift(2) - periodic_partition_image_shell*partition_periodicity(2)
    2739          798 :          iy_max = base_shift(2) + periodic_partition_image_shell*partition_periodicity(2)
    2740          798 :          iz_min = base_shift(3) - periodic_partition_image_shell*partition_periodicity(3)
    2741          798 :          iz_max = base_shift(3) + periodic_partition_image_shell*partition_periodicity(3)
    2742         3722 :          DO iz = iz_min, iz_max
    2743        10374 :             DO iy = iy_min, iy_max
    2744        31122 :                DO ix = ix_min, ix_max
    2745        21546 :                   image_index = image_index + 1
    2746              :                   image_coords(:, image_index) = atom_coords(:, iatom) + &
    2747       474012 :                                                  MATMUL(cell%hmat, REAL([ix, iy, iz], KIND=dp))
    2748        28728 :                   IF (iatom == target_atom .AND. ALL([ix, iy, iz] == 0)) THEN
    2749          530 :                      target_image = image_index
    2750              :                   END IF
    2751              :                END DO
    2752              :             END DO
    2753              :          END DO
    2754              :       END DO
    2755          530 :       CPASSERT(image_index == image_count)
    2756          530 :       CPASSERT(target_image > 0)
    2757              : 
    2758          530 :    END SUBROUTINE build_periodic_atom_image_layout
    2759              : 
    2760              : ! **************************************************************************************************
    2761              : !> \brief Return an image-complete periodic atom weight for a prebuilt image layout.
    2762              : !> \param grid_point ...
    2763              : !> \param image_coords ...
    2764              : !> \param target_image ...
    2765              : !> \param weight ...
    2766              : ! **************************************************************************************************
    2767       916520 :    SUBROUTINE periodic_atom_image_partition_from_layout(grid_point, image_coords, target_image, &
    2768              :                                                         weight)
    2769              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: grid_point
    2770              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: image_coords
    2771              :       INTEGER, INTENT(IN)                                :: target_image
    2772              :       REAL(KIND=dp), INTENT(OUT)                         :: weight
    2773              : 
    2774              :       INTEGER                                            :: iimage, image_count
    2775      1833040 :       REAL(KIND=dp), DIMENSION(SIZE(image_coords, 2))    :: distances, normalized_weights
    2776              : 
    2777       916520 :       image_count = SIZE(image_coords, 2)
    2778       916520 :       CPASSERT(SIZE(image_coords, 1) == 3)
    2779       916520 :       CPASSERT(target_image >= 1 .AND. target_image <= image_count)
    2780       916520 :       IF (image_count == 1) THEN
    2781            0 :          weight = 1.0_dp
    2782              :          RETURN
    2783              :       END IF
    2784              : 
    2785       916520 :       weight = 0.0_dp
    2786     38238080 :       DO iimage = 1, image_count
    2787    150202760 :          distances(iimage) = SQRT(SUM((grid_point - image_coords(:, iimage))**2))
    2788              :       END DO
    2789       916520 :       CALL periodic_image_normalized_weights(image_coords, distances, normalized_weights)
    2790       916520 :       weight = normalized_weights(target_image)
    2791              : 
    2792              :    END SUBROUTINE periodic_atom_image_partition_from_layout
    2793              : 
    2794              : ! **************************************************************************************************
    2795              : !> \brief Return an image-complete periodic atom weight without derivatives.
    2796              : !> \param grid_point ...
    2797              : !> \param atom_coords ...
    2798              : !> \param cell ...
    2799              : !> \param target_atom ...
    2800              : !> \param weight ...
    2801              : !> \param image_periodicity optional image directions used by the auxiliary-grid partition
    2802              : ! **************************************************************************************************
    2803            0 :    SUBROUTINE periodic_atom_image_partition_value(grid_point, atom_coords, cell, target_atom, &
    2804              :                                                   weight, image_periodicity)
    2805              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: grid_point
    2806              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: atom_coords
    2807              :       TYPE(cell_type), POINTER                           :: cell
    2808              :       INTEGER, INTENT(IN)                                :: target_atom
    2809              :       REAL(KIND=dp), INTENT(OUT)                         :: weight
    2810              :       INTEGER, DIMENSION(3), INTENT(IN), OPTIONAL        :: image_periodicity
    2811              : 
    2812              :       INTEGER                                            :: target_image
    2813            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: image_coords
    2814              : 
    2815              :       CALL build_periodic_atom_image_layout(atom_coords, cell, target_atom, image_periodicity, &
    2816            0 :                                             image_coords, target_image)
    2817            0 :       CALL periodic_atom_image_partition_from_layout(grid_point, image_coords, target_image, weight)
    2818            0 :       DEALLOCATE (image_coords)
    2819              : 
    2820            0 :    END SUBROUTINE periodic_atom_image_partition_value
    2821              : 
    2822              : ! **************************************************************************************************
    2823              : !> \brief Return the smooth weight of one reference-cell atom in an image-complete periodic
    2824              : !>        atom partition. Each atom's fixed image shell is centered on the image nearest to the
    2825              : !>        target atom. The list is constant over the target atom block and covariant under lattice
    2826              : !>        translations of individual atoms.
    2827              : !> \param grid_point ...
    2828              : !> \param atom_coords ...
    2829              : !> \param cell ...
    2830              : !> \param target_atom ...
    2831              : !> \param weight ...
    2832              : !> \param dweight_datom optional derivative at fixed grid point with respect to base atoms
    2833              : !> \param dweight_dstrain optional derivative due to lattice-image translations
    2834              : !> \param image_periodicity optional image directions used by the auxiliary-grid partition
    2835              : ! **************************************************************************************************
    2836       240080 :    SUBROUTINE periodic_atom_image_partition(grid_point, atom_coords, cell, target_atom, weight, &
    2837       240080 :                                             dweight_datom, dweight_dstrain, image_periodicity)
    2838              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: grid_point
    2839              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: atom_coords
    2840              :       TYPE(cell_type), POINTER                           :: cell
    2841              :       INTEGER, INTENT(IN)                                :: target_atom
    2842              :       REAL(KIND=dp), INTENT(OUT)                         :: weight
    2843              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT), &
    2844              :          OPTIONAL                                        :: dweight_datom, dweight_dstrain
    2845              :       INTEGER, DIMENSION(3), INTENT(IN), OPTIONAL        :: image_periodicity
    2846              : 
    2847              :       INTEGER :: active_count, active_image, active_index, base_shift(3), candidate_image, iatom, &
    2848              :          idir, iimage, image_count, image_index, ix, ix_max, ix_min, iy, iy_max, iy_min, iz, &
    2849              :          iz_max, iz_min, jatom, jdir, jimage, natom, target_image
    2850       240080 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: active_images, image_atom
    2851       240080 :       INTEGER, ALLOCATABLE, DIMENSION(:, :)              :: image_shift
    2852              :       INTEGER, DIMENSION(3)                              :: partition_periodicity
    2853              :       LOGICAL                                            :: need_derivatives
    2854              :       REAL(KIND=dp)                                      :: dist_diff, ds_dmu, mu, mu_raw, &
    2855              :                                                             one_minus_switch, rab, switch
    2856       240080 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: distances, normalized_weights
    2857       240080 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: image_coords, mean_log_derivative, &
    2858       240080 :                                                             rvecs, target_log_derivative, &
    2859       240080 :                                                             unit_rvecs
    2860              :       REAL(KIND=dp), DIMENSION(3) :: dmu_image_i, dmu_image_j, ds_image_i, ds_image_j, fractional, &
    2861              :          image_translation, image_weight_derivative, pair, unit_pair
    2862              : 
    2863       240080 :       natom = SIZE(atom_coords, 2)
    2864       240080 :       CPASSERT(ASSOCIATED(cell))
    2865       240080 :       CPASSERT(target_atom >= 1 .AND. target_atom <= natom)
    2866       240080 :       CPASSERT(SIZE(atom_coords, 1) == 3)
    2867       240080 :       need_derivatives = PRESENT(dweight_datom)
    2868       240080 :       CPASSERT(PRESENT(dweight_dstrain) .EQV. need_derivatives)
    2869       240080 :       IF (.NOT. need_derivatives) THEN
    2870              :          CALL periodic_atom_image_partition_value( &
    2871            0 :             grid_point, atom_coords, cell, target_atom, weight, image_periodicity)
    2872            0 :          RETURN
    2873              :       END IF
    2874              :       IF (need_derivatives) THEN
    2875       240080 :          CPASSERT(SIZE(dweight_datom, 1) == 3)
    2876       240080 :          CPASSERT(SIZE(dweight_datom, 2) == natom)
    2877       240080 :          CPASSERT(SIZE(dweight_dstrain, 1) == 3)
    2878       240080 :          CPASSERT(SIZE(dweight_dstrain, 2) == 3)
    2879      1680560 :          dweight_datom = 0.0_dp
    2880      3121040 :          dweight_dstrain = 0.0_dp
    2881              :       END IF
    2882       240080 :       weight = 0.0_dp
    2883       960320 :       partition_periodicity = cell%perd
    2884       240080 :       IF (PRESENT(image_periodicity)) partition_periodicity = image_periodicity
    2885       960320 :       CPASSERT(ALL(partition_periodicity == 0 .OR. partition_periodicity == 1))
    2886              : 
    2887       240080 :       image_count = 1
    2888       960320 :       DO idir = 1, 3
    2889       960320 :          IF (partition_periodicity(idir) == 1) THEN
    2890       720240 :             image_count = image_count*(2*periodic_partition_image_shell + 1)
    2891              :          END IF
    2892              :       END DO
    2893       240080 :       image_count = natom*image_count
    2894              :       ALLOCATE (active_images(image_count), image_atom(image_count), image_shift(3, image_count), &
    2895              :                 image_coords(3, image_count), distances(image_count), &
    2896              :                 normalized_weights(image_count), &
    2897              :                 rvecs(3, image_count), unit_rvecs(3, image_count), &
    2898              :                 mean_log_derivative(3, image_count), &
    2899      3601200 :                 target_log_derivative(3, image_count))
    2900              : 
    2901       600200 :       image_index = 0
    2902       600200 :       target_image = 0
    2903       600200 :       DO iatom = 1, natom
    2904              :          fractional = MATMUL(cell%h_inv, &
    2905      5761920 :                              atom_coords(:, target_atom) - atom_coords(:, iatom))
    2906      1440480 :          DO idir = 1, 3
    2907      1440480 :             base_shift(idir) = partition_periodicity(idir)*NINT(fractional(idir))
    2908              :          END DO
    2909       360120 :          ix_min = base_shift(1) - periodic_partition_image_shell*partition_periodicity(1)
    2910       360120 :          ix_max = base_shift(1) + periodic_partition_image_shell*partition_periodicity(1)
    2911       360120 :          iy_min = base_shift(2) - periodic_partition_image_shell*partition_periodicity(2)
    2912       360120 :          iy_max = base_shift(2) + periodic_partition_image_shell*partition_periodicity(2)
    2913       360120 :          iz_min = base_shift(3) - periodic_partition_image_shell*partition_periodicity(3)
    2914       360120 :          iz_max = base_shift(3) + periodic_partition_image_shell*partition_periodicity(3)
    2915      1680560 :          DO iz = iz_min, iz_max
    2916      4681560 :             DO iy = iy_min, iy_max
    2917     14044680 :                DO ix = ix_min, ix_max
    2918      9723240 :                   image_index = image_index + 1
    2919      9723240 :                   image_atom(image_index) = iatom
    2920     38892960 :                   image_shift(:, image_index) = [ix, iy, iz]
    2921              :                   image_coords(:, image_index) = atom_coords(:, iatom) + &
    2922    223634520 :                                                  MATMUL(cell%hmat, REAL([ix, iy, iz], KIND=dp))
    2923     17645880 :                   IF (iatom == target_atom .AND. ALL(image_shift(:, image_index) == 0)) THEN
    2924       240080 :                      target_image = image_index
    2925              :                   END IF
    2926              :                END DO
    2927              :             END DO
    2928              :          END DO
    2929              :       END DO
    2930       240080 :       CPASSERT(image_index == image_count)
    2931      9963320 :       DO iimage = 1, image_count
    2932     38892960 :          rvecs(:, iimage) = grid_point - image_coords(:, iimage)
    2933     38892960 :          distances(iimage) = SQRT(SUM(rvecs(:, iimage)**2))
    2934      9963320 :          IF (distances(iimage) > layout_tol) THEN
    2935     38892960 :             unit_rvecs(:, iimage) = rvecs(:, iimage)/distances(iimage)
    2936              :          ELSE
    2937            0 :             unit_rvecs(:, iimage) = 0.0_dp
    2938              :          END IF
    2939              :       END DO
    2940              : 
    2941       240080 :       CALL periodic_image_normalized_weights(image_coords, distances, normalized_weights)
    2942       240080 :       weight = normalized_weights(target_image)
    2943              : 
    2944       240080 :       IF (need_derivatives .AND. weight > 0.0_dp) THEN
    2945              :          active_count = 0
    2946      8857423 :          DO iimage = 1, image_count
    2947      8857423 :             IF (normalized_weights(iimage) > 0.0_dp) THEN
    2948      1625836 :                active_count = active_count + 1
    2949      1625836 :                active_images(active_count) = iimage
    2950              :             END IF
    2951              :          END DO
    2952       215425 :          CPASSERT(active_count > 0)
    2953              :          ! Revisit the image pairs after normalization to contract the logarithmic
    2954              :          ! weight derivatives directly, avoiding both an image_count-squared tensor
    2955              :          ! and pairs for which both screened normalized weights vanish.
    2956       215425 :          mean_log_derivative = 0.0_dp
    2957       215425 :          target_log_derivative = 0.0_dp
    2958      1841261 :          DO active_index = 1, active_count
    2959      1625836 :             active_image = active_images(active_index)
    2960     64454909 :             DO candidate_image = 1, image_count
    2961     62613648 :                IF (candidate_image == active_image) CYCLE
    2962              :                ! A pair of active images is visited by the lower-index image only.
    2963     60987812 :                IF (normalized_weights(candidate_image) > 0.0_dp .AND. &
    2964              :                    candidate_image < active_image) CYCLE
    2965     52512962 :                iimage = MIN(active_image, candidate_image)
    2966     52512962 :                jimage = MAX(active_image, candidate_image)
    2967    210051848 :                pair = image_coords(:, iimage) - image_coords(:, jimage)
    2968    210051848 :                rab = SQRT(SUM(pair**2))
    2969     52512962 :                IF (rab <= layout_tol) CYCLE
    2970    210051848 :                unit_pair = pair/rab
    2971     52512962 :                dist_diff = distances(iimage) - distances(jimage)
    2972     52512962 :                mu_raw = dist_diff/rab
    2973     52512962 :                mu = MAX(-1.0_dp, MIN(1.0_dp, mu_raw))
    2974     52512962 :                switch = 0.5_dp*(1.0_dp - becke_shape(mu))
    2975     52512962 :                one_minus_switch = 1.0_dp - switch
    2976     52512962 :                IF (ABS(mu_raw) < 1.0_dp) THEN
    2977     52407786 :                   ds_dmu = -0.5_dp*becke_shape_derivative(mu)
    2978              :                ELSE
    2979              :                   ds_dmu = 0.0_dp
    2980              :                END IF
    2981     52407786 :                IF (ABS(ds_dmu) <= 0.0_dp .OR. switch <= TINY(1.0_dp) .OR. &
    2982              :                    one_minus_switch <= TINY(1.0_dp)) CYCLE
    2983    199745552 :                dmu_image_i = (-unit_rvecs(:, iimage)*rab - dist_diff*unit_pair)/rab**2
    2984    199745552 :                dmu_image_j = (unit_rvecs(:, jimage)*rab + dist_diff*unit_pair)/rab**2
    2985    199745552 :                ds_image_i = ds_dmu*dmu_image_i
    2986    199745552 :                ds_image_j = ds_dmu*dmu_image_j
    2987              : 
    2988              :                mean_log_derivative(:, iimage) = mean_log_derivative(:, iimage) + &
    2989              :                                                 normalized_weights(iimage)*ds_image_i/switch - &
    2990    199745552 :                                                 normalized_weights(jimage)*ds_image_i/one_minus_switch
    2991              :                mean_log_derivative(:, jimage) = mean_log_derivative(:, jimage) + &
    2992              :                                                 normalized_weights(iimage)*ds_image_j/switch - &
    2993    199745552 :                                                 normalized_weights(jimage)*ds_image_j/one_minus_switch
    2994     51562224 :                IF (target_image == iimage) THEN
    2995              :                   target_log_derivative(:, iimage) = &
    2996     11224020 :                      target_log_derivative(:, iimage) + ds_image_i/switch
    2997              :                   target_log_derivative(:, jimage) = &
    2998     11224020 :                      target_log_derivative(:, jimage) + ds_image_j/switch
    2999     47130383 :                ELSE IF (target_image == jimage) THEN
    3000              :                   target_log_derivative(:, iimage) = &
    3001     12685368 :                      target_log_derivative(:, iimage) - ds_image_i/one_minus_switch
    3002              :                   target_log_derivative(:, jimage) = &
    3003     12685368 :                      target_log_derivative(:, jimage) - ds_image_j/one_minus_switch
    3004              :                END IF
    3005              :             END DO
    3006              :          END DO
    3007      8857423 :          DO jimage = 1, image_count
    3008              :             image_weight_derivative = weight*(target_log_derivative(:, jimage) - &
    3009     34567992 :                                               mean_log_derivative(:, jimage))
    3010      8641998 :             jatom = image_atom(jimage)
    3011              :             dweight_datom(:, jatom) = dweight_datom(:, jatom) + &
    3012     34567992 :                                       image_weight_derivative
    3013              :             image_translation = MATMUL( &
    3014    138271968 :                                 cell%hmat, REAL(image_shift(:, jimage), KIND=dp))
    3015     34783417 :             DO idir = 1, 3
    3016    112345974 :                DO jdir = 1, 3
    3017              :                   dweight_dstrain(idir, jdir) = dweight_dstrain(idir, jdir) + &
    3018              :                                                 image_weight_derivative(idir)* &
    3019    103703976 :                                                 image_translation(jdir)
    3020              :                END DO
    3021              :             END DO
    3022              :          END DO
    3023              :       END IF
    3024              : 
    3025            0 :       DEALLOCATE (active_images, image_atom, image_shift, image_coords, distances, &
    3026            0 :                   normalized_weights, rvecs, unit_rvecs, mean_log_derivative, &
    3027       240080 :                   target_log_derivative)
    3028              : 
    3029       240080 :    END SUBROUTINE periodic_atom_image_partition
    3030              : 
    3031              : ! **************************************************************************************************
    3032              : !> \brief Build smooth atom weights and their atom/cell deformation derivatives.
    3033              : !> \param grid_point ...
    3034              : !> \param atom_coords ...
    3035              : !> \param cell ...
    3036              : !> \param weights ...
    3037              : !> \param included ...
    3038              : !> \param dweights_datom ...
    3039              : !> \param dweights_dstrain ...
    3040              : ! **************************************************************************************************
    3041        86319 :    SUBROUTINE skala_gpw_smooth_partition_derivatives(grid_point, atom_coords, cell, &
    3042        86319 :                                                      weights, included, dweights_datom, &
    3043        86319 :                                                      dweights_dstrain)
    3044              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: grid_point
    3045              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: atom_coords
    3046              :       TYPE(cell_type), POINTER                           :: cell
    3047              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: weights
    3048              :       LOGICAL, DIMENSION(:), INTENT(OUT)                 :: included
    3049              :       REAL(KIND=dp), DIMENSION(:, :, :), INTENT(OUT)     :: dweights_datom, dweights_dstrain
    3050              : 
    3051              :       INTEGER                                            :: iatom, idir, jatom, jdir, natom
    3052              :       REAL(KIND=dp)                                      :: dist_diff, ds_dmu, included_sum, mu, &
    3053              :                                                             mu_raw, one_minus_switch, rab, rsum, &
    3054              :                                                             switch, total
    3055              :       REAL(KIND=dp), DIMENSION(3)                        :: dmu_atom_i, dmu_atom_j, ds_atom_i, &
    3056              :                                                             ds_atom_j, pair, unit_pair
    3057              :       REAL(KIND=dp), DIMENSION(3, 3)                     :: dmu_strain, ds_strain, mean_strain
    3058              :       REAL(KIND=dp), DIMENSION(3, SIZE(atom_coords, 2), &
    3059       172638 :          SIZE(atom_coords, 2))                           :: log_weight_atom
    3060       172638 :       REAL(KIND=dp), DIMENSION(3, SIZE(atom_coords, 2))  :: mean_atom, partition_atom_coords, rvecs, &
    3061       172638 :                                                             unit_rvecs
    3062              :       REAL(KIND=dp), &
    3063       172638 :          DIMENSION(3, 3, SIZE(atom_coords, 2))           :: log_weight_strain
    3064       172638 :       REAL(KIND=dp), DIMENSION(SIZE(atom_coords, 2))     :: distances, normalized_weights, &
    3065        86319 :                                                             raw_weights
    3066              : 
    3067        86319 :       natom = SIZE(atom_coords, 2)
    3068        86319 :       CPASSERT(SIZE(weights) == natom)
    3069        86319 :       CPASSERT(SIZE(included) == natom)
    3070        86319 :       CPASSERT(SIZE(dweights_datom, 1) == 3)
    3071        86319 :       CPASSERT(SIZE(dweights_datom, 2) == natom)
    3072        86319 :       CPASSERT(SIZE(dweights_datom, 3) == natom)
    3073        86319 :       CPASSERT(SIZE(dweights_dstrain, 1) == 3)
    3074        86319 :       CPASSERT(SIZE(dweights_dstrain, 2) == 3)
    3075        86319 :       CPASSERT(SIZE(dweights_dstrain, 3) == natom)
    3076              : 
    3077       258957 :       weights = 0.0_dp
    3078       258957 :       included = .FALSE.
    3079      1640061 :       dweights_datom = 0.0_dp
    3080      2330613 :       dweights_dstrain = 0.0_dp
    3081       258957 :       raw_weights = 1.0_dp
    3082      1640061 :       log_weight_atom = 0.0_dp
    3083      2330613 :       log_weight_strain = 0.0_dp
    3084              : 
    3085       258957 :       DO iatom = 1, natom
    3086              :          partition_atom_coords(:, iatom) = &
    3087       172638 :             nearest_atom_image_coordinate(atom_coords(:, iatom), grid_point, cell)
    3088       690552 :          rvecs(:, iatom) = grid_point - partition_atom_coords(:, iatom)
    3089       690552 :          distances(iatom) = SQRT(SUM(rvecs(:, iatom)**2))
    3090       258957 :          IF (distances(iatom) > layout_tol) THEN
    3091       690552 :             unit_rvecs(:, iatom) = rvecs(:, iatom)/distances(iatom)
    3092              :          ELSE
    3093            0 :             unit_rvecs(:, iatom) = 0.0_dp
    3094              :          END IF
    3095              :       END DO
    3096              : 
    3097       172638 :       DO iatom = 1, natom - 1
    3098       258957 :          DO jatom = iatom + 1, natom
    3099       345276 :             pair = partition_atom_coords(:, iatom) - partition_atom_coords(:, jatom)
    3100       345276 :             rab = SQRT(SUM(pair**2))
    3101        86319 :             IF (rab <= layout_tol) CYCLE
    3102       345276 :             unit_pair = pair/rab
    3103        86319 :             dist_diff = distances(iatom) - distances(jatom)
    3104        86319 :             mu_raw = dist_diff/rab
    3105        86319 :             mu = MAX(-1.0_dp, MIN(1.0_dp, mu_raw))
    3106        86319 :             switch = 0.5_dp*(1.0_dp - becke_shape(mu))
    3107        86319 :             one_minus_switch = 1.0_dp - switch
    3108              : 
    3109        86319 :             IF (ABS(mu_raw) < 1.0_dp) THEN
    3110        86241 :                ds_dmu = -0.5_dp*becke_shape_derivative(mu)
    3111              :             ELSE
    3112              :                ds_dmu = 0.0_dp
    3113              :             END IF
    3114        86241 :             IF (ABS(ds_dmu) > 0.0_dp .AND. switch > TINY(1.0_dp) .AND. &
    3115              :                 one_minus_switch > TINY(1.0_dp)) THEN
    3116       344748 :                dmu_atom_i = (-unit_rvecs(:, iatom)*rab - dist_diff*unit_pair)/rab**2
    3117       344748 :                dmu_atom_j = (unit_rvecs(:, jatom)*rab + dist_diff*unit_pair)/rab**2
    3118       344748 :                ds_atom_i = ds_dmu*dmu_atom_i
    3119       344748 :                ds_atom_j = ds_dmu*dmu_atom_j
    3120              :                log_weight_atom(:, iatom, iatom) = &
    3121       344748 :                   log_weight_atom(:, iatom, iatom) + ds_atom_i/switch
    3122              :                log_weight_atom(:, iatom, jatom) = &
    3123       344748 :                   log_weight_atom(:, iatom, jatom) - ds_atom_i/one_minus_switch
    3124              :                log_weight_atom(:, jatom, iatom) = &
    3125       344748 :                   log_weight_atom(:, jatom, iatom) + ds_atom_j/switch
    3126              :                log_weight_atom(:, jatom, jatom) = &
    3127       344748 :                   log_weight_atom(:, jatom, jatom) - ds_atom_j/one_minus_switch
    3128              : 
    3129       344748 :                DO idir = 1, 3
    3130      1120431 :                   DO jdir = 1, 3
    3131              :                      dmu_strain(idir, jdir) = &
    3132              :                         ((unit_rvecs(idir, iatom)*rvecs(jdir, iatom) - &
    3133              :                           unit_rvecs(idir, jatom)*rvecs(jdir, jatom))*rab - &
    3134      1034244 :                          dist_diff*unit_pair(idir)*pair(jdir))/rab**2
    3135              :                   END DO
    3136              :                END DO
    3137      1120431 :                ds_strain = ds_dmu*dmu_strain
    3138              :                log_weight_strain(:, :, iatom) = &
    3139      1120431 :                   log_weight_strain(:, :, iatom) + ds_strain/switch
    3140              :                log_weight_strain(:, :, jatom) = &
    3141      1120431 :                   log_weight_strain(:, :, jatom) - ds_strain/one_minus_switch
    3142              :             END IF
    3143              : 
    3144        86319 :             raw_weights(iatom) = raw_weights(iatom)*switch
    3145       172638 :             raw_weights(jatom) = raw_weights(jatom)*one_minus_switch
    3146              :          END DO
    3147              :       END DO
    3148              : 
    3149       258957 :       total = SUM(raw_weights)
    3150        86319 :       IF (total > 0.0_dp) THEN
    3151       258957 :          normalized_weights = raw_weights/total
    3152       258957 :          included = normalized_weights > smooth_partition_eps
    3153              :       ELSE
    3154              :          rsum = HUGE(1.0_dp)
    3155              :          jatom = 1
    3156            0 :          DO iatom = 1, natom
    3157            0 :             IF (distances(iatom) < rsum) THEN
    3158            0 :                rsum = distances(iatom)
    3159            0 :                jatom = iatom
    3160              :             END IF
    3161              :          END DO
    3162            0 :          included(jatom) = .TRUE.
    3163            0 :          weights(jatom) = 1.0_dp
    3164            0 :          RETURN
    3165              :       END IF
    3166              : 
    3167       258957 :       included_sum = SUM(raw_weights, MASK=included)
    3168        86319 :       IF (included_sum <= 0.0_dp) THEN
    3169              :          rsum = HUGE(1.0_dp)
    3170              :          jatom = 1
    3171            0 :          DO iatom = 1, natom
    3172            0 :             IF (distances(iatom) < rsum) THEN
    3173            0 :                rsum = distances(iatom)
    3174            0 :                jatom = iatom
    3175              :             END IF
    3176              :          END DO
    3177            0 :          included = .FALSE.
    3178            0 :          included(jatom) = .TRUE.
    3179            0 :          weights = 0.0_dp
    3180            0 :          weights(jatom) = 1.0_dp
    3181            0 :          RETURN
    3182              :       END IF
    3183              : 
    3184       258957 :       DO iatom = 1, natom
    3185       258957 :          IF (included(iatom)) weights(iatom) = raw_weights(iatom)/included_sum
    3186              :       END DO
    3187              : 
    3188       776871 :       mean_atom = 0.0_dp
    3189        86319 :       mean_strain = 0.0_dp
    3190       258957 :       DO iatom = 1, natom
    3191       172638 :          IF (.NOT. included(iatom)) CYCLE
    3192      2238262 :          mean_strain = mean_strain + weights(iatom)*log_weight_strain(:, :, iatom)
    3193       602841 :          DO jatom = 1, natom
    3194              :             mean_atom(:, jatom) = mean_atom(:, jatom) + &
    3195      1550030 :                                   weights(iatom)*log_weight_atom(:, jatom, iatom)
    3196              :          END DO
    3197              :       END DO
    3198              : 
    3199       258957 :       DO iatom = 1, natom
    3200       172638 :          IF (.NOT. included(iatom)) CYCLE
    3201              :          dweights_dstrain(:, :, iatom) = &
    3202      2238262 :             weights(iatom)*(log_weight_strain(:, :, iatom) - mean_strain)
    3203       602841 :          DO jatom = 1, natom
    3204              :             dweights_datom(:, jatom, iatom) = &
    3205      1550030 :                weights(iatom)*(log_weight_atom(:, jatom, iatom) - mean_atom(:, jatom))
    3206              :          END DO
    3207              :       END DO
    3208              : 
    3209              :    END SUBROUTINE skala_gpw_smooth_partition_derivatives
    3210              : 
    3211              : ! **************************************************************************************************
    3212              : !> \brief Smoothly suppress a sparse atom row's internal quadrature weight at the layout cutoff.
    3213              : !> \param weight normalized smooth-partition weight
    3214              : !> \return scale in the interval [0, 1]
    3215              : ! **************************************************************************************************
    3216       588690 :    PURE FUNCTION smooth_partition_atomic_weight_scale(weight) RESULT(scale)
    3217              :       REAL(KIND=dp), INTENT(IN)                          :: weight
    3218              :       REAL(KIND=dp)                                      :: scale
    3219              : 
    3220              :       REAL(KIND=dp)                                      :: x
    3221              : 
    3222       588690 :       IF (weight <= smooth_partition_eps) THEN
    3223              :          scale = 0.0_dp
    3224       545898 :       ELSE IF (weight >= smooth_partition_taper_end) THEN
    3225              :          scale = 1.0_dp
    3226              :       ELSE
    3227              :          x = (weight - smooth_partition_eps)/ &
    3228         1470 :              (smooth_partition_taper_end - smooth_partition_eps)
    3229         1470 :          scale = x**3*(10.0_dp + x*(-15.0_dp + 6.0_dp*x))
    3230              :       END IF
    3231              : 
    3232       588690 :    END FUNCTION smooth_partition_atomic_weight_scale
    3233              : 
    3234              : ! **************************************************************************************************
    3235              : !> \brief Derivative of the sparse atom-row quadrature taper with respect to partition weight.
    3236              : !> \param weight normalized smooth-partition weight
    3237              : !> \return derivative of the internal quadrature-weight scale
    3238              : ! **************************************************************************************************
    3239       292214 :    PURE FUNCTION smooth_partition_atomic_weight_scale_derivative(weight) RESULT(derivative)
    3240              :       REAL(KIND=dp), INTENT(IN)                          :: weight
    3241              :       REAL(KIND=dp)                                      :: derivative
    3242              : 
    3243              :       REAL(KIND=dp)                                      :: x
    3244              : 
    3245       292214 :       IF (weight <= smooth_partition_eps .OR. weight >= smooth_partition_taper_end) THEN
    3246              :          derivative = 0.0_dp
    3247              :       ELSE
    3248              :          x = (weight - smooth_partition_eps)/ &
    3249          588 :              (smooth_partition_taper_end - smooth_partition_eps)
    3250              :          derivative = 30.0_dp*x**2*(1.0_dp - x)**2/ &
    3251          588 :                       (smooth_partition_taper_end - smooth_partition_eps)
    3252              :       END IF
    3253              : 
    3254       292214 :    END FUNCTION smooth_partition_atomic_weight_scale_derivative
    3255              : 
    3256              : ! **************************************************************************************************
    3257              : !> \brief Becke fuzzy-cell shape function.
    3258              : !> \param mu ...
    3259              : !> \return ...
    3260              : ! **************************************************************************************************
    3261    852798531 :    PURE FUNCTION becke_shape(mu) RESULT(val)
    3262              :       REAL(KIND=dp), INTENT(IN)                          :: mu
    3263              :       REAL(KIND=dp)                                      :: val
    3264              : 
    3265              :       INTEGER                                            :: iter
    3266              : 
    3267    852798531 :       val = mu
    3268   3411194124 :       DO iter = 1, 3
    3269   3411194124 :          val = 0.5_dp*val*(3.0_dp - val*val)
    3270              :       END DO
    3271              : 
    3272    852798531 :    END FUNCTION becke_shape
    3273              : 
    3274              : ! **************************************************************************************************
    3275              : !> \brief Derivative of the Becke fuzzy-cell shape function.
    3276              : !> \param mu ...
    3277              : !> \return ...
    3278              : ! **************************************************************************************************
    3279     52494027 :    PURE FUNCTION becke_shape_derivative(mu) RESULT(val)
    3280              :       REAL(KIND=dp), INTENT(IN)                          :: mu
    3281              :       REAL(KIND=dp)                                      :: val
    3282              : 
    3283              :       INTEGER                                            :: iter
    3284              :       REAL(KIND=dp)                                      :: x
    3285              : 
    3286     52494027 :       x = mu
    3287     52494027 :       val = 1.0_dp
    3288    209976108 :       DO iter = 1, 3
    3289    157482081 :          val = val*1.5_dp*(1.0_dp - x*x)
    3290    209976108 :          x = 0.5_dp*x*(3.0_dp - x*x)
    3291              :       END DO
    3292              : 
    3293     52494027 :    END FUNCTION becke_shape_derivative
    3294              : 
    3295              : ! **************************************************************************************************
    3296              : !> \brief Return the atom image nearest to a regular-grid point.
    3297              : !> \param atom_coord ...
    3298              : !> \param grid_point ...
    3299              : !> \param cell ...
    3300              : !> \return ...
    3301              : ! **************************************************************************************************
    3302       283230 :    FUNCTION nearest_atom_image_coordinate(atom_coord, grid_point, cell) RESULT(coord)
    3303              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: atom_coord, grid_point
    3304              :       TYPE(cell_type), POINTER                           :: cell
    3305              :       REAL(KIND=dp), DIMENSION(3)                        :: coord
    3306              : 
    3307              :       REAL(KIND=dp)                                      :: dx, dy, dz
    3308              : 
    3309       283230 :       IF (cell%orthorhombic) THEN
    3310       283230 :          dx = atom_coord(1) - grid_point(1)
    3311       283230 :          dy = atom_coord(2) - grid_point(2)
    3312       283230 :          dz = atom_coord(3) - grid_point(3)
    3313       283230 :          dx = dx - cell%hmat(1, 1)*cell%perd(1)*ANINT(cell%h_inv(1, 1)*dx)
    3314       283230 :          dy = dy - cell%hmat(2, 2)*cell%perd(2)*ANINT(cell%h_inv(2, 2)*dy)
    3315       283230 :          dz = dz - cell%hmat(3, 3)*cell%perd(3)*ANINT(cell%h_inv(3, 3)*dz)
    3316      1132920 :          coord = grid_point + [dx, dy, dz]
    3317              :       ELSE
    3318            0 :          coord = grid_point + pbc(grid_point, atom_coord, cell)
    3319              :       END IF
    3320              : 
    3321       283230 :    END FUNCTION nearest_atom_image_coordinate
    3322              : 
    3323              : ! **************************************************************************************************
    3324              : !> \brief Assign a grid point to the nearest periodic atom.
    3325              : !> \param grid_point ...
    3326              : !> \param atom_coords ...
    3327              : !> \param cell ...
    3328              : !> \return ...
    3329              : ! **************************************************************************************************
    3330       849406 :    FUNCTION nearest_atom(grid_point, atom_coords, cell) RESULT(owner)
    3331              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: grid_point
    3332              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: atom_coords
    3333              :       TYPE(cell_type), POINTER                           :: cell
    3334              :       INTEGER                                            :: owner
    3335              : 
    3336              :       INTEGER                                            :: iatom
    3337              :       REAL(KIND=dp)                                      :: best_r2, dx, dy, dz, r2
    3338              :       REAL(KIND=dp), DIMENSION(3)                        :: rij
    3339              : 
    3340       849406 :       owner = 1
    3341       849406 :       best_r2 = HUGE(1.0_dp)
    3342       849406 :       IF (cell%orthorhombic) THEN
    3343      3426905 :          DO iatom = 1, SIZE(atom_coords, 2)
    3344      2577499 :             dx = grid_point(1) - atom_coords(1, iatom)
    3345      2577499 :             dy = grid_point(2) - atom_coords(2, iatom)
    3346      2577499 :             dz = grid_point(3) - atom_coords(3, iatom)
    3347      2577499 :             dx = dx - cell%hmat(1, 1)*cell%perd(1)*ANINT(cell%h_inv(1, 1)*dx)
    3348      2577499 :             dy = dy - cell%hmat(2, 2)*cell%perd(2)*ANINT(cell%h_inv(2, 2)*dy)
    3349      2577499 :             dz = dz - cell%hmat(3, 3)*cell%perd(3)*ANINT(cell%h_inv(3, 3)*dz)
    3350      2577499 :             r2 = dx*dx + dy*dy + dz*dz
    3351      3426905 :             IF (r2 < best_r2) THEN
    3352      1548787 :                best_r2 = r2
    3353      1548787 :                owner = iatom
    3354              :             END IF
    3355              :          END DO
    3356              :       ELSE
    3357            0 :          DO iatom = 1, SIZE(atom_coords, 2)
    3358            0 :             rij = pbc(grid_point, atom_coords(:, iatom), cell)
    3359            0 :             r2 = SUM(rij**2)
    3360            0 :             IF (r2 < best_r2) THEN
    3361            0 :                best_r2 = r2
    3362            0 :                owner = iatom
    3363              :             END IF
    3364              :          END DO
    3365              :       END IF
    3366              : 
    3367       849406 :    END FUNCTION nearest_atom
    3368              : 
    3369      9002916 : END MODULE skala_gpw_features
        

Generated by: LCOV version 2.0-1