LCOV - code coverage report
Current view: top level - src - skala_gpw_functional.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:71c3ab0) Lines: 76.2 % 1013 772
Test Date: 2026-07-25 06:35:44 Functions: 88.9 % 27 24

            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 Experimental CP2K-native GPW real-space-grid path for SKALA TorchScript models.
      10              : ! **************************************************************************************************
      11              : MODULE skala_gpw_functional
      12              :    USE cell_types,                      ONLY: cell_type,&
      13              :                                               pbc
      14              :    USE cp_array_utils,                  ONLY: cp_3d_r_cp_type
      15              :    USE cp_log_handling,                 ONLY: cp_logger_get_default_io_unit
      16              :    USE input_section_types,             ONLY: section_get_rval,&
      17              :                                               section_vals_get_subs_vals,&
      18              :                                               section_vals_get_subs_vals2,&
      19              :                                               section_vals_type,&
      20              :                                               section_vals_val_get
      21              :    USE kinds,                           ONLY: default_path_length,&
      22              :                                               dp,&
      23              :                                               int_8
      24              :    USE message_passing,                 ONLY: mp_comm_type
      25              :    USE offload_api,                     ONLY: offload_set_chosen_device
      26              :    USE particle_types,                  ONLY: particle_type
      27              :    USE pw_grid_types,                   ONLY: pw_grid_type
      28              :    USE pw_methods,                      ONLY: pw_scale,&
      29              :                                               pw_zero
      30              :    USE pw_pool_types,                   ONLY: pw_pool_type
      31              :    USE pw_types,                        ONLY: pw_c1d_gs_type,&
      32              :                                               pw_r3d_rs_type
      33              :    USE qs_grid_atom,                    ONLY: grid_atom_type
      34              :    USE skala_gpw_features,              ONLY: skala_gpw_atom_partition_hard,&
      35              :                                               skala_gpw_atom_partition_smooth,&
      36              :                                               skala_gpw_atom_subchunk_count,&
      37              :                                               skala_gpw_feature_build,&
      38              :                                               skala_gpw_feature_build_atom_subchunk,&
      39              :                                               skala_gpw_feature_release,&
      40              :                                               skala_gpw_feature_type,&
      41              :                                               skala_gpw_smooth_partition_derivatives
      42              :    USE skala_torch_api,                 ONLY: skala_torch_model_get_exc,&
      43              :                                               skala_torch_model_get_exc_density,&
      44              :                                               skala_torch_model_load,&
      45              :                                               skala_torch_model_release,&
      46              :                                               skala_torch_model_type
      47              :    USE string_utilities,                ONLY: uppercase
      48              :    USE torch_api,                       ONLY: &
      49              :         torch_cuda_device_count, torch_cuda_is_available, torch_dict_create, torch_dict_insert, &
      50              :         torch_dict_release, torch_dict_type, torch_tensor_backward_scalar, torch_tensor_data_ptr, &
      51              :         torch_tensor_from_array, torch_tensor_grad, torch_tensor_release, &
      52              :         torch_tensor_to_device_leaf, torch_tensor_type, torch_use_cuda
      53              :    USE xc_rho_cflags_types,             ONLY: xc_rho_cflags_type
      54              :    USE xc_rho_set_types,                ONLY: xc_rho_set_create,&
      55              :                                               xc_rho_set_get,&
      56              :                                               xc_rho_set_release,&
      57              :                                               xc_rho_set_type,&
      58              :                                               xc_rho_set_update
      59              :    USE xc_util,                         ONLY: xc_pw_divergence,&
      60              :                                               xc_requires_tmp_g
      61              : #include "./base/base_uses.f90"
      62              : 
      63              :    IMPLICIT NONE
      64              : 
      65              :    PRIVATE
      66              : 
      67              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'skala_gpw_functional'
      68              :    INTEGER, PARAMETER, PRIVATE          :: atom_chunk_auto_max_rows = 400000, &
      69              :                                            atom_chunk_auto_min_rows = 100000, &
      70              :                                            atom_chunk_auto_row_quantum = 100000, &
      71              :                                            ncollapsed_grad_per_point = 5, ngrad_per_point = 10
      72              :    INTEGER, PARAMETER, PUBLIC           :: skala_gapw_density_partition_hard_minus_soft = 1, &
      73              :                                            skala_gapw_density_partition_hard_only = 2, &
      74              :                                            skala_gapw_density_partition_soft_only = 3, &
      75              :                                            skala_gapw_density_partition_none = 4
      76              : 
      77              :    PUBLIC :: ensure_native_skala_grid_scope, get_gauxc_section, skala_gapw_atom_vxc_of_r, &
      78              :              native_skala_gapw_density_partition, skala_gpw_eval, skala_gpw_exc_density, &
      79              :              xc_section_uses_native_skala_grid, xc_section_uses_gauxc_model
      80              : 
      81              :    TYPE(skala_torch_model_type), SAVE                  :: cached_model
      82              :    CHARACTER(len=default_path_length), SAVE            :: cached_model_path = ""
      83              :    LOGICAL, SAVE                                       :: cached_model_loaded = .FALSE.
      84              :    INTEGER, SAVE                                       :: cached_model_cuda_device = -3
      85              :    INTEGER, SAVE                                       :: logged_cuda_device = -3, &
      86              :                                                           logged_cuda_device_count = -1, &
      87              :                                                           logged_cuda_nproc = -1, &
      88              :                                                           logged_cuda_request = -3
      89              : 
      90              : CONTAINS
      91              : 
      92              : ! **************************************************************************************************
      93              : !> \brief Return true if the GAUXC subsection requests the CP2K-native GPW grid path.
      94              : !> \param xc_section ...
      95              : !> \return ...
      96              : ! **************************************************************************************************
      97       157281 :    FUNCTION xc_section_uses_native_skala_grid(xc_section) RESULT(uses_native_grid)
      98              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
      99              :       LOGICAL                                            :: uses_native_grid
     100              : 
     101              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     102              : 
     103       157281 :       uses_native_grid = .FALSE.
     104       157281 :       gauxc_section => get_gauxc_section(xc_section)
     105       157281 :       IF (ASSOCIATED(gauxc_section)) THEN
     106          998 :          CALL section_vals_val_get(gauxc_section, "NATIVE_GRID", l_val=uses_native_grid)
     107              :       END IF
     108              : 
     109       157281 :    END FUNCTION xc_section_uses_native_skala_grid
     110              : 
     111              : ! **************************************************************************************************
     112              : !> \brief Return true if the GAUXC subsection requests a model evaluation.
     113              : !> \param xc_section ...
     114              : !> \return ...
     115              : ! **************************************************************************************************
     116        30902 :    FUNCTION xc_section_uses_gauxc_model(xc_section) RESULT(uses_gauxc_model)
     117              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     118              :       LOGICAL                                            :: uses_gauxc_model
     119              : 
     120              :       CHARACTER(len=default_path_length)                 :: model_key, model_name, xc_key, xc_name
     121              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     122              : 
     123        30902 :       uses_gauxc_model = .FALSE.
     124        30902 :       gauxc_section => get_gauxc_section(xc_section)
     125        30902 :       IF (ASSOCIATED(gauxc_section)) THEN
     126          144 :          CALL section_vals_val_get(gauxc_section, "MODEL", c_val=model_name)
     127          144 :          CALL section_vals_val_get(gauxc_section, "FUNCTIONAL", c_val=xc_name)
     128          144 :          model_key = ADJUSTL(model_name)
     129          144 :          xc_key = ADJUSTL(xc_name)
     130          144 :          CALL uppercase(model_key)
     131          144 :          CALL uppercase(xc_key)
     132              :          uses_gauxc_model = (TRIM(model_key) /= "" .AND. TRIM(model_key) /= "NONE" .AND. &
     133          144 :                              TRIM(model_key) /= TRIM(xc_key))
     134              :       END IF
     135              : 
     136        30902 :    END FUNCTION xc_section_uses_gauxc_model
     137              : 
     138              : ! **************************************************************************************************
     139              : !> \brief Return the hard/soft GAPW one-center density partition for native SKALA.
     140              : !> \param xc_section ...
     141              : !> \return ...
     142              : ! **************************************************************************************************
     143          144 :    FUNCTION native_skala_gapw_density_partition(xc_section) RESULT(partition)
     144              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     145              :       INTEGER                                            :: partition
     146              : 
     147              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     148              : 
     149          144 :       partition = skala_gapw_density_partition_hard_minus_soft
     150          144 :       gauxc_section => get_gauxc_section(xc_section)
     151          144 :       IF (ASSOCIATED(gauxc_section)) THEN
     152              :          CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_GAPW_DENSITY_PARTITION", &
     153          144 :                                    i_val=partition)
     154              :       END IF
     155              : 
     156              :       SELECT CASE (partition)
     157              :       CASE (skala_gapw_density_partition_hard_minus_soft, &
     158              :             skala_gapw_density_partition_hard_only, &
     159              :             skala_gapw_density_partition_soft_only, &
     160              :             skala_gapw_density_partition_none)
     161            0 :          CONTINUE
     162              :       CASE DEFAULT
     163              :          CALL cp_abort(__LOCATION__, &
     164          144 :                        "Unknown GAUXC%NATIVE_GRID_GAPW_DENSITY_PARTITION value.")
     165              :       END SELECT
     166              : 
     167          144 :    END FUNCTION native_skala_gapw_density_partition
     168              : 
     169              : ! **************************************************************************************************
     170              : !> \brief Enforce the currently implemented native SKALA GPW input scope.
     171              : !> \param xc_section ...
     172              : ! **************************************************************************************************
     173          580 :    SUBROUTINE ensure_native_skala_grid_scope(xc_section)
     174              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     175              : 
     176              :       CHARACTER(len=default_path_length)                 :: model_key, model_name
     177              :       INTEGER                                            :: ifun, nfun
     178              :       LOGICAL                                            :: native_grid
     179              :       TYPE(section_vals_type), POINTER                   :: functionals, gauxc_section, xc_fun
     180              : 
     181          290 :       NULLIFY (gauxc_section)
     182          290 :       IF (.NOT. ASSOCIATED(xc_section)) THEN
     183            0 :          CPABORT("Native SKALA GPW requires an XC section")
     184              :       END IF
     185              : 
     186          290 :       functionals => section_vals_get_subs_vals(xc_section, "XC_FUNCTIONAL")
     187          290 :       IF (.NOT. ASSOCIATED(functionals)) THEN
     188            0 :          CPABORT("Native SKALA GPW requires an XC_FUNCTIONAL section")
     189              :       END IF
     190              : 
     191          290 :       nfun = 0
     192          290 :       ifun = 0
     193              :       DO
     194          580 :          ifun = ifun + 1
     195          580 :          xc_fun => section_vals_get_subs_vals2(functionals, i_section=ifun)
     196          580 :          IF (.NOT. ASSOCIATED(xc_fun)) EXIT
     197          290 :          nfun = nfun + 1
     198          580 :          IF (xc_fun%section%name == "GAUXC") gauxc_section => xc_fun
     199              :       END DO
     200              : 
     201          290 :       IF (.NOT. ASSOCIATED(gauxc_section)) THEN
     202            0 :          CPABORT("Native SKALA GPW requires an XC_FUNCTIONAL%GAUXC section")
     203              :       END IF
     204          290 :       IF (nfun /= 1) THEN
     205            0 :          CPABORT("Native SKALA GPW requires GAUXC to be the only XC functional")
     206              :       END IF
     207              : 
     208          290 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID", l_val=native_grid)
     209          290 :       IF (.NOT. native_grid) RETURN
     210              : 
     211          290 :       CALL section_vals_val_get(gauxc_section, "MODEL", c_val=model_name)
     212          290 :       model_key = ADJUSTL(model_name)
     213          290 :       CALL uppercase(model_key)
     214          290 :       IF (TRIM(model_key) == "NONE" .OR. TRIM(model_key) == "") THEN
     215            0 :          CPABORT("Native SKALA GPW requires GAUXC%MODEL SKALA or a TorchScript model path")
     216              :       END IF
     217              : 
     218              :    END SUBROUTINE ensure_native_skala_grid_scope
     219              : 
     220              : ! **************************************************************************************************
     221              : !> \brief Evaluate SKALA energy and first derivatives on a CP2K GPW grid.
     222              : !> \param vxc_rho ...
     223              : !> \param vxc_tau ...
     224              : !> \param exc ...
     225              : !> \param rho_r ...
     226              : !> \param rho_g ...
     227              : !> \param tau ...
     228              : !> \param xc_section ...
     229              : !> \param weights ...
     230              : !> \param pw_pool ...
     231              : !> \param particle_set ...
     232              : !> \param cell ...
     233              : !> \param compute_virial ...
     234              : !> \param virial_xc ...
     235              : !> \param just_energy ...
     236              : !> \param atom_force ...
     237              : ! **************************************************************************************************
     238          290 :    SUBROUTINE skala_gpw_eval(vxc_rho, vxc_tau, exc, rho_r, rho_g, tau, xc_section, &
     239              :                              weights, pw_pool, particle_set, cell, compute_virial, virial_xc, &
     240          290 :                              just_energy, atom_force)
     241              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: vxc_rho, vxc_tau
     242              :       REAL(KIND=dp), INTENT(OUT)                         :: exc
     243              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: rho_r
     244              :       TYPE(pw_c1d_gs_type), DIMENSION(:), POINTER        :: rho_g
     245              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: tau
     246              :       TYPE(section_vals_type), POINTER                   :: xc_section
     247              :       TYPE(pw_r3d_rs_type), POINTER                      :: weights
     248              :       TYPE(pw_pool_type), POINTER                        :: pw_pool
     249              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     250              :       TYPE(cell_type), POINTER                           :: cell
     251              :       LOGICAL, INTENT(IN)                                :: compute_virial
     252              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(OUT)        :: virial_xc
     253              :       LOGICAL, INTENT(IN), OPTIONAL                      :: just_energy
     254              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT), &
     255              :          OPTIONAL                                        :: atom_force
     256              : 
     257              :       CHARACTER(len=default_path_length)                 :: model_path
     258              :       INTEGER :: iw, native_grid_atom_chunk_max_rows, native_grid_atom_partition, &
     259              :          native_grid_atom_subchunks, native_grid_cuda_device, nspins, phase_handle, &
     260              :          selected_cuda_device, xc_deriv_method_id, xc_rho_smooth_id
     261              :       LOGICAL :: has_atom_chunk_work, have_atom_coord_grad, lsd, my_just_energy, &
     262              :          native_grid_atom_chunk_routing, native_grid_atom_chunks, native_grid_diagnostics, &
     263              :          native_grid_use_cuda, needs_atom_force, use_atom_subchunks
     264          290 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: density_grad, kin_grad
     265          290 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: grad_grad
     266              :       REAL(KIND=dp), DIMENSION(3, 3)                     :: virial_before
     267              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     268          290 :       TYPE(skala_gpw_feature_type)                       :: features
     269              :       TYPE(torch_tensor_type)                            :: atom_coord_grad_t, &
     270              :                                                             atomic_grid_weight_grad_t, exc_tensor, &
     271              :                                                             grid_coord_grad_t, grid_weight_grad_t
     272              :       TYPE(xc_rho_cflags_type)                           :: needs
     273              :       TYPE(xc_rho_set_type)                              :: rho_set
     274              : 
     275          290 :       virial_xc = 0.0_dp
     276          290 :       exc = 0.0_dp
     277          290 :       my_just_energy = .FALSE.
     278          290 :       IF (PRESENT(just_energy)) my_just_energy = just_energy
     279          290 :       needs_atom_force = PRESENT(atom_force)
     280          770 :       IF (needs_atom_force) atom_force = 0.0_dp
     281          290 :       have_atom_coord_grad = .FALSE.
     282              : 
     283          290 :       IF (compute_virial .AND. my_just_energy) THEN
     284              :          CALL cp_abort(__LOCATION__, &
     285            0 :                        "Native SKALA GPW stress/virial requires feature gradients.")
     286              :       END IF
     287          290 :       IF (.NOT. ASSOCIATED(rho_g)) THEN
     288              :          CALL cp_abort(__LOCATION__, &
     289            0 :                        "Native SKALA GPW requires the reciprocal-space density to form density gradients.")
     290              :       END IF
     291          290 :       IF (.NOT. ASSOCIATED(tau)) THEN
     292              :          CALL cp_abort(__LOCATION__, &
     293            0 :                        "Native SKALA GPW requires the kinetic-energy density.")
     294              :       END IF
     295              : 
     296          290 :       nspins = SIZE(rho_r)
     297          290 :       lsd = (nspins /= 1)
     298          290 :       CALL get_skala_model_path(xc_section, model_path)
     299          290 :       gauxc_section => get_gauxc_section(xc_section)
     300          290 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_USE_CUDA", l_val=native_grid_use_cuda)
     301              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_CUDA_DEVICE", &
     302          290 :                                 i_val=native_grid_cuda_device)
     303              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_CHUNKS", &
     304          290 :                                 l_val=native_grid_atom_chunks)
     305              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_CHUNK_ROUTING", &
     306          290 :                                 l_val=native_grid_atom_chunk_routing)
     307              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_CHUNK_MAX_ROWS", &
     308          290 :                                 i_val=native_grid_atom_chunk_max_rows)
     309              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_PARTITION", &
     310          290 :                                 i_val=native_grid_atom_partition)
     311           26 :       SELECT CASE (native_grid_atom_partition)
     312              :       CASE (1)
     313           26 :          native_grid_atom_partition = skala_gpw_atom_partition_hard
     314              :       CASE (2)
     315          264 :          native_grid_atom_partition = skala_gpw_atom_partition_smooth
     316              :       CASE DEFAULT
     317              :          CALL cp_abort(__LOCATION__, &
     318          290 :                        "Unknown GAUXC%NATIVE_GRID_ATOM_PARTITION value.")
     319              :       END SELECT
     320          290 :       native_grid_atom_chunk_routing = native_grid_atom_chunk_routing .OR. native_grid_atom_chunks
     321          290 :       native_grid_atom_chunks = native_grid_atom_chunks .OR. native_grid_atom_chunk_routing
     322          290 :       IF (native_grid_atom_chunk_max_rows < -1) THEN
     323              :          CALL cp_abort(__LOCATION__, &
     324            0 :                        "GAUXC%NATIVE_GRID_ATOM_CHUNK_MAX_ROWS must be -1, zero, or positive.")
     325              :       END IF
     326          290 :       IF (needs_atom_force .OR. compute_virial) THEN
     327           60 :          IF (native_grid_atom_partition == skala_gpw_atom_partition_hard) THEN
     328            0 :             native_grid_atom_partition = skala_gpw_atom_partition_smooth
     329              :          END IF
     330           60 :          native_grid_atom_chunk_routing = .FALSE.
     331           60 :          native_grid_atom_chunks = .FALSE.
     332              :       END IF
     333              :       ! The portable SKALA export used by the regtests builds ragged-index tensors on CPU.
     334          290 :       CALL torch_use_cuda(native_grid_use_cuda)
     335              :       selected_cuda_device = configure_native_grid_cuda( &
     336          290 :                              native_grid_use_cuda, native_grid_cuda_device, rho_r(1)%pw_grid%para%group)
     337          290 :       CALL ensure_model_loaded(model_path, selected_cuda_device)
     338              : 
     339          290 :       IF (lsd) THEN
     340           48 :          needs%rho_spin = .TRUE.
     341           48 :          needs%drho_spin = .TRUE.
     342           48 :          needs%tau_spin = .TRUE.
     343              :       ELSE
     344          242 :          needs%rho = .TRUE.
     345          242 :          needs%drho = .TRUE.
     346          242 :          needs%tau = .TRUE.
     347              :       END IF
     348              : 
     349          290 :       CALL section_vals_val_get(xc_section, "XC_GRID%XC_DERIV", i_val=xc_deriv_method_id)
     350          290 :       CALL section_vals_val_get(xc_section, "XC_GRID%XC_SMOOTH_RHO", i_val=xc_rho_smooth_id)
     351              : 
     352              :       CALL xc_rho_set_create(rho_set, &
     353              :                              rho_r(1)%pw_grid%bounds_local, &
     354              :                              rho_cutoff=section_get_rval(xc_section, "density_cutoff"), &
     355              :                              drho_cutoff=section_get_rval(xc_section, "gradient_cutoff"), &
     356          290 :                              tau_cutoff=section_get_rval(xc_section, "tau_cutoff"))
     357              :       CALL xc_rho_set_update(rho_set, rho_r, rho_g, tau, needs, &
     358          290 :                              xc_deriv_method_id, xc_rho_smooth_id, pw_pool)
     359              : 
     360              :       CALL skala_gpw_feature_build(features, rho_set, rho_r, particle_set, cell, &
     361              :                                    requires_grad=(.NOT. my_just_energy), weights=weights, &
     362              :                                    requires_coordinate_grad=(needs_atom_force .OR. compute_virial), &
     363              :                                    requires_stress_grad=compute_virial, &
     364              :                                    use_atom_chunks=native_grid_atom_chunks, &
     365              :                                    route_atom_chunks=native_grid_atom_chunk_routing, &
     366          520 :                                    atom_partition=native_grid_atom_partition)
     367          290 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_DIAGNOSTICS", l_val=native_grid_diagnostics)
     368          290 :       IF (native_grid_diagnostics) THEN
     369           24 :          CALL print_native_grid_diagnostics(features, rho_r(1)%pw_grid%para%group%mepos == 0)
     370              :       END IF
     371              : 
     372          290 :       IF (features%uses_atom_chunks .AND. native_grid_atom_chunk_max_rows == -1) THEN
     373            0 :          IF (native_grid_use_cuda) THEN
     374              :             native_grid_atom_chunk_max_rows = auto_atom_chunk_max_rows(features, &
     375            0 :                                                                        rho_r(1)%pw_grid%para%group)
     376              :          ELSE
     377            0 :             native_grid_atom_chunk_max_rows = 0
     378              :          END IF
     379              :       END IF
     380          290 :       IF (native_grid_diagnostics .AND. features%uses_atom_chunks .AND. &
     381              :           rho_r(1)%pw_grid%para%group%mepos == 0) THEN
     382            1 :          iw = cp_logger_get_default_io_unit()
     383            1 :          IF (iw > 0) THEN
     384              :             WRITE (UNIT=iw, FMT="(T2,A,1X,I0)") &
     385            1 :                "SKALA_GPW| Native grid atom chunk max rows", native_grid_atom_chunk_max_rows
     386              :          END IF
     387              :       END IF
     388          290 :       native_grid_atom_subchunks = 1
     389          290 :       IF (features%uses_atom_chunks .AND. native_grid_atom_chunk_max_rows > 0) THEN
     390            8 :          native_grid_atom_subchunks = skala_gpw_atom_subchunk_count(native_grid_atom_chunk_max_rows)
     391            8 :          CALL rho_r(1)%pw_grid%para%group%max(native_grid_atom_subchunks)
     392              :       END IF
     393          290 :       use_atom_subchunks = features%uses_atom_chunks .AND. native_grid_atom_subchunks > 1
     394          290 :       has_atom_chunk_work = .NOT. features%uses_atom_chunks .OR. features%chunk_feature_count > 0
     395          290 :       exc = 0.0_dp
     396          290 :       IF (use_atom_subchunks) THEN
     397              :          CALL evaluate_atom_subchunks(features, rho_r(1)%pw_grid%para%group, &
     398              :                                       native_grid_atom_chunk_max_rows, &
     399              :                                       compute_grads=(.NOT. my_just_energy), exc=exc, &
     400              :                                       density_grad=density_grad, grad_grad=grad_grad, &
     401            2 :                                       kin_grad=kin_grad, collapse_spin_grads=(nspins == 1))
     402          288 :       ELSE IF (has_atom_chunk_work) THEN
     403              :          CALL skala_torch_model_get_exc(cached_model, features%inputs, &
     404          288 :                                         features%grid_weights_t, exc_tensor, exc)
     405              :       END IF
     406          290 :       IF (features%uses_atom_chunks) CALL rho_r(1)%pw_grid%para%group%sum(exc)
     407              : 
     408          290 :       IF (.NOT. my_just_energy) THEN
     409          290 :          IF (.NOT. use_atom_subchunks) THEN
     410          288 :             IF (has_atom_chunk_work) THEN
     411          288 :                CALL timeset("skala_gpw_backward", phase_handle)
     412          288 :                CALL torch_tensor_backward_scalar(exc_tensor)
     413          288 :                CALL timestop(phase_handle)
     414              : 
     415          288 :                IF (compute_virial) THEN
     416           50 :                   IF (native_grid_diagnostics) virial_before = virial_xc
     417              :                   CALL build_weight_virial(virial_xc, features, exc, grid_weight_grad_t, &
     418              :                                            atomic_grid_weight_grad_t, &
     419              :                                            rho_r(1)%pw_grid%para%group%mepos == 0, &
     420           50 :                                            native_grid_diagnostics)
     421           50 :                   IF (native_grid_diagnostics) THEN
     422              :                      CALL print_virial_delta("weight-residual", virial_xc - virial_before, &
     423            0 :                                              rho_r(1)%pw_grid%para%group%mepos == 0)
     424              :                   END IF
     425              :                END IF
     426              :             END IF
     427              : 
     428          288 :             CALL timeset("skala_gpw_grad_fetch", phase_handle)
     429          288 :             IF (features%uses_atom_chunks) THEN
     430              :                CALL fetch_and_gather_atom_chunk_grads(features, rho_r(1)%pw_grid%para%group, &
     431            6 :                                                       density_grad, grad_grad, kin_grad)
     432              :             ELSE
     433          282 :                CALL fetch_local_feature_grads(features, density_grad, grad_grad, kin_grad)
     434              :             END IF
     435          288 :             CALL timestop(phase_handle)
     436              :          END IF
     437          290 :          IF (needs_atom_force) THEN
     438              :             CALL add_explicit_coordinate_force(atom_force, features, atom_coord_grad_t, &
     439           60 :                                                rho_r(1)%pw_grid%para%group%mepos == 0)
     440           60 :             IF (features%atom_partition == skala_gpw_atom_partition_smooth) THEN
     441              :                CALL add_smooth_partition_force(atom_force, features, particle_set, cell, rho_r, &
     442           60 :                                                grid_weight_grad_t, atomic_grid_weight_grad_t)
     443              :             END IF
     444              :             have_atom_coord_grad = .TRUE.
     445              :          END IF
     446              : 
     447          290 :          CALL timeset("skala_gpw_vxc_unpack", phase_handle)
     448          290 :          IF (compute_virial) THEN
     449           50 :             IF (native_grid_diagnostics) virial_before = virial_xc
     450           50 :             CALL build_virial_from_feature_grads(virial_xc, rho_set, rho_r, grad_grad)
     451           50 :             IF (native_grid_diagnostics) THEN
     452              :                CALL print_virial_delta("feature-gradient", virial_xc - virial_before, &
     453            0 :                                        rho_r(1)%pw_grid%para%group%mepos == 0)
     454            0 :                virial_before = virial_xc
     455              :             END IF
     456           50 :             IF (.NOT. have_atom_coord_grad) THEN
     457            0 :                CALL torch_tensor_grad(features%coarse_0_atomic_coords_t, atom_coord_grad_t)
     458            0 :                have_atom_coord_grad = .TRUE.
     459              :             END IF
     460              :             CALL build_static_coordinate_virial(virial_xc, features, atom_coord_grad_t, &
     461              :                                                 grid_coord_grad_t, &
     462              :                                                 rho_r(1)%pw_grid%para%group%mepos == 0, &
     463           50 :                                                 native_grid_diagnostics)
     464           50 :             IF (native_grid_diagnostics) THEN
     465              :                CALL print_virial_delta("static-coordinates", virial_xc - virial_before, &
     466            0 :                                        rho_r(1)%pw_grid%para%group%mepos == 0)
     467            0 :                virial_before = virial_xc
     468              :             END IF
     469           50 :             IF (features%atom_partition == skala_gpw_atom_partition_smooth) THEN
     470              :                CALL build_smooth_partition_virial(virial_xc, features, particle_set, cell, rho_r, &
     471           50 :                                                   grid_weight_grad_t, atomic_grid_weight_grad_t)
     472           50 :                IF (native_grid_diagnostics) THEN
     473              :                   CALL print_virial_delta("smooth-partition", virial_xc - virial_before, &
     474            0 :                                           rho_r(1)%pw_grid%para%group%mepos == 0)
     475              :                   virial_before = virial_xc
     476              :                END IF
     477              :             END IF
     478              :          END IF
     479              :          CALL build_vxc_from_feature_grads(vxc_rho, vxc_tau, rho_r, pw_pool, &
     480              :                                            density_grad, grad_grad, kin_grad, &
     481          290 :                                            xc_deriv_method_id)
     482          290 :          CALL timestop(phase_handle)
     483              : 
     484          290 :          CALL timeset("skala_gpw_grad_release", phase_handle)
     485          290 :          DEALLOCATE (density_grad, grad_grad, kin_grad)
     486          290 :          IF (have_atom_coord_grad) CALL torch_tensor_release(atom_coord_grad_t)
     487          290 :          CALL timestop(phase_handle)
     488              :       END IF
     489              : 
     490          290 :       CALL timeset("skala_gpw_cleanup", phase_handle)
     491          290 :       IF (.NOT. use_atom_subchunks .AND. has_atom_chunk_work) CALL torch_tensor_release(exc_tensor)
     492          290 :       CALL skala_gpw_feature_release(features)
     493          290 :       CALL xc_rho_set_release(rho_set, pw_pool=pw_pool)
     494          290 :       CALL torch_use_cuda(.TRUE.)
     495          290 :       CALL timestop(phase_handle)
     496              : 
     497         5800 :    END SUBROUTINE skala_gpw_eval
     498              : 
     499              : ! **************************************************************************************************
     500              : !> \brief Evaluate the native SKALA XC energy density on the CP2K PW grid.
     501              : !> \param exc_r ...
     502              : !> \param rho_r ...
     503              : !> \param rho_g ...
     504              : !> \param tau ...
     505              : !> \param xc_section ...
     506              : !> \param weights ...
     507              : !> \param pw_pool ...
     508              : !> \param particle_set ...
     509              : !> \param cell ...
     510              : ! **************************************************************************************************
     511            0 :    SUBROUTINE skala_gpw_exc_density(exc_r, rho_r, rho_g, tau, xc_section, weights, pw_pool, &
     512              :                                     particle_set, cell)
     513              :       TYPE(pw_r3d_rs_type), INTENT(INOUT)                :: exc_r
     514              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: rho_r
     515              :       TYPE(pw_c1d_gs_type), DIMENSION(:), POINTER        :: rho_g
     516              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: tau
     517              :       TYPE(section_vals_type), POINTER                   :: xc_section
     518              :       TYPE(pw_r3d_rs_type), POINTER                      :: weights
     519              :       TYPE(pw_pool_type), POINTER                        :: pw_pool
     520              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     521              :       TYPE(cell_type), POINTER                           :: cell
     522              : 
     523              :       CHARACTER(len=default_path_length)                 :: model_path
     524              :       INTEGER :: feature_pos, i, j, k, local_row, native_grid_atom_partition, &
     525              :          native_grid_cuda_device, nspins, row, selected_cuda_device, xc_deriv_method_id, &
     526              :          xc_rho_smooth_id
     527              :       LOGICAL                                            :: lsd, native_grid_atom_chunk_routing, &
     528              :                                                             native_grid_atom_chunks, &
     529              :                                                             native_grid_use_cuda
     530              :       REAL(KIND=dp)                                      :: local_exc
     531            0 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: exc_density
     532              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     533            0 :       TYPE(skala_gpw_feature_type)                       :: features
     534              :       TYPE(torch_tensor_type)                            :: exc_density_t
     535              :       TYPE(xc_rho_cflags_type)                           :: needs
     536              :       TYPE(xc_rho_set_type)                              :: rho_set
     537              : 
     538            0 :       CPASSERT(ASSOCIATED(rho_r))
     539            0 :       CPASSERT(ASSOCIATED(rho_g))
     540            0 :       CPASSERT(ASSOCIATED(tau))
     541            0 :       CALL pw_zero(exc_r)
     542              : 
     543            0 :       nspins = SIZE(rho_r)
     544            0 :       lsd = (nspins /= 1)
     545            0 :       CALL get_skala_model_path(xc_section, model_path)
     546            0 :       gauxc_section => get_gauxc_section(xc_section)
     547            0 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_USE_CUDA", l_val=native_grid_use_cuda)
     548              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_CUDA_DEVICE", &
     549            0 :                                 i_val=native_grid_cuda_device)
     550              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_CHUNKS", &
     551            0 :                                 l_val=native_grid_atom_chunks)
     552              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_CHUNK_ROUTING", &
     553            0 :                                 l_val=native_grid_atom_chunk_routing)
     554              :       native_grid_atom_chunks = .FALSE.
     555              :       native_grid_atom_chunk_routing = .FALSE.
     556              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_PARTITION", &
     557            0 :                                 i_val=native_grid_atom_partition)
     558            0 :       SELECT CASE (native_grid_atom_partition)
     559              :       CASE (1)
     560            0 :          native_grid_atom_partition = skala_gpw_atom_partition_hard
     561              :       CASE (2)
     562            0 :          native_grid_atom_partition = skala_gpw_atom_partition_smooth
     563              :       CASE DEFAULT
     564              :          CALL cp_abort(__LOCATION__, &
     565            0 :                        "Unknown GAUXC%NATIVE_GRID_ATOM_PARTITION value.")
     566              :       END SELECT
     567              : 
     568            0 :       CALL torch_use_cuda(native_grid_use_cuda)
     569              :       selected_cuda_device = configure_native_grid_cuda( &
     570            0 :                              native_grid_use_cuda, native_grid_cuda_device, rho_r(1)%pw_grid%para%group)
     571            0 :       CALL ensure_model_loaded(model_path, selected_cuda_device)
     572              : 
     573            0 :       IF (lsd) THEN
     574            0 :          needs%rho_spin = .TRUE.
     575            0 :          needs%drho_spin = .TRUE.
     576            0 :          needs%tau_spin = .TRUE.
     577              :       ELSE
     578            0 :          needs%rho = .TRUE.
     579            0 :          needs%drho = .TRUE.
     580            0 :          needs%tau = .TRUE.
     581              :       END IF
     582              : 
     583            0 :       CALL section_vals_val_get(xc_section, "XC_GRID%XC_DERIV", i_val=xc_deriv_method_id)
     584            0 :       CALL section_vals_val_get(xc_section, "XC_GRID%XC_SMOOTH_RHO", i_val=xc_rho_smooth_id)
     585              : 
     586              :       CALL xc_rho_set_create(rho_set, &
     587              :                              rho_r(1)%pw_grid%bounds_local, &
     588              :                              rho_cutoff=section_get_rval(xc_section, "density_cutoff"), &
     589              :                              drho_cutoff=section_get_rval(xc_section, "gradient_cutoff"), &
     590            0 :                              tau_cutoff=section_get_rval(xc_section, "tau_cutoff"))
     591              :       CALL xc_rho_set_update(rho_set, rho_r, rho_g, tau, needs, &
     592            0 :                              xc_deriv_method_id, xc_rho_smooth_id, pw_pool)
     593              : 
     594              :       CALL skala_gpw_feature_build(features, rho_set, rho_r, particle_set, cell, &
     595              :                                    requires_grad=.FALSE., weights=weights, &
     596              :                                    requires_coordinate_grad=.FALSE., &
     597              :                                    requires_stress_grad=.FALSE., &
     598              :                                    use_atom_chunks=.FALSE., route_atom_chunks=.FALSE., &
     599            0 :                                    atom_partition=native_grid_atom_partition)
     600            0 :       CALL skala_torch_model_get_exc_density(cached_model, features%inputs, exc_density_t)
     601            0 :       NULLIFY (exc_density)
     602            0 :       CALL torch_tensor_data_ptr(exc_density_t, exc_density)
     603              : 
     604            0 :       local_row = 0
     605            0 :       DO k = LBOUND(features%feature_index, 3), UBOUND(features%feature_index, 3)
     606            0 :          DO j = LBOUND(features%feature_index, 2), UBOUND(features%feature_index, 2)
     607            0 :             DO i = LBOUND(features%feature_index, 1), UBOUND(features%feature_index, 1)
     608            0 :                local_row = local_row + 1
     609            0 :                local_exc = 0.0_dp
     610            0 :                DO feature_pos = features%local_feature_offsets(local_row), &
     611            0 :                   features%local_feature_offsets(local_row + 1) - 1
     612            0 :                   row = features%local_feature_rows(feature_pos)
     613            0 :                   local_exc = local_exc + exc_density(row)*features%grid_weights(row)
     614              :                END DO
     615            0 :                exc_r%array(i, j, k) = local_exc/rho_r(1)%pw_grid%dvol
     616              :             END DO
     617              :          END DO
     618              :       END DO
     619            0 :       CPASSERT(local_row == features%nflat_local)
     620              : 
     621            0 :       CALL torch_tensor_release(exc_density_t)
     622            0 :       CALL skala_gpw_feature_release(features)
     623            0 :       CALL xc_rho_set_release(rho_set, pw_pool=pw_pool)
     624            0 :       CALL torch_use_cuda(.TRUE.)
     625              : 
     626            0 :    END SUBROUTINE skala_gpw_exc_density
     627              : 
     628              : ! **************************************************************************************************
     629              : !> \brief Evaluate SKALA on a GAPW one-center atomic grid.
     630              : !> \param xc_section ...
     631              : !> \param grid_atom ...
     632              : !> \param group ...
     633              : !> \param atom_coord ...
     634              : !> \param rho ...
     635              : !> \param drho ...
     636              : !> \param tau ...
     637              : !> \param weights ...
     638              : !> \param lsd ...
     639              : !> \param nspins ...
     640              : !> \param na ...
     641              : !> \param nr ...
     642              : !> \param exc ...
     643              : !> \param vxc ...
     644              : !> \param vxg ...
     645              : !> \param vtau ...
     646              : !> \param energy_only ...
     647              : !> \param atom_force ...
     648              : !> \param atom_virial ...
     649              : ! **************************************************************************************************
     650           32 :    SUBROUTINE skala_gapw_atom_vxc_of_r(xc_section, grid_atom, group, atom_coord, &
     651           32 :                                        rho, drho, tau, weights, lsd, nspins, na, nr, &
     652              :                                        exc, vxc, vxg, vtau, energy_only, atom_force, atom_virial)
     653              :       TYPE(section_vals_type), POINTER                   :: xc_section
     654              :       TYPE(grid_atom_type), POINTER                      :: grid_atom
     655              : 
     656              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
     657              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: atom_coord
     658              :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: rho, tau, vxc, vtau
     659              :       REAL(KIND=dp), DIMENSION(:, :, :, :), POINTER      :: drho, vxg
     660              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: weights
     661              :       LOGICAL, INTENT(IN)                                :: lsd
     662              :       INTEGER, INTENT(IN)                                :: nspins, na, nr
     663              :       REAL(KIND=dp), INTENT(OUT)                         :: exc
     664              :       LOGICAL, INTENT(IN), OPTIONAL                      :: energy_only
     665              :       REAL(KIND=dp), DIMENSION(3), INTENT(OUT), &
     666              :          OPTIONAL                                        :: atom_force
     667              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(OUT), &
     668              :          OPTIONAL                                        :: atom_virial
     669              : 
     670              :       CHARACTER(len=default_path_length)                 :: model_path
     671              :       INTEGER                                            :: ia, idir, ir, native_grid_cuda_device, &
     672              :                                                             jdir, nflat, row, selected_cuda_device
     673           32 :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:)     :: atomic_grid_sizes
     674           32 :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:, :)  :: atomic_grid_size_bound_shape
     675              :       LOGICAL                                            :: need_coord_grad, my_energy_only, native_grid_use_cuda
     676              :       REAL(KIND=dp)                                      :: tmp
     677           32 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: atomic_grid_weights, grid_weights
     678           32 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: coarse_0_atomic_coords, density, &
     679           32 :                                                             grid_coords, kin
     680           32 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: grad
     681           32 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: atom_coord_grad, density_grad, &
     682           32 :                                                             grid_coord_grad, kin_grad
     683           32 :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: grad_grad
     684              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     685              :       TYPE(torch_dict_type)                              :: inputs
     686              :       TYPE(torch_tensor_type)                            :: atomic_grid_size_bound_shape_t, &
     687              :                                                             atomic_grid_sizes_t, &
     688              :                                                             atomic_grid_weights_t, &
     689              :                                                             atom_coord_grad_t, &
     690              :                                                             coarse_0_atomic_coords_t, density_t, &
     691              :                                                             density_grad_t, exc_tensor, grad_t, &
     692              :                                                             grad_grad_t, grid_coord_grad_t, &
     693              :                                                             grid_coords_t, grid_weights_t, kin_t, &
     694              :                                                             kin_grad_t
     695              : 
     696            0 :       CPASSERT(ASSOCIATED(xc_section))
     697           32 :       CPASSERT(ASSOCIATED(grid_atom))
     698           32 :       CPASSERT(ASSOCIATED(rho))
     699           32 :       CPASSERT(ASSOCIATED(drho))
     700           32 :       CPASSERT(ASSOCIATED(tau))
     701              : 
     702           32 :       my_energy_only = .FALSE.
     703           32 :       IF (PRESENT(energy_only)) my_energy_only = energy_only
     704           32 :       need_coord_grad = PRESENT(atom_force) .OR. PRESENT(atom_virial)
     705           32 :       exc = 0.0_dp
     706           32 :       IF (PRESENT(atom_force)) atom_force = 0.0_dp
     707           32 :       IF (PRESENT(atom_virial)) atom_virial = 0.0_dp
     708           32 :       IF (.NOT. my_energy_only) THEN
     709        81664 :          vxc = 0.0_dp
     710       321664 :          vxg = 0.0_dp
     711        81664 :          vtau = 0.0_dp
     712              :       END IF
     713              : 
     714           32 :       CALL get_skala_model_path(xc_section, model_path)
     715           32 :       gauxc_section => get_gauxc_section(xc_section)
     716           32 :       CPASSERT(ASSOCIATED(gauxc_section))
     717           32 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_USE_CUDA", l_val=native_grid_use_cuda)
     718              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_CUDA_DEVICE", &
     719           32 :                                 i_val=native_grid_cuda_device)
     720           32 :       CALL torch_use_cuda(native_grid_use_cuda)
     721              :       selected_cuda_device = configure_native_grid_cuda( &
     722           32 :                              native_grid_use_cuda, native_grid_cuda_device, group)
     723           32 :       CALL ensure_model_loaded(model_path, selected_cuda_device)
     724              : 
     725           32 :       nflat = na*nr
     726              :       ALLOCATE (density(nflat, 2), grad(nflat, 3, 2), kin(nflat, 2), &
     727              :                 grid_coords(3, nflat), grid_weights(nflat), &
     728              :                 atomic_grid_weights(nflat), atomic_grid_sizes(1), &
     729          416 :                 coarse_0_atomic_coords(3, 1), atomic_grid_size_bound_shape(0, nflat))
     730           32 :       density = 0.0_dp
     731           32 :       grad = 0.0_dp
     732           32 :       kin = 0.0_dp
     733           32 :       grid_coords = 0.0_dp
     734           32 :       grid_weights = 0.0_dp
     735           32 :       atomic_grid_weights = 0.0_dp
     736           32 :       atomic_grid_sizes(1) = INT(nflat, KIND=int_8)
     737              :       atomic_grid_size_bound_shape = 0_int_8
     738          128 :       coarse_0_atomic_coords(:, 1) = atom_coord
     739              : 
     740              :       row = 0
     741         1632 :       DO ir = 1, nr
     742        81632 :          DO ia = 1, na
     743        80000 :             row = row + 1
     744              :             grid_coords(1, row) = atom_coord(1) + grid_atom%rad(ir)* &
     745        80000 :                                   grid_atom%sin_pol(ia)*grid_atom%cos_azi(ia)
     746              :             grid_coords(2, row) = atom_coord(2) + grid_atom%rad(ir)* &
     747        80000 :                                   grid_atom%sin_pol(ia)*grid_atom%sin_azi(ia)
     748        80000 :             grid_coords(3, row) = atom_coord(3) + grid_atom%rad(ir)*grid_atom%cos_pol(ia)
     749        80000 :             grid_weights(row) = weights(ia, ir)
     750        80000 :             atomic_grid_weights(row) = weights(ia, ir)
     751        81600 :             IF (nspins == 1) THEN
     752       240000 :                density(row, :) = 0.5_dp*rho(ia, ir, 1)
     753       320000 :                DO idir = 1, 3
     754       800000 :                   grad(row, idir, :) = 0.5_dp*drho(idir, ia, ir, 1)
     755              :                END DO
     756       240000 :                kin(row, :) = 0.5_dp*tau(ia, ir, 1)
     757              :             ELSE
     758            0 :                density(row, :) = rho(ia, ir, 1:2)
     759            0 :                DO idir = 1, 3
     760            0 :                   grad(row, idir, :) = drho(idir, ia, ir, 1:2)
     761              :                END DO
     762            0 :                kin(row, :) = tau(ia, ir, 1:2)
     763              :             END IF
     764              :          END DO
     765              :       END DO
     766              : 
     767           32 :       CALL torch_tensor_from_array(grid_coords_t, grid_coords)
     768           32 :       CALL torch_tensor_to_device_leaf(grid_coords_t, need_coord_grad)
     769           32 :       CALL torch_tensor_from_array(grid_weights_t, grid_weights)
     770           32 :       CALL torch_tensor_to_device_leaf(grid_weights_t, .FALSE.)
     771           32 :       CALL torch_tensor_from_array(atomic_grid_weights_t, atomic_grid_weights)
     772           32 :       CALL torch_tensor_to_device_leaf(atomic_grid_weights_t, .FALSE.)
     773           32 :       CALL torch_tensor_from_array(atomic_grid_sizes_t, atomic_grid_sizes)
     774           32 :       CALL torch_tensor_to_device_leaf(atomic_grid_sizes_t, .FALSE.)
     775              :       CALL torch_tensor_from_array(atomic_grid_size_bound_shape_t, &
     776           32 :                                    atomic_grid_size_bound_shape)
     777           32 :       CALL torch_tensor_to_device_leaf(atomic_grid_size_bound_shape_t, .FALSE.)
     778           32 :       CALL torch_tensor_from_array(coarse_0_atomic_coords_t, coarse_0_atomic_coords)
     779           32 :       CALL torch_tensor_to_device_leaf(coarse_0_atomic_coords_t, need_coord_grad)
     780           32 :       CALL torch_tensor_from_array(density_t, density)
     781           32 :       CALL torch_tensor_to_device_leaf(density_t,.NOT. my_energy_only)
     782           32 :       CALL torch_tensor_from_array(grad_t, grad)
     783           32 :       CALL torch_tensor_to_device_leaf(grad_t,.NOT. my_energy_only)
     784           32 :       CALL torch_tensor_from_array(kin_t, kin)
     785           32 :       CALL torch_tensor_to_device_leaf(kin_t,.NOT. my_energy_only)
     786              : 
     787           32 :       CALL torch_dict_create(inputs)
     788           32 :       CALL torch_dict_insert(inputs, "grid_coords", grid_coords_t)
     789           32 :       CALL torch_dict_insert(inputs, "grid_weights", grid_weights_t)
     790           32 :       CALL torch_dict_insert(inputs, "atomic_grid_weights", atomic_grid_weights_t)
     791           32 :       CALL torch_dict_insert(inputs, "atomic_grid_sizes", atomic_grid_sizes_t)
     792              :       CALL torch_dict_insert(inputs, "atomic_grid_size_bound_shape", &
     793           32 :                              atomic_grid_size_bound_shape_t)
     794           32 :       CALL torch_dict_insert(inputs, "density", density_t)
     795           32 :       CALL torch_dict_insert(inputs, "grad", grad_t)
     796           32 :       CALL torch_dict_insert(inputs, "kin", kin_t)
     797           32 :       CALL torch_dict_insert(inputs, "coarse_0_atomic_coords", coarse_0_atomic_coords_t)
     798              : 
     799           32 :       CALL skala_torch_model_get_exc(cached_model, inputs, grid_weights_t, exc_tensor, exc)
     800              : 
     801           32 :       IF (.NOT. my_energy_only) THEN
     802           32 :          NULLIFY (atom_coord_grad, density_grad, grad_grad, grid_coord_grad, kin_grad)
     803           32 :          CALL torch_tensor_backward_scalar(exc_tensor)
     804           32 :          IF (need_coord_grad) THEN
     805           32 :             CALL torch_tensor_grad(grid_coords_t, grid_coord_grad_t)
     806           32 :             CALL torch_tensor_grad(coarse_0_atomic_coords_t, atom_coord_grad_t)
     807           32 :             CALL torch_tensor_data_ptr(grid_coord_grad_t, grid_coord_grad)
     808           32 :             CALL torch_tensor_data_ptr(atom_coord_grad_t, atom_coord_grad)
     809           32 :             IF (PRESENT(atom_force)) THEN
     810          128 :                atom_force(:) = atom_coord_grad(:, 1)
     811        80032 :                DO row = 1, nflat
     812       320032 :                   atom_force(:) = atom_force(:) + grid_coord_grad(:, row)
     813              :                END DO
     814              :             END IF
     815           32 :             IF (PRESENT(atom_virial)) THEN
     816        80032 :                DO row = 1, nflat
     817       320032 :                   DO idir = 1, 3
     818      1040000 :                      DO jdir = 1, 3
     819       720000 :                         tmp = grid_coord_grad(idir, row)*coarse_0_atomic_coords(jdir, 1)
     820       960000 :                         atom_virial(idir, jdir) = atom_virial(idir, jdir) + tmp
     821              :                      END DO
     822              :                   END DO
     823              :                END DO
     824          128 :                DO idir = 1, 3
     825          416 :                   DO jdir = 1, 3
     826          288 :                      tmp = atom_coord_grad(idir, 1)*coarse_0_atomic_coords(jdir, 1)
     827          384 :                      atom_virial(idir, jdir) = atom_virial(idir, jdir) + tmp
     828              :                   END DO
     829              :                END DO
     830              :             END IF
     831              :          END IF
     832           32 :          CALL torch_tensor_grad(density_t, density_grad_t)
     833           32 :          CALL torch_tensor_grad(grad_t, grad_grad_t)
     834           32 :          CALL torch_tensor_grad(kin_t, kin_grad_t)
     835           32 :          CALL torch_tensor_data_ptr(density_grad_t, density_grad)
     836           32 :          CALL torch_tensor_data_ptr(grad_grad_t, grad_grad)
     837           32 :          CALL torch_tensor_data_ptr(kin_grad_t, kin_grad)
     838              : 
     839           32 :          row = 0
     840         1632 :          DO ir = 1, nr
     841        81632 :             DO ia = 1, na
     842        80000 :                row = row + 1
     843        81600 :                IF (lsd) THEN
     844            0 :                   vxc(ia, ir, 1:2) = density_grad(row, 1:2)
     845            0 :                   DO idir = 1, 3
     846            0 :                      vxg(idir, ia, ir, 1:2) = grad_grad(row, idir, 1:2)
     847              :                   END DO
     848            0 :                   vtau(ia, ir, 1:2) = kin_grad(row, 1:2)
     849              :                ELSE
     850        80000 :                   vxc(ia, ir, 1) = 0.5_dp*(density_grad(row, 1) + density_grad(row, 2))
     851       320000 :                   DO idir = 1, 3
     852              :                      vxg(idir, ia, ir, 1) = &
     853       320000 :                         0.5_dp*(grad_grad(row, idir, 1) + grad_grad(row, idir, 2))
     854              :                   END DO
     855        80000 :                   vtau(ia, ir, 1) = 0.5_dp*(kin_grad(row, 1) + kin_grad(row, 2))
     856              :                END IF
     857              :             END DO
     858              :          END DO
     859              : 
     860           32 :          CALL torch_tensor_release(density_grad_t)
     861           32 :          CALL torch_tensor_release(grad_grad_t)
     862           32 :          CALL torch_tensor_release(kin_grad_t)
     863           32 :          IF (need_coord_grad) THEN
     864           32 :             CALL torch_tensor_release(grid_coord_grad_t)
     865           32 :             CALL torch_tensor_release(atom_coord_grad_t)
     866              :          END IF
     867              :       END IF
     868              : 
     869           32 :       CALL torch_tensor_release(exc_tensor)
     870           32 :       CALL torch_tensor_release(density_t)
     871           32 :       CALL torch_tensor_release(grad_t)
     872           32 :       CALL torch_tensor_release(kin_t)
     873           32 :       CALL torch_tensor_release(grid_coords_t)
     874           32 :       CALL torch_tensor_release(grid_weights_t)
     875           32 :       CALL torch_tensor_release(atomic_grid_weights_t)
     876           32 :       CALL torch_tensor_release(atomic_grid_sizes_t)
     877           32 :       CALL torch_tensor_release(atomic_grid_size_bound_shape_t)
     878           32 :       CALL torch_tensor_release(coarse_0_atomic_coords_t)
     879           32 :       CALL torch_dict_release(inputs)
     880            0 :       DEALLOCATE (atomic_grid_size_bound_shape, atomic_grid_sizes, atomic_grid_weights, &
     881           32 :                   coarse_0_atomic_coords, density, grad, grid_coords, grid_weights, kin)
     882           32 :       CALL torch_use_cuda(.TRUE.)
     883              : 
     884           96 :    END SUBROUTINE skala_gapw_atom_vxc_of_r
     885              : 
     886              : ! **************************************************************************************************
     887              : !> \brief Add the explicit SKALA derivative with respect to atom-center coordinates.
     888              : !> \param atom_force ...
     889              : !> \param features ...
     890              : !> \param atom_coord_grad_t ...
     891              : !> \param root_rank ...
     892              : ! **************************************************************************************************
     893           60 :    SUBROUTINE add_explicit_coordinate_force(atom_force, features, atom_coord_grad_t, root_rank)
     894              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: atom_force
     895              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
     896              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: atom_coord_grad_t
     897              :       LOGICAL, INTENT(IN)                                :: root_rank
     898              : 
     899           60 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: atom_coord_grad
     900              : 
     901           60 :       NULLIFY (atom_coord_grad)
     902           60 :       CALL torch_tensor_grad(features%coarse_0_atomic_coords_t, atom_coord_grad_t)
     903           60 :       IF (root_rank) THEN
     904           30 :          CALL torch_tensor_data_ptr(atom_coord_grad_t, atom_coord_grad)
     905           30 :          CPASSERT(SIZE(atom_force, 1) == SIZE(atom_coord_grad, 1))
     906           30 :          CPASSERT(SIZE(atom_force, 2) == SIZE(atom_coord_grad, 2))
     907          270 :          atom_force(:, :) = atom_force(:, :) + atom_coord_grad(:, :)
     908              :       END IF
     909              : 
     910           60 :    END SUBROUTINE add_explicit_coordinate_force
     911              : 
     912              : ! **************************************************************************************************
     913              : !> \brief Add the force from SMOOTH native-grid atom partition weights.
     914              : !> \param atom_force ...
     915              : !> \param features ...
     916              : !> \param particle_set ...
     917              : !> \param cell ...
     918              : !> \param rho_r ...
     919              : !> \param grid_weight_grad_t ...
     920              : !> \param atomic_grid_weight_grad_t ...
     921              : ! **************************************************************************************************
     922           60 :    SUBROUTINE add_smooth_partition_force(atom_force, features, particle_set, cell, rho_r, &
     923              :                                          grid_weight_grad_t, atomic_grid_weight_grad_t)
     924              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: atom_force
     925              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
     926              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     927              :       TYPE(cell_type), POINTER                           :: cell
     928              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: rho_r
     929              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: grid_weight_grad_t, &
     930              :                                                             atomic_grid_weight_grad_t
     931              : 
     932              :       INTEGER                                            :: feature_begin, feature_end, feature_pos, &
     933              :                                                             i, iatom, j, jatom, k, local_row, &
     934              :                                                             natom, row
     935              :       INTEGER, DIMENSION(2, 3)                           :: bo
     936              :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: included
     937              :       REAL(KIND=dp)                                      :: base_weight, weight_grad
     938              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: weights
     939              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: atom_coords_pbc
     940              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: dweights_datom, dweights_dstrain
     941              :       REAL(KIND=dp), DIMENSION(3)                        :: grid_point
     942           60 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: atomic_grid_weight_grad, grid_weight_grad
     943              : 
     944           60 :       NULLIFY (atomic_grid_weight_grad, grid_weight_grad)
     945           60 :       CALL torch_tensor_grad(features%grid_weights_t, grid_weight_grad_t)
     946           60 :       CALL torch_tensor_grad(features%atomic_grid_weights_t, atomic_grid_weight_grad_t)
     947           60 :       CALL torch_tensor_data_ptr(grid_weight_grad_t, grid_weight_grad)
     948           60 :       CALL torch_tensor_data_ptr(atomic_grid_weight_grad_t, atomic_grid_weight_grad)
     949              : 
     950           60 :       natom = SIZE(particle_set)
     951           60 :       CPASSERT(SIZE(atom_force, 1) == 3)
     952           60 :       CPASSERT(SIZE(atom_force, 2) == natom)
     953              :       ALLOCATE (atom_coords_pbc(3, natom), included(natom), weights(natom), &
     954          720 :                 dweights_datom(3, natom, natom), dweights_dstrain(3, 3, natom))
     955          180 :       DO iatom = 1, natom
     956          180 :          atom_coords_pbc(:, iatom) = pbc(particle_set(iatom)%r, cell, positive_range=.TRUE.)
     957              :       END DO
     958              : 
     959          600 :       bo = rho_r(1)%pw_grid%bounds_local
     960           60 :       local_row = 0
     961         1308 :       DO k = bo(1, 3), bo(2, 3)
     962        28140 :          DO j = bo(1, 2), bo(2, 2)
     963       324264 :             DO i = bo(1, 1), bo(2, 1)
     964       296184 :                local_row = local_row + 1
     965      1184736 :                grid_point = native_grid_coordinate(rho_r(1)%pw_grid, [i, j, k])
     966              :                CALL skala_gpw_smooth_partition_derivatives(grid_point, atom_coords_pbc, cell, &
     967              :                                                            weights, included, dweights_datom, &
     968       296184 :                                                            dweights_dstrain)
     969       296184 :                feature_begin = features%local_feature_offsets(local_row)
     970       296184 :                feature_end = features%local_feature_offsets(local_row + 1) - 1
     971       888552 :                CPASSERT(feature_end - feature_begin + 1 == COUNT(included))
     972       296184 :                base_weight = 0.0_dp
     973       887144 :                DO feature_pos = feature_begin, feature_end
     974       590960 :                   row = features%local_feature_rows(feature_pos)
     975       887144 :                   base_weight = base_weight + features%grid_weights(row)
     976              :                END DO
     977              :                feature_pos = feature_begin
     978       888552 :                DO iatom = 1, natom
     979       592368 :                   IF (.NOT. included(iatom)) CYCLE
     980       590960 :                   row = features%local_feature_rows(feature_pos)
     981       590960 :                   weight_grad = grid_weight_grad(row)
     982      1772880 :                   DO jatom = 1, natom
     983              :                      atom_force(:, jatom) = atom_force(:, jatom) + &
     984              :                                             weight_grad*base_weight* &
     985      5318640 :                                             dweights_datom(:, jatom, iatom)
     986              :                   END DO
     987       888552 :                   feature_pos = feature_pos + 1
     988              :                END DO
     989       323016 :                CPASSERT(feature_pos == feature_end + 1)
     990              :             END DO
     991              :          END DO
     992              :       END DO
     993           60 :       CPASSERT(local_row == features%nflat_local)
     994              : 
     995           60 :       DEALLOCATE (atom_coords_pbc, dweights_datom, dweights_dstrain, included, weights)
     996           60 :       CALL torch_tensor_release(grid_weight_grad_t)
     997           60 :       CALL torch_tensor_release(atomic_grid_weight_grad_t)
     998              : 
     999           60 :    END SUBROUTINE add_smooth_partition_force
    1000              : 
    1001              : ! **************************************************************************************************
    1002              : !> \brief Add the virial from SMOOTH native-grid atom partition weights.
    1003              : !> \param virial_xc ...
    1004              : !> \param features ...
    1005              : !> \param particle_set ...
    1006              : !> \param cell ...
    1007              : !> \param rho_r ...
    1008              : !> \param grid_weight_grad_t ...
    1009              : !> \param atomic_grid_weight_grad_t ...
    1010              : ! **************************************************************************************************
    1011           50 :    SUBROUTINE build_smooth_partition_virial(virial_xc, features, particle_set, cell, rho_r, &
    1012              :                                             grid_weight_grad_t, atomic_grid_weight_grad_t)
    1013              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(INOUT)      :: virial_xc
    1014              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1015              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1016              :       TYPE(cell_type), POINTER                           :: cell
    1017              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: rho_r
    1018              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: grid_weight_grad_t, &
    1019              :                                                             atomic_grid_weight_grad_t
    1020              : 
    1021              :       INTEGER                                            :: feature_begin, feature_end, feature_pos, &
    1022              :                                                             i, iatom, idir, j, jdir, k, local_row, &
    1023              :                                                             natom, row
    1024              :       INTEGER, DIMENSION(2, 3)                           :: bo
    1025              :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: included
    1026              :       REAL(KIND=dp)                                      :: base_weight, tmp, weight_grad
    1027              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: weights
    1028              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: atom_coords_pbc
    1029              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: dweights_datom, dweights_dstrain
    1030              :       REAL(KIND=dp), DIMENSION(3)                        :: grid_point
    1031           50 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: atomic_grid_weight_grad, grid_weight_grad
    1032              : 
    1033           50 :       NULLIFY (atomic_grid_weight_grad, grid_weight_grad)
    1034           50 :       CALL torch_tensor_grad(features%grid_weights_t, grid_weight_grad_t)
    1035           50 :       CALL torch_tensor_grad(features%atomic_grid_weights_t, atomic_grid_weight_grad_t)
    1036           50 :       CALL torch_tensor_data_ptr(grid_weight_grad_t, grid_weight_grad)
    1037           50 :       CALL torch_tensor_data_ptr(atomic_grid_weight_grad_t, atomic_grid_weight_grad)
    1038              : 
    1039           50 :       natom = SIZE(particle_set)
    1040              :       ALLOCATE (atom_coords_pbc(3, natom), included(natom), weights(natom), &
    1041          600 :                 dweights_datom(3, natom, natom), dweights_dstrain(3, 3, natom))
    1042          150 :       DO iatom = 1, natom
    1043          150 :          atom_coords_pbc(:, iatom) = pbc(particle_set(iatom)%r, cell, positive_range=.TRUE.)
    1044              :       END DO
    1045              : 
    1046          500 :       bo = rho_r(1)%pw_grid%bounds_local
    1047           50 :       local_row = 0
    1048         1112 :       DO k = bo(1, 3), bo(2, 3)
    1049        24290 :          DO j = bo(1, 2), bo(2, 2)
    1050       282651 :             DO i = bo(1, 1), bo(2, 1)
    1051       258411 :                local_row = local_row + 1
    1052      1033644 :                grid_point = native_grid_coordinate(rho_r(1)%pw_grid, [i, j, k])
    1053              :                CALL skala_gpw_smooth_partition_derivatives(grid_point, atom_coords_pbc, cell, &
    1054              :                                                            weights, included, dweights_datom, &
    1055       258411 :                                                            dweights_dstrain)
    1056       258411 :                feature_begin = features%local_feature_offsets(local_row)
    1057       258411 :                feature_end = features%local_feature_offsets(local_row + 1) - 1
    1058       775233 :                CPASSERT(feature_end - feature_begin + 1 == COUNT(included))
    1059       258411 :                base_weight = 0.0_dp
    1060       774049 :                DO feature_pos = feature_begin, feature_end
    1061       515638 :                   row = features%local_feature_rows(feature_pos)
    1062       774049 :                   base_weight = base_weight + features%grid_weights(row)
    1063              :                END DO
    1064              :                feature_pos = feature_begin
    1065       775233 :                DO iatom = 1, natom
    1066       516822 :                   IF (.NOT. included(iatom)) CYCLE
    1067       515638 :                   row = features%local_feature_rows(feature_pos)
    1068       515638 :                   weight_grad = grid_weight_grad(row)
    1069      2062552 :                   DO idir = 1, 3
    1070      5156380 :                      DO jdir = 1, idir
    1071      3093828 :                         tmp = weight_grad*base_weight*dweights_dstrain(idir, jdir, iatom)
    1072      3093828 :                         virial_xc(jdir, idir) = virial_xc(jdir, idir) + tmp
    1073      4640742 :                         IF (idir /= jdir) virial_xc(idir, jdir) = virial_xc(idir, jdir) + tmp
    1074              :                      END DO
    1075              :                   END DO
    1076       775233 :                   feature_pos = feature_pos + 1
    1077              :                END DO
    1078       281589 :                CPASSERT(feature_pos == feature_end + 1)
    1079              :             END DO
    1080              :          END DO
    1081              :       END DO
    1082           50 :       CPASSERT(local_row == features%nflat_local)
    1083              : 
    1084           50 :       DEALLOCATE (atom_coords_pbc, dweights_datom, dweights_dstrain, included, weights)
    1085           50 :       CALL torch_tensor_release(grid_weight_grad_t)
    1086           50 :       CALL torch_tensor_release(atomic_grid_weight_grad_t)
    1087              : 
    1088           50 :    END SUBROUTINE build_smooth_partition_virial
    1089              : 
    1090              : ! **************************************************************************************************
    1091              : !> \brief Return the Cartesian coordinate of a regular GPW grid point.
    1092              : !> \param pw_grid ...
    1093              : !> \param index ...
    1094              : !> \return ...
    1095              : ! **************************************************************************************************
    1096       554595 :    FUNCTION native_grid_coordinate(pw_grid, index) RESULT(coord)
    1097              :       TYPE(pw_grid_type), POINTER                        :: pw_grid
    1098              :       INTEGER, DIMENSION(3), INTENT(IN)                  :: index
    1099              :       REAL(KIND=dp), DIMENSION(3)                        :: coord
    1100              : 
    1101              :       INTEGER, DIMENSION(3)                              :: relative_index
    1102              : 
    1103      2218380 :       relative_index = index - pw_grid%bounds(1, :)
    1104              :       coord = REAL(relative_index(1), KIND=dp)*pw_grid%dh(:, 1) + &
    1105              :               REAL(relative_index(2), KIND=dp)*pw_grid%dh(:, 2) + &
    1106      2218380 :               REAL(relative_index(3), KIND=dp)*pw_grid%dh(:, 3)
    1107              : 
    1108       554595 :    END FUNCTION native_grid_coordinate
    1109              : 
    1110              : ! **************************************************************************************************
    1111              : !> \brief Evaluate a rank-local atom chunk as multiple atom-contiguous Torch subchunks.
    1112              : !> \param features ...
    1113              : !> \param group ...
    1114              : !> \param max_rows ...
    1115              : !> \param compute_grads ...
    1116              : !> \param exc ...
    1117              : !> \param density_grad ...
    1118              : !> \param grad_grad ...
    1119              : !> \param kin_grad ...
    1120              : !> \param collapse_spin_grads ...
    1121              : ! **************************************************************************************************
    1122            2 :    SUBROUTINE evaluate_atom_subchunks(features, group, max_rows, compute_grads, exc, &
    1123              :                                       density_grad, grad_grad, kin_grad, collapse_spin_grads)
    1124              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1125              : 
    1126              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    1127              :       INTEGER, INTENT(IN)                                :: max_rows
    1128              :       LOGICAL, INTENT(IN)                                :: compute_grads, collapse_spin_grads
    1129              :       REAL(KIND=dp), INTENT(OUT)                         :: exc
    1130              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    1131              :          INTENT(OUT)                                     :: density_grad, kin_grad
    1132              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
    1133              :          INTENT(OUT)                                     :: grad_grad
    1134              : 
    1135              :       INTEGER                                            :: base, isubchunk, local_row, nflat_local, &
    1136              :                                                             nroute_grad_per_point, nroute_points, &
    1137              :                                                             nsubchunks, phase_handle, point_pos, &
    1138              :                                                             subphase_handle
    1139            2 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: route_grad_return_recv_counts, &
    1140            2 :                                                             route_grad_return_recv_displs, &
    1141            2 :                                                             route_grad_return_send_counts, &
    1142            2 :                                                             route_grad_return_send_displs
    1143              :       REAL(KIND=dp)                                      :: subchunk_exc
    1144            2 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: recv_grad_buffer, send_grad_buffer
    1145            2 :       TYPE(skala_gpw_feature_type)                       :: subchunk
    1146              :       TYPE(torch_tensor_type)                            :: subchunk_exc_tensor
    1147              : 
    1148            0 :       CPASSERT(features%uses_atom_chunks)
    1149            2 :       CPASSERT(max_rows > 0)
    1150            2 :       nflat_local = features%nflat_local
    1151            2 :       nsubchunks = skala_gpw_atom_subchunk_count(max_rows)
    1152              : 
    1153            2 :       exc = 0.0_dp
    1154            2 :       IF (compute_grads) THEN
    1155            2 :          CPASSERT(features%uses_atom_chunk_routing)
    1156            6 :          CPASSERT(SUM(features%route_point_recv_counts) == features%chunk_feature_count)
    1157            2 :          nroute_points = SIZE(features%route_send_local_rows)
    1158            6 :          CPASSERT(SUM(features%route_point_send_counts) == nroute_points)
    1159            2 :          nroute_grad_per_point = ngrad_per_point
    1160            2 :          IF (collapse_spin_grads) nroute_grad_per_point = ncollapsed_grad_per_point
    1161              :          ALLOCATE (send_grad_buffer(MAX(1, nroute_grad_per_point*features%chunk_feature_count)), &
    1162              :                    recv_grad_buffer(MAX(1, nroute_grad_per_point*nroute_points)), &
    1163              :                    route_grad_return_send_counts(SIZE(features%route_point_recv_counts)), &
    1164              :                    route_grad_return_send_displs(SIZE(features%route_point_recv_displs)), &
    1165              :                    route_grad_return_recv_counts(SIZE(features%route_point_send_counts)), &
    1166           26 :                    route_grad_return_recv_displs(SIZE(features%route_point_send_displs)))
    1167              :          route_grad_return_send_counts(:) = &
    1168            6 :             nroute_grad_per_point*features%route_point_recv_counts
    1169              :          route_grad_return_send_displs(:) = &
    1170            6 :             nroute_grad_per_point*features%route_point_recv_displs
    1171              :          route_grad_return_recv_counts(:) = &
    1172            6 :             nroute_grad_per_point*features%route_point_send_counts
    1173              :          route_grad_return_recv_displs(:) = &
    1174            6 :             nroute_grad_per_point*features%route_point_send_displs
    1175              :       END IF
    1176              : 
    1177            2 :       CALL timeset("skala_gpw_atom_subchunks", phase_handle)
    1178            6 :       DO isubchunk = 1, nsubchunks
    1179            4 :          CALL timeset("skala_gpw_atom_subchunk_build", subphase_handle)
    1180              :          CALL skala_gpw_feature_build_atom_subchunk(features, subchunk, isubchunk, &
    1181            4 :                                                     max_rows, compute_grads)
    1182            4 :          CALL timestop(subphase_handle)
    1183            4 :          CALL timeset("skala_gpw_atom_subchunk_forward", subphase_handle)
    1184              :          CALL skala_torch_model_get_exc(cached_model, subchunk%inputs, &
    1185              :                                         subchunk%grid_weights_t, subchunk_exc_tensor, &
    1186            4 :                                         subchunk_exc)
    1187            4 :          CALL timestop(subphase_handle)
    1188            4 :          exc = exc + subchunk_exc
    1189            4 :          IF (compute_grads) THEN
    1190            4 :             CALL timeset("skala_gpw_atom_subchunk_backward", subphase_handle)
    1191            4 :             CALL torch_tensor_backward_scalar(subchunk_exc_tensor)
    1192            4 :             CALL timestop(subphase_handle)
    1193              :          END IF
    1194            4 :          CALL timeset("skala_gpw_atom_subchunk_release", subphase_handle)
    1195            4 :          CALL torch_tensor_release(subchunk_exc_tensor)
    1196            4 :          CALL skala_gpw_feature_release(subchunk)
    1197           18 :          CALL timestop(subphase_handle)
    1198              :       END DO
    1199            2 :       IF (compute_grads .AND. features%chunk_feature_count > 0) THEN
    1200            2 :          CALL timeset("skala_gpw_atom_subchunk_grad_pack", subphase_handle)
    1201            2 :          CALL pack_atom_chunk_grads(features, send_grad_buffer, .TRUE., collapse_spin_grads)
    1202            2 :          CALL timestop(subphase_handle)
    1203              :       END IF
    1204            2 :       CALL timestop(phase_handle)
    1205              : 
    1206            2 :       IF (compute_grads) THEN
    1207            2 :          CALL timeset("skala_gpw_grad_route_comm", phase_handle)
    1208              :          CALL group%alltoall(send_grad_buffer, route_grad_return_send_counts, &
    1209              :                              route_grad_return_send_displs, recv_grad_buffer, &
    1210            2 :                              route_grad_return_recv_counts, route_grad_return_recv_displs)
    1211            2 :          CALL timestop(phase_handle)
    1212              : 
    1213            2 :          CALL timeset("skala_gpw_grad_route_scatter", phase_handle)
    1214            0 :          ALLOCATE (density_grad(nflat_local, 2), grad_grad(nflat_local, 3, 2), &
    1215           14 :                    kin_grad(nflat_local, 2))
    1216            2 :          density_grad = 0.0_dp
    1217            2 :          grad_grad = 0.0_dp
    1218            2 :          kin_grad = 0.0_dp
    1219        64002 :          DO point_pos = 1, nroute_points
    1220        64000 :             local_row = features%route_send_local_rows(point_pos)
    1221        64000 :             CPASSERT(local_row >= 1 .AND. local_row <= nflat_local)
    1222        64000 :             base = nroute_grad_per_point*(point_pos - 1)
    1223        64002 :             IF (collapse_spin_grads) THEN
    1224              :                density_grad(local_row, :) = density_grad(local_row, :) + &
    1225       192000 :                                             recv_grad_buffer(base + 1)
    1226              :                grad_grad(local_row, 1, :) = grad_grad(local_row, 1, :) + &
    1227       192000 :                                             recv_grad_buffer(base + 2)
    1228              :                grad_grad(local_row, 2, :) = grad_grad(local_row, 2, :) + &
    1229       192000 :                                             recv_grad_buffer(base + 3)
    1230              :                grad_grad(local_row, 3, :) = grad_grad(local_row, 3, :) + &
    1231       192000 :                                             recv_grad_buffer(base + 4)
    1232       192000 :                kin_grad(local_row, :) = kin_grad(local_row, :) + recv_grad_buffer(base + 5)
    1233              :             ELSE
    1234              :                density_grad(local_row, :) = density_grad(local_row, :) + &
    1235            0 :                                             recv_grad_buffer(base + 1:base + 2)
    1236              :                grad_grad(local_row, 1, 1) = grad_grad(local_row, 1, 1) + &
    1237            0 :                                             recv_grad_buffer(base + 3)
    1238              :                grad_grad(local_row, 2, 1) = grad_grad(local_row, 2, 1) + &
    1239            0 :                                             recv_grad_buffer(base + 4)
    1240              :                grad_grad(local_row, 3, 1) = grad_grad(local_row, 3, 1) + &
    1241            0 :                                             recv_grad_buffer(base + 5)
    1242              :                grad_grad(local_row, 1, 2) = grad_grad(local_row, 1, 2) + &
    1243            0 :                                             recv_grad_buffer(base + 6)
    1244              :                grad_grad(local_row, 2, 2) = grad_grad(local_row, 2, 2) + &
    1245            0 :                                             recv_grad_buffer(base + 7)
    1246              :                grad_grad(local_row, 3, 2) = grad_grad(local_row, 3, 2) + &
    1247            0 :                                             recv_grad_buffer(base + 8)
    1248              :                kin_grad(local_row, :) = kin_grad(local_row, :) + &
    1249            0 :                                         recv_grad_buffer(base + 9:base + 10)
    1250              :             END IF
    1251              :          END DO
    1252            2 :          CALL timestop(phase_handle)
    1253              : 
    1254            0 :          DEALLOCATE (recv_grad_buffer, route_grad_return_recv_counts, &
    1255            0 :                      route_grad_return_recv_displs, route_grad_return_send_counts, &
    1256            6 :                      route_grad_return_send_displs, send_grad_buffer)
    1257              :       END IF
    1258              : 
    1259            4 :    END SUBROUTINE evaluate_atom_subchunks
    1260              : 
    1261              : ! **************************************************************************************************
    1262              : !> \brief Select an automatic CUDA atom-subchunk row cap.
    1263              : !> \param features ...
    1264              : !> \param group ...
    1265              : !> \return ...
    1266              : ! **************************************************************************************************
    1267            0 :    FUNCTION auto_atom_chunk_max_rows(features, group) RESULT(max_rows)
    1268              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1269              : 
    1270              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    1271              :       INTEGER                                            :: max_rows
    1272              : 
    1273              :       INTEGER                                            :: local_rows_max, target_rows
    1274              : 
    1275            0 :       local_rows_max = features%chunk_feature_count
    1276            0 :       CALL group%max(local_rows_max)
    1277            0 :       IF (local_rows_max <= 0) THEN
    1278            0 :          max_rows = 0
    1279              :          RETURN
    1280              :       END IF
    1281              : 
    1282            0 :       IF (group%num_pe > 1) THEN
    1283            0 :          target_rows = CEILING(REAL(local_rows_max, KIND=dp)/2.0_dp)
    1284              :          max_rows = atom_chunk_auto_row_quantum* &
    1285            0 :                     ((target_rows + atom_chunk_auto_row_quantum - 1)/atom_chunk_auto_row_quantum)
    1286              :       ELSE
    1287            0 :          target_rows = NINT(REAL(local_rows_max, KIND=dp)/4.0_dp)
    1288              :          max_rows = atom_chunk_auto_row_quantum* &
    1289              :                     MAX(1, NINT(REAL(target_rows, KIND=dp)/ &
    1290            0 :                                 REAL(atom_chunk_auto_row_quantum, KIND=dp)))
    1291              :       END IF
    1292            0 :       max_rows = MAX(atom_chunk_auto_min_rows, MIN(atom_chunk_auto_max_rows, max_rows))
    1293              : 
    1294            0 :    END FUNCTION auto_atom_chunk_max_rows
    1295              : 
    1296              : ! **************************************************************************************************
    1297              : !> \brief Map full Torch feature gradients back to this rank's local grid order.
    1298              : !> \param features ...
    1299              : !> \param density_grad ...
    1300              : !> \param grad_grad ...
    1301              : !> \param kin_grad ...
    1302              : ! **************************************************************************************************
    1303          282 :    SUBROUTINE fetch_local_feature_grads(features, density_grad, grad_grad, kin_grad)
    1304              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1305              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    1306              :          INTENT(OUT)                                     :: density_grad
    1307              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
    1308              :          INTENT(OUT)                                     :: grad_grad
    1309              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    1310              :          INTENT(OUT)                                     :: kin_grad
    1311              : 
    1312              :       INTEGER                                            :: feature_pos, i, j, k, local_row, row
    1313          282 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: density_grad_all, kin_grad_all
    1314          282 :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: grad_grad_all
    1315              :       TYPE(torch_tensor_type)                            :: density_grad_t, grad_grad_t, kin_grad_t
    1316              : 
    1317          282 :       NULLIFY (density_grad_all, grad_grad_all, kin_grad_all)
    1318              :       CALL get_feature_grad_views(features, density_grad_t, grad_grad_t, kin_grad_t, &
    1319          282 :                                   density_grad_all, grad_grad_all, kin_grad_all)
    1320          282 :       CPASSERT(SIZE(density_grad_all, 1) == features%nflat)
    1321          282 :       CPASSERT(SIZE(density_grad_all, 2) == 2)
    1322          282 :       CPASSERT(SIZE(grad_grad_all, 1) == features%nflat)
    1323          282 :       CPASSERT(SIZE(grad_grad_all, 2) == 3)
    1324          282 :       CPASSERT(SIZE(grad_grad_all, 3) == 2)
    1325          282 :       CPASSERT(SIZE(kin_grad_all, 1) == features%nflat)
    1326          282 :       CPASSERT(SIZE(kin_grad_all, 2) == 2)
    1327              : 
    1328            0 :       ALLOCATE (density_grad(features%nflat_local, 2), &
    1329            0 :                 grad_grad(features%nflat_local, 3, 2), &
    1330         1974 :                 kin_grad(features%nflat_local, 2))
    1331          282 :       density_grad = 0.0_dp
    1332          282 :       grad_grad = 0.0_dp
    1333          282 :       kin_grad = 0.0_dp
    1334          282 :       local_row = 0
    1335         6408 :       DO k = LBOUND(features%feature_index, 3), UBOUND(features%feature_index, 3)
    1336       144114 :          DO j = LBOUND(features%feature_index, 2), UBOUND(features%feature_index, 2)
    1337      2124981 :             DO i = LBOUND(features%feature_index, 1), UBOUND(features%feature_index, 1)
    1338      1737981 :                local_row = local_row + 1
    1339      4286402 :                DO feature_pos = features%local_feature_offsets(local_row), &
    1340      1865127 :                   features%local_feature_offsets(local_row + 1) - 1
    1341      2548421 :                   row = features%local_feature_rows(feature_pos)
    1342      2548421 :                   CPASSERT(row >= 1 .AND. row <= features%nflat)
    1343              :                   density_grad(local_row, :) = density_grad(local_row, :) + &
    1344      7645263 :                                                density_grad_all(row, :)
    1345              :                   grad_grad(local_row, :, :) = grad_grad(local_row, :, :) + &
    1346     22935789 :                                                grad_grad_all(row, :, :)
    1347      9383244 :                   kin_grad(local_row, :) = kin_grad(local_row, :) + kin_grad_all(row, :)
    1348              :                END DO
    1349              :             END DO
    1350              :          END DO
    1351              :       END DO
    1352          282 :       CPASSERT(local_row == features%nflat_local)
    1353              : 
    1354          282 :       CALL torch_tensor_release(density_grad_t)
    1355          282 :       CALL torch_tensor_release(grad_grad_t)
    1356          282 :       CALL torch_tensor_release(kin_grad_t)
    1357              : 
    1358          282 :    END SUBROUTINE fetch_local_feature_grads
    1359              : 
    1360              : ! **************************************************************************************************
    1361              : !> \brief Pack atom-chunk Torch gradients into CP2K communication buffers.
    1362              : !> \param features ...
    1363              : !> \param TARGET ...
    1364              : !> \param route_to_return_positions ...
    1365              : !> \param collapse_spin_grads ...
    1366              : ! **************************************************************************************************
    1367            8 :    SUBROUTINE pack_atom_chunk_grads(features, TARGET, route_to_return_positions, &
    1368              :                                     collapse_spin_grads)
    1369              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1370              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
    1371              :          INTENT(INOUT)                                   :: target
    1372              :       LOGICAL, INTENT(IN)                                :: route_to_return_positions
    1373              :       LOGICAL, INTENT(IN), OPTIONAL                      :: collapse_spin_grads
    1374              : 
    1375              :       INTEGER                                            :: base, irow, ngrad_buffer_per_point, &
    1376              :                                                             point_pos, target_points
    1377              :       LOGICAL                                            :: my_collapse_spin_grads
    1378            8 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: chunk_density_grad, chunk_kin_grad
    1379            8 :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: chunk_grad_grad
    1380              :       TYPE(torch_tensor_type)                            :: density_grad_t, grad_grad_t, kin_grad_t
    1381              : 
    1382            8 :       my_collapse_spin_grads = .FALSE.
    1383           16 :       IF (PRESENT(collapse_spin_grads)) my_collapse_spin_grads = collapse_spin_grads
    1384            8 :       ngrad_buffer_per_point = ngrad_per_point
    1385            8 :       IF (my_collapse_spin_grads) ngrad_buffer_per_point = ncollapsed_grad_per_point
    1386              : 
    1387            8 :       NULLIFY (chunk_density_grad, chunk_grad_grad, chunk_kin_grad)
    1388              :       CALL get_feature_grad_views(features, density_grad_t, grad_grad_t, kin_grad_t, &
    1389            8 :                                   chunk_density_grad, chunk_grad_grad, chunk_kin_grad)
    1390            8 :       CPASSERT(MOD(SIZE(TARGET), ngrad_buffer_per_point) == 0)
    1391            8 :       target_points = SIZE(TARGET)/ngrad_buffer_per_point
    1392            8 :       CPASSERT(target_points >= features%chunk_feature_count)
    1393            8 :       CPASSERT(SIZE(chunk_density_grad, 1) == features%chunk_feature_count)
    1394            8 :       CPASSERT(SIZE(chunk_grad_grad, 1) == features%chunk_feature_count)
    1395            8 :       CPASSERT(SIZE(chunk_grad_grad, 2) == 3)
    1396            8 :       CPASSERT(SIZE(chunk_kin_grad, 1) == features%chunk_feature_count)
    1397            8 :       IF (features%uses_collapsed_rks_dynamic) THEN
    1398            8 :          CPASSERT(my_collapse_spin_grads)
    1399            8 :          CPASSERT(SIZE(chunk_density_grad, 2) == 1)
    1400            8 :          CPASSERT(SIZE(chunk_grad_grad, 3) == 1)
    1401            8 :          CPASSERT(SIZE(chunk_kin_grad, 2) == 1)
    1402              :       ELSE
    1403            0 :          CPASSERT(SIZE(chunk_density_grad, 2) == 2)
    1404            0 :          CPASSERT(SIZE(chunk_grad_grad, 3) == 2)
    1405            0 :          CPASSERT(SIZE(chunk_kin_grad, 2) == 2)
    1406              :       END IF
    1407              : 
    1408       146742 :       DO irow = 1, features%chunk_feature_count
    1409       146734 :          IF (route_to_return_positions) THEN
    1410       146734 :             point_pos = features%chunk_return_positions(irow)
    1411       146734 :             CPASSERT(point_pos >= 1 .AND. point_pos <= target_points)
    1412              :          ELSE
    1413              :             point_pos = irow
    1414              :          END IF
    1415       146734 :          base = ngrad_buffer_per_point*(point_pos - 1)
    1416       146742 :          IF (my_collapse_spin_grads) THEN
    1417       146734 :             IF (features%uses_collapsed_rks_dynamic) THEN
    1418       146734 :                TARGET(base + 1) = 0.5_dp*chunk_density_grad(irow, 1)
    1419       146734 :                TARGET(base + 2) = 0.5_dp*chunk_grad_grad(irow, 1, 1)
    1420       146734 :                TARGET(base + 3) = 0.5_dp*chunk_grad_grad(irow, 2, 1)
    1421       146734 :                TARGET(base + 4) = 0.5_dp*chunk_grad_grad(irow, 3, 1)
    1422       146734 :                TARGET(base + 5) = 0.5_dp*chunk_kin_grad(irow, 1)
    1423              :             ELSE
    1424              :                TARGET(base + 1) = 0.5_dp*(chunk_density_grad(irow, 1) + &
    1425            0 :                                           chunk_density_grad(irow, 2))
    1426              :                TARGET(base + 2) = 0.5_dp*(chunk_grad_grad(irow, 1, 1) + &
    1427            0 :                                           chunk_grad_grad(irow, 1, 2))
    1428              :                TARGET(base + 3) = 0.5_dp*(chunk_grad_grad(irow, 2, 1) + &
    1429            0 :                                           chunk_grad_grad(irow, 2, 2))
    1430              :                TARGET(base + 4) = 0.5_dp*(chunk_grad_grad(irow, 3, 1) + &
    1431            0 :                                           chunk_grad_grad(irow, 3, 2))
    1432            0 :                TARGET(base + 5) = 0.5_dp*(chunk_kin_grad(irow, 1) + chunk_kin_grad(irow, 2))
    1433              :             END IF
    1434              :          ELSE
    1435            0 :             TARGET(base + 1:base + 2) = chunk_density_grad(irow, :)
    1436            0 :             TARGET(base + 3) = chunk_grad_grad(irow, 1, 1)
    1437            0 :             TARGET(base + 4) = chunk_grad_grad(irow, 2, 1)
    1438            0 :             TARGET(base + 5) = chunk_grad_grad(irow, 3, 1)
    1439            0 :             TARGET(base + 6) = chunk_grad_grad(irow, 1, 2)
    1440            0 :             TARGET(base + 7) = chunk_grad_grad(irow, 2, 2)
    1441            0 :             TARGET(base + 8) = chunk_grad_grad(irow, 3, 2)
    1442            0 :             TARGET(base + 9:base + 10) = chunk_kin_grad(irow, :)
    1443              :          END IF
    1444              :       END DO
    1445              : 
    1446            8 :       CALL torch_tensor_release(density_grad_t)
    1447            8 :       CALL torch_tensor_release(grad_grad_t)
    1448            8 :       CALL torch_tensor_release(kin_grad_t)
    1449              : 
    1450            8 :    END SUBROUTINE pack_atom_chunk_grads
    1451              : 
    1452              : ! **************************************************************************************************
    1453              : !> \brief Return CPU views of autograd outputs for the SKALA dynamic feature tensors.
    1454              : !> \param features ...
    1455              : !> \param density_grad_t ...
    1456              : !> \param grad_grad_t ...
    1457              : !> \param kin_grad_t ...
    1458              : !> \param density_grad ...
    1459              : !> \param grad_grad ...
    1460              : !> \param kin_grad ...
    1461              : ! **************************************************************************************************
    1462          290 :    SUBROUTINE get_feature_grad_views(features, density_grad_t, grad_grad_t, kin_grad_t, &
    1463              :                                      density_grad, grad_grad, kin_grad)
    1464              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1465              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: density_grad_t, grad_grad_t, kin_grad_t
    1466              :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: density_grad
    1467              :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: grad_grad
    1468              :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: kin_grad
    1469              : 
    1470          290 :       NULLIFY (density_grad, grad_grad, kin_grad)
    1471          290 :       CALL torch_tensor_grad(features%density_t, density_grad_t)
    1472          290 :       CALL torch_tensor_grad(features%grad_t, grad_grad_t)
    1473          290 :       CALL torch_tensor_grad(features%kin_t, kin_grad_t)
    1474          290 :       CALL torch_tensor_data_ptr(density_grad_t, density_grad)
    1475          290 :       CALL torch_tensor_data_ptr(grad_grad_t, grad_grad)
    1476          290 :       CALL torch_tensor_data_ptr(kin_grad_t, kin_grad)
    1477              : 
    1478          290 :    END SUBROUTINE get_feature_grad_views
    1479              : 
    1480              : ! **************************************************************************************************
    1481              : !> \brief Fetch atom-chunk gradients and route them back to their local grid owners.
    1482              : !> \param features ...
    1483              : !> \param group ...
    1484              : !> \param density_grad ...
    1485              : !> \param grad_grad ...
    1486              : !> \param kin_grad ...
    1487              : ! **************************************************************************************************
    1488            6 :    SUBROUTINE fetch_and_gather_atom_chunk_grads(features, group, density_grad, grad_grad, &
    1489              :                                                 kin_grad)
    1490              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1491              : 
    1492              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    1493              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    1494              :          INTENT(OUT)                                     :: density_grad, kin_grad
    1495              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
    1496              :          INTENT(OUT)                                     :: grad_grad
    1497              : 
    1498              :       INTEGER                                            :: base, feature_pos, i, j, k, local_row, &
    1499              :                                                             nflat_local, nroute_grad_per_point, &
    1500              :                                                             nroute_points, phase_handle, point_pos, row
    1501            6 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: route_grad_return_recv_counts, &
    1502            6 :                                                             route_grad_return_recv_displs, &
    1503            6 :                                                             route_grad_return_send_counts, &
    1504            6 :                                                             route_grad_return_send_displs
    1505            6 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: chunk_grad_buffer, global_grad_buffer, &
    1506            6 :                                                             recv_grad_buffer, send_grad_buffer
    1507              : 
    1508            6 :       CPASSERT(features%uses_atom_chunks)
    1509              : 
    1510            6 :       nflat_local = features%nflat_local
    1511            6 :       IF (features%uses_atom_chunk_routing) THEN
    1512           18 :          CPASSERT(SUM(features%route_point_recv_counts) == features%chunk_feature_count)
    1513            6 :          nroute_points = SIZE(features%route_send_local_rows)
    1514           18 :          CPASSERT(SUM(features%route_point_send_counts) == nroute_points)
    1515              : 
    1516            6 :          nroute_grad_per_point = ngrad_per_point
    1517            6 :          IF (features%uses_collapsed_rks_dynamic) THEN
    1518            6 :             nroute_grad_per_point = ncollapsed_grad_per_point
    1519              :          END IF
    1520              :          ALLOCATE (send_grad_buffer(MAX(1, nroute_grad_per_point*features%chunk_feature_count)), &
    1521              :                    recv_grad_buffer(MAX(1, nroute_grad_per_point*nroute_points)), &
    1522              :                    route_grad_return_send_counts(SIZE(features%route_point_recv_counts)), &
    1523              :                    route_grad_return_send_displs(SIZE(features%route_point_recv_displs)), &
    1524              :                    route_grad_return_recv_counts(SIZE(features%route_point_send_counts)), &
    1525           78 :                    route_grad_return_recv_displs(SIZE(features%route_point_send_displs)))
    1526              :          route_grad_return_send_counts(:) = &
    1527           18 :             nroute_grad_per_point*features%route_point_recv_counts
    1528              :          route_grad_return_send_displs(:) = &
    1529           18 :             nroute_grad_per_point*features%route_point_recv_displs
    1530              :          route_grad_return_recv_counts(:) = &
    1531           18 :             nroute_grad_per_point*features%route_point_send_counts
    1532              :          route_grad_return_recv_displs(:) = &
    1533           18 :             nroute_grad_per_point*features%route_point_send_displs
    1534              : 
    1535            6 :          IF (features%chunk_feature_count > 0) THEN
    1536            6 :             CALL timeset("skala_gpw_grad_torch_pack", phase_handle)
    1537              :             CALL pack_atom_chunk_grads(features, send_grad_buffer, .TRUE., &
    1538            6 :                                        features%uses_collapsed_rks_dynamic)
    1539            6 :             CALL timestop(phase_handle)
    1540              :          END IF
    1541              : 
    1542            6 :          CALL timeset("skala_gpw_grad_route_comm", phase_handle)
    1543              :          CALL group%alltoall(send_grad_buffer, route_grad_return_send_counts, &
    1544              :                              route_grad_return_send_displs, recv_grad_buffer, &
    1545            6 :                              route_grad_return_recv_counts, route_grad_return_recv_displs)
    1546            6 :          CALL timestop(phase_handle)
    1547              : 
    1548            6 :          CALL timeset("skala_gpw_grad_route_scatter", phase_handle)
    1549            0 :          ALLOCATE (density_grad(nflat_local, 2), grad_grad(nflat_local, 3, 2), &
    1550           42 :                    kin_grad(nflat_local, 2))
    1551            6 :          density_grad = 0.0_dp
    1552            6 :          grad_grad = 0.0_dp
    1553            6 :          kin_grad = 0.0_dp
    1554        82740 :          DO point_pos = 1, nroute_points
    1555        82734 :             local_row = features%route_send_local_rows(point_pos)
    1556        82734 :             CPASSERT(local_row >= 1 .AND. local_row <= nflat_local)
    1557        82734 :             base = nroute_grad_per_point*(point_pos - 1)
    1558        82740 :             IF (features%uses_collapsed_rks_dynamic) THEN
    1559              :                density_grad(local_row, :) = density_grad(local_row, :) + &
    1560       248202 :                                             recv_grad_buffer(base + 1)
    1561              :                grad_grad(local_row, 1, :) = grad_grad(local_row, 1, :) + &
    1562       248202 :                                             recv_grad_buffer(base + 2)
    1563              :                grad_grad(local_row, 2, :) = grad_grad(local_row, 2, :) + &
    1564       248202 :                                             recv_grad_buffer(base + 3)
    1565              :                grad_grad(local_row, 3, :) = grad_grad(local_row, 3, :) + &
    1566       248202 :                                             recv_grad_buffer(base + 4)
    1567       248202 :                kin_grad(local_row, :) = kin_grad(local_row, :) + recv_grad_buffer(base + 5)
    1568              :             ELSE
    1569              :                density_grad(local_row, :) = density_grad(local_row, :) + &
    1570            0 :                                             recv_grad_buffer(base + 1:base + 2)
    1571              :                grad_grad(local_row, 1, 1) = grad_grad(local_row, 1, 1) + &
    1572            0 :                                             recv_grad_buffer(base + 3)
    1573              :                grad_grad(local_row, 2, 1) = grad_grad(local_row, 2, 1) + &
    1574            0 :                                             recv_grad_buffer(base + 4)
    1575              :                grad_grad(local_row, 3, 1) = grad_grad(local_row, 3, 1) + &
    1576            0 :                                             recv_grad_buffer(base + 5)
    1577              :                grad_grad(local_row, 1, 2) = grad_grad(local_row, 1, 2) + &
    1578            0 :                                             recv_grad_buffer(base + 6)
    1579              :                grad_grad(local_row, 2, 2) = grad_grad(local_row, 2, 2) + &
    1580            0 :                                             recv_grad_buffer(base + 7)
    1581              :                grad_grad(local_row, 3, 2) = grad_grad(local_row, 3, 2) + &
    1582            0 :                                             recv_grad_buffer(base + 8)
    1583              :                kin_grad(local_row, :) = kin_grad(local_row, :) + &
    1584            0 :                                         recv_grad_buffer(base + 9:base + 10)
    1585              :             END IF
    1586              :          END DO
    1587            6 :          CALL timestop(phase_handle)
    1588              : 
    1589            0 :          DEALLOCATE (recv_grad_buffer, route_grad_return_recv_counts, &
    1590            0 :                      route_grad_return_recv_displs, route_grad_return_send_counts, &
    1591           18 :                      route_grad_return_send_displs, send_grad_buffer)
    1592              :       ELSE
    1593              :          ALLOCATE (chunk_grad_buffer(MAX(1, ngrad_per_point*features%chunk_feature_count)), &
    1594            0 :                    global_grad_buffer(ngrad_per_point*features%nflat))
    1595            0 :          IF (features%chunk_feature_count > 0) THEN
    1596            0 :             CALL timeset("skala_gpw_grad_torch_pack", phase_handle)
    1597            0 :             CALL pack_atom_chunk_grads(features, chunk_grad_buffer, .FALSE.)
    1598            0 :             CALL timestop(phase_handle)
    1599              :          END IF
    1600              : 
    1601            0 :          CALL timeset("skala_gpw_grad_allgatherv", phase_handle)
    1602              :          CALL group%allgatherv(chunk_grad_buffer, global_grad_buffer, &
    1603            0 :                                features%chunk_grad_counts, features%chunk_grad_displs)
    1604            0 :          CALL timestop(phase_handle)
    1605              : 
    1606            0 :          CALL timeset("skala_gpw_grad_scatter", phase_handle)
    1607            0 :          ALLOCATE (density_grad(nflat_local, 2), grad_grad(nflat_local, 3, 2), &
    1608            0 :                    kin_grad(nflat_local, 2))
    1609            0 :          density_grad = 0.0_dp
    1610            0 :          grad_grad = 0.0_dp
    1611            0 :          kin_grad = 0.0_dp
    1612            0 :          local_row = 0
    1613            0 :          DO k = LBOUND(features%feature_index, 3), UBOUND(features%feature_index, 3)
    1614            0 :             DO j = LBOUND(features%feature_index, 2), UBOUND(features%feature_index, 2)
    1615            0 :                DO i = LBOUND(features%feature_index, 1), UBOUND(features%feature_index, 1)
    1616            0 :                   local_row = local_row + 1
    1617            0 :                   DO feature_pos = features%local_feature_offsets(local_row), &
    1618            0 :                      features%local_feature_offsets(local_row + 1) - 1
    1619            0 :                      row = features%local_feature_rows(feature_pos)
    1620            0 :                      CPASSERT(row >= 1 .AND. row <= features%nflat)
    1621            0 :                      base = ngrad_per_point*(row - 1)
    1622              :                      density_grad(local_row, :) = density_grad(local_row, :) + &
    1623            0 :                                                   global_grad_buffer(base + 1:base + 2)
    1624              :                      grad_grad(local_row, 1, 1) = grad_grad(local_row, 1, 1) + &
    1625            0 :                                                   global_grad_buffer(base + 3)
    1626              :                      grad_grad(local_row, 2, 1) = grad_grad(local_row, 2, 1) + &
    1627            0 :                                                   global_grad_buffer(base + 4)
    1628              :                      grad_grad(local_row, 3, 1) = grad_grad(local_row, 3, 1) + &
    1629            0 :                                                   global_grad_buffer(base + 5)
    1630              :                      grad_grad(local_row, 1, 2) = grad_grad(local_row, 1, 2) + &
    1631            0 :                                                   global_grad_buffer(base + 6)
    1632              :                      grad_grad(local_row, 2, 2) = grad_grad(local_row, 2, 2) + &
    1633            0 :                                                   global_grad_buffer(base + 7)
    1634              :                      grad_grad(local_row, 3, 2) = grad_grad(local_row, 3, 2) + &
    1635            0 :                                                   global_grad_buffer(base + 8)
    1636              :                      kin_grad(local_row, :) = kin_grad(local_row, :) + &
    1637            0 :                                               global_grad_buffer(base + 9:base + 10)
    1638              :                   END DO
    1639              :                END DO
    1640              :             END DO
    1641              :          END DO
    1642            0 :          CALL timestop(phase_handle)
    1643            0 :          DEALLOCATE (chunk_grad_buffer, global_grad_buffer)
    1644              : 
    1645              :       END IF
    1646              : 
    1647            6 :    END SUBROUTINE fetch_and_gather_atom_chunk_grads
    1648              : 
    1649              : ! **************************************************************************************************
    1650              : !> \brief Build the native SKALA XC virial from feature gradients.
    1651              : !> \param virial_xc ...
    1652              : !> \param rho_set ...
    1653              : !> \param rho_r ...
    1654              : !> \param grad_grad ...
    1655              : ! **************************************************************************************************
    1656           50 :    SUBROUTINE build_virial_from_feature_grads(virial_xc, rho_set, rho_r, grad_grad)
    1657              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(INOUT)      :: virial_xc
    1658              :       TYPE(xc_rho_set_type), INTENT(IN)                  :: rho_set
    1659              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: rho_r
    1660              :       REAL(KIND=dp), DIMENSION(:, :, :), INTENT(IN)      :: grad_grad
    1661              : 
    1662              :       INTEGER                                            :: i, idir, ipt, ispin, j, jdir, k, nspins
    1663              :       INTEGER, DIMENSION(2, 3)                           :: bo
    1664              :       REAL(KIND=dp)                                      :: grad_i, tmp
    1665          600 :       TYPE(cp_3d_r_cp_type), DIMENSION(3)                :: drho, drhoa, drhob
    1666              : 
    1667           50 :       nspins = SIZE(rho_r)
    1668          500 :       bo = rho_r(1)%pw_grid%bounds_local
    1669           50 :       ipt = 0
    1670              : 
    1671           50 :       IF (nspins == 1) THEN
    1672           50 :          CALL xc_rho_set_get(rho_set, drho=drho)
    1673         1112 :          DO k = bo(1, 3), bo(2, 3)
    1674        24290 :             DO j = bo(1, 2), bo(2, 2)
    1675       282651 :                DO i = bo(1, 1), bo(2, 1)
    1676       258411 :                   ipt = ipt + 1
    1677      1056822 :                   DO idir = 1, 3
    1678       775233 :                      grad_i = 0.5_dp*(grad_grad(ipt, idir, 1) + grad_grad(ipt, idir, 2))
    1679      2584110 :                      DO jdir = 1, idir
    1680      1550466 :                         tmp = -grad_i*drho(jdir)%array(i, j, k)
    1681      1550466 :                         virial_xc(jdir, idir) = virial_xc(jdir, idir) + tmp
    1682      2325699 :                         virial_xc(idir, jdir) = virial_xc(jdir, idir)
    1683              :                      END DO
    1684              :                   END DO
    1685              :                END DO
    1686              :             END DO
    1687              :          END DO
    1688              :       ELSE
    1689            0 :          CALL xc_rho_set_get(rho_set, drhoa=drhoa, drhob=drhob)
    1690            0 :          DO k = bo(1, 3), bo(2, 3)
    1691            0 :             DO j = bo(1, 2), bo(2, 2)
    1692            0 :                DO i = bo(1, 1), bo(2, 1)
    1693            0 :                   ipt = ipt + 1
    1694            0 :                   DO idir = 1, 3
    1695            0 :                      DO jdir = 1, idir
    1696              :                         tmp = 0.0_dp
    1697            0 :                         DO ispin = 1, 2
    1698            0 :                            IF (ispin == 1) THEN
    1699            0 :                               tmp = tmp - grad_grad(ipt, idir, ispin)*drhoa(jdir)%array(i, j, k)
    1700              :                            ELSE
    1701            0 :                               tmp = tmp - grad_grad(ipt, idir, ispin)*drhob(jdir)%array(i, j, k)
    1702              :                            END IF
    1703              :                         END DO
    1704            0 :                         virial_xc(jdir, idir) = virial_xc(jdir, idir) + tmp
    1705            0 :                         virial_xc(idir, jdir) = virial_xc(jdir, idir)
    1706              :                      END DO
    1707              :                   END DO
    1708              :                END DO
    1709              :             END DO
    1710              :          END DO
    1711              :       END IF
    1712              : 
    1713           50 :    END SUBROUTINE build_virial_from_feature_grads
    1714              : 
    1715              : ! **************************************************************************************************
    1716              : !> \brief Print a native SKALA XC virial contribution for diagnostics.
    1717              : !> \param label ...
    1718              : !> \param delta ...
    1719              : !> \param root_rank ...
    1720              : ! **************************************************************************************************
    1721            0 :    SUBROUTINE print_virial_delta(label, delta, root_rank)
    1722              :       CHARACTER(LEN=*), INTENT(IN)                       :: label
    1723              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(IN)         :: delta
    1724              :       LOGICAL, INTENT(IN)                                :: root_rank
    1725              : 
    1726              :       INTEGER                                            :: i, iw
    1727              : 
    1728            0 :       IF (.NOT. root_rank) RETURN
    1729            0 :       iw = cp_logger_get_default_io_unit()
    1730            0 :       IF (iw <= 0) RETURN
    1731            0 :       WRITE (iw, "(T2,A,1X,A)") "SKALA_GPW| XC virial contribution", TRIM(label)
    1732            0 :       DO i = 1, 3
    1733            0 :          WRITE (iw, "(T2,A,1X,3ES20.10)") "SKALA_GPW|", delta(i, 1:3)
    1734              :       END DO
    1735              : 
    1736              :    END SUBROUTINE print_virial_delta
    1737              : 
    1738              : ! **************************************************************************************************
    1739              : !> \brief Add explicit SKALA coordinate-feature contributions to the XC virial.
    1740              : !> \param virial_xc ...
    1741              : !> \param features ...
    1742              : !> \param atom_coord_grad_t ...
    1743              : !> \param grid_coord_grad_t ...
    1744              : !> \param root_rank ...
    1745              : !> \param print_components ...
    1746              : ! **************************************************************************************************
    1747           50 :    SUBROUTINE build_static_coordinate_virial(virial_xc, features, atom_coord_grad_t, &
    1748              :                                              grid_coord_grad_t, root_rank, print_components)
    1749              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(INOUT)      :: virial_xc
    1750              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1751              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: atom_coord_grad_t, grid_coord_grad_t
    1752              :       LOGICAL, INTENT(IN)                                :: root_rank
    1753              :       LOGICAL, INTENT(IN), OPTIONAL                      :: print_components
    1754              : 
    1755              :       INTEGER                                            :: feature_pos, i, iatom, idir, iw, j, &
    1756              :                                                             jdir, k, local_row, row
    1757              :       LOGICAL                                            :: my_print_components
    1758              :       REAL(KIND=dp)                                      :: tmp
    1759              :       REAL(KIND=dp), DIMENSION(3, 3)                     :: atom_virial, grid_virial
    1760           50 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: atom_coord_grad, grid_coord_grad
    1761              : 
    1762           50 :       my_print_components = .FALSE.
    1763           50 :       IF (PRESENT(print_components)) my_print_components = print_components
    1764              : 
    1765           50 :       NULLIFY (atom_coord_grad, grid_coord_grad)
    1766           50 :       CALL torch_tensor_grad(features%grid_coords_t, grid_coord_grad_t)
    1767           50 :       CALL torch_tensor_data_ptr(grid_coord_grad_t, grid_coord_grad)
    1768           50 :       CALL torch_tensor_data_ptr(atom_coord_grad_t, atom_coord_grad)
    1769              : 
    1770           50 :       grid_virial = 0.0_dp
    1771           50 :       atom_virial = 0.0_dp
    1772           50 :       local_row = 0
    1773         1212 :       DO k = LBOUND(features%feature_index, 3), UBOUND(features%feature_index, 3)
    1774        26414 :          DO j = LBOUND(features%feature_index, 2), UBOUND(features%feature_index, 2)
    1775       329007 :             DO i = LBOUND(features%feature_index, 1), UBOUND(features%feature_index, 1)
    1776       258411 :                local_row = local_row + 1
    1777       774049 :                DO feature_pos = features%local_feature_offsets(local_row), &
    1778       281589 :                   features%local_feature_offsets(local_row + 1) - 1
    1779       515638 :                   row = features%local_feature_rows(feature_pos)
    1780      2320963 :                   DO idir = 1, 3
    1781      6703294 :                      DO jdir = 1, 3
    1782      4640742 :                         tmp = grid_coord_grad(idir, row)*features%grid_coords(jdir, row)
    1783      4640742 :                         grid_virial(idir, jdir) = grid_virial(idir, jdir) + tmp
    1784      6187656 :                         virial_xc(idir, jdir) = virial_xc(idir, jdir) + tmp
    1785              :                      END DO
    1786              :                   END DO
    1787              :                END DO
    1788              :             END DO
    1789              :          END DO
    1790              :       END DO
    1791           50 :       CPASSERT(local_row == features%nflat_local)
    1792              : 
    1793           50 :       IF (root_rank) THEN
    1794           75 :          DO iatom = 1, SIZE(features%coarse_0_atomic_coords, 2)
    1795          225 :             DO idir = 1, 3
    1796          650 :                DO jdir = 1, 3
    1797          450 :                   tmp = atom_coord_grad(idir, iatom)*features%coarse_0_atomic_coords(jdir, iatom)
    1798          450 :                   atom_virial(idir, jdir) = atom_virial(idir, jdir) + tmp
    1799          600 :                   virial_xc(idir, jdir) = virial_xc(idir, jdir) + tmp
    1800              :                END DO
    1801              :             END DO
    1802              :          END DO
    1803              :       END IF
    1804              : 
    1805           50 :       IF (my_print_components .AND. root_rank) THEN
    1806            0 :          iw = cp_logger_get_default_io_unit()
    1807            0 :          IF (iw > 0) THEN
    1808            0 :             CALL print_virial_delta("static-grid", grid_virial, .TRUE.)
    1809            0 :             CALL print_virial_delta("static-atom", atom_virial, .TRUE.)
    1810              :          END IF
    1811              :       END IF
    1812              : 
    1813           50 :       CALL torch_tensor_release(grid_coord_grad_t)
    1814              : 
    1815           50 :    END SUBROUTINE build_static_coordinate_virial
    1816              : 
    1817              : ! **************************************************************************************************
    1818              : !> \brief Add residual SKALA weight-feature contributions to the XC virial.
    1819              : !> \param virial_xc ...
    1820              : !> \param features ...
    1821              : !> \param exc ...
    1822              : !> \param grid_weight_grad_t ...
    1823              : !> \param atomic_grid_weight_grad_t ...
    1824              : !> \param root_rank ...
    1825              : !> \param print_components ...
    1826              : ! **************************************************************************************************
    1827           50 :    SUBROUTINE build_weight_virial(virial_xc, features, exc, grid_weight_grad_t, &
    1828              :                                   atomic_grid_weight_grad_t, root_rank, print_components)
    1829              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(INOUT)      :: virial_xc
    1830              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1831              :       REAL(KIND=dp), INTENT(IN)                          :: exc
    1832              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: grid_weight_grad_t, &
    1833              :                                                             atomic_grid_weight_grad_t
    1834              :       LOGICAL, INTENT(IN)                                :: root_rank
    1835              :       LOGICAL, INTENT(IN), OPTIONAL                      :: print_components
    1836              : 
    1837              :       INTEGER                                            :: feature_pos, i, idir, iw, j, k, &
    1838              :                                                             local_row, row
    1839              :       LOGICAL                                            :: my_print_components
    1840              :       REAL(KIND=dp)                                      :: atomic_tmp, exc_tmp, grid_tmp, tmp
    1841           50 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: atomic_grid_weight_grad, grid_weight_grad
    1842              : 
    1843           50 :       my_print_components = .FALSE.
    1844           50 :       IF (PRESENT(print_components)) my_print_components = print_components
    1845              : 
    1846           50 :       NULLIFY (atomic_grid_weight_grad, grid_weight_grad)
    1847           50 :       CALL torch_tensor_grad(features%grid_weights_t, grid_weight_grad_t)
    1848           50 :       CALL torch_tensor_grad(features%atomic_grid_weights_t, atomic_grid_weight_grad_t)
    1849           50 :       CALL torch_tensor_data_ptr(grid_weight_grad_t, grid_weight_grad)
    1850           50 :       CALL torch_tensor_data_ptr(atomic_grid_weight_grad_t, atomic_grid_weight_grad)
    1851              : 
    1852           50 :       grid_tmp = 0.0_dp
    1853           50 :       atomic_tmp = 0.0_dp
    1854           50 :       local_row = 0
    1855         1212 :       DO k = LBOUND(features%feature_index, 3), UBOUND(features%feature_index, 3)
    1856        26414 :          DO j = LBOUND(features%feature_index, 2), UBOUND(features%feature_index, 2)
    1857       329007 :             DO i = LBOUND(features%feature_index, 1), UBOUND(features%feature_index, 1)
    1858       258411 :                local_row = local_row + 1
    1859       774049 :                DO feature_pos = features%local_feature_offsets(local_row), &
    1860       281589 :                   features%local_feature_offsets(local_row + 1) - 1
    1861       515638 :                   row = features%local_feature_rows(feature_pos)
    1862       515638 :                   grid_tmp = grid_tmp + grid_weight_grad(row)*features%grid_weights(row)
    1863              :                   atomic_tmp = atomic_tmp + &
    1864       774049 :                                atomic_grid_weight_grad(row)*features%atomic_grid_weights(row)
    1865              :                END DO
    1866              :             END DO
    1867              :          END DO
    1868              :       END DO
    1869           50 :       CPASSERT(local_row == features%nflat_local)
    1870           50 :       exc_tmp = 0.0_dp
    1871           50 :       IF (root_rank) exc_tmp = -exc
    1872           50 :       tmp = grid_tmp + atomic_tmp + exc_tmp
    1873              : 
    1874           50 :       IF (my_print_components .AND. root_rank) THEN
    1875            0 :          iw = cp_logger_get_default_io_unit()
    1876            0 :          IF (iw > 0) THEN
    1877            0 :             WRITE (iw, "(T2,A,1X,ES20.10)") "SKALA_GPW| XC virial weight grid", grid_tmp
    1878            0 :             WRITE (iw, "(T2,A,1X,ES20.10)") "SKALA_GPW| XC virial weight atomic", atomic_tmp
    1879            0 :             WRITE (iw, "(T2,A,1X,ES20.10)") "SKALA_GPW| XC virial weight final", exc_tmp
    1880            0 :             WRITE (iw, "(T2,A,1X,ES20.10)") "SKALA_GPW| XC virial weight residual", tmp
    1881              :          END IF
    1882              :       END IF
    1883              : 
    1884          200 :       DO idir = 1, 3
    1885          200 :          virial_xc(idir, idir) = virial_xc(idir, idir) + tmp
    1886              :       END DO
    1887              : 
    1888           50 :       CALL torch_tensor_release(grid_weight_grad_t)
    1889           50 :       CALL torch_tensor_release(atomic_grid_weight_grad_t)
    1890              : 
    1891           50 :    END SUBROUTINE build_weight_virial
    1892              : 
    1893              : ! **************************************************************************************************
    1894              : !> \brief Fill CP2K VXC real-space arrays from Torch feature gradients.
    1895              : !> \param vxc_rho ...
    1896              : !> \param vxc_tau ...
    1897              : !> \param rho_r ...
    1898              : !> \param pw_pool ...
    1899              : !> \param density_grad ...
    1900              : !> \param grad_grad ...
    1901              : !> \param kin_grad ...
    1902              : !> \param xc_deriv_method_id ...
    1903              : ! **************************************************************************************************
    1904          290 :    SUBROUTINE build_vxc_from_feature_grads(vxc_rho, vxc_tau, rho_r, pw_pool, &
    1905          290 :                                            density_grad, grad_grad, kin_grad, &
    1906              :                                            xc_deriv_method_id)
    1907              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: vxc_rho, vxc_tau, rho_r
    1908              :       TYPE(pw_pool_type), POINTER                        :: pw_pool
    1909              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: density_grad
    1910              :       REAL(KIND=dp), DIMENSION(:, :, :), INTENT(IN)      :: grad_grad
    1911              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: kin_grad
    1912              :       INTEGER, INTENT(IN)                                :: xc_deriv_method_id
    1913              : 
    1914              :       INTEGER                                            :: i, ipt, ispin, j, k, nspins
    1915              :       INTEGER, DIMENSION(2, 3)                           :: bo
    1916              :       REAL(KIND=dp)                                      :: dvol_inv
    1917              :       TYPE(pw_c1d_gs_type)                               :: tmp_g, vxc_g
    1918         1160 :       TYPE(pw_r3d_rs_type), DIMENSION(3)                 :: grad_pw
    1919              : 
    1920          290 :       nspins = SIZE(rho_r)
    1921         2900 :       bo = rho_r(1)%pw_grid%bounds_local
    1922          290 :       dvol_inv = 1.0_dp/rho_r(1)%pw_grid%dvol
    1923              : 
    1924         1836 :       ALLOCATE (vxc_rho(nspins), vxc_tau(nspins))
    1925          628 :       DO ispin = 1, nspins
    1926          338 :          CALL pw_pool%create_pw(vxc_rho(ispin))
    1927          338 :          CALL pw_pool%create_pw(vxc_tau(ispin))
    1928          338 :          CALL pw_zero(vxc_rho(ispin))
    1929          628 :          CALL pw_zero(vxc_tau(ispin))
    1930              :       END DO
    1931              : 
    1932          290 :       IF (xc_requires_tmp_g(xc_deriv_method_id) .OR. rho_r(1)%pw_grid%spherical) THEN
    1933          290 :          CALL pw_pool%create_pw(vxc_g)
    1934          290 :          IF (.NOT. rho_r(1)%pw_grid%spherical) CALL pw_pool%create_pw(tmp_g)
    1935              :       END IF
    1936              : 
    1937          628 :       DO ispin = 1, nspins
    1938         1352 :          DO i = 1, 3
    1939         1014 :             CALL pw_pool%create_pw(grad_pw(i))
    1940         1352 :             CALL pw_zero(grad_pw(i))
    1941              :          END DO
    1942              : 
    1943          338 :          ipt = 0
    1944         7024 :          DO k = bo(1, 3), bo(2, 3)
    1945       162426 :             DO j = bo(1, 2), bo(2, 2)
    1946      2349791 :                DO i = bo(1, 1), bo(2, 1)
    1947      2187703 :                   ipt = ipt + 1
    1948      2343105 :                   IF (nspins == 1) THEN
    1949              :                      vxc_rho(1)%array(i, j, k) = 0.5_dp*dvol_inv* &
    1950      1499203 :                                                  (density_grad(ipt, 1) + density_grad(ipt, 2))
    1951              :                      vxc_tau(1)%array(i, j, k) = 0.5_dp*dvol_inv* &
    1952      1499203 :                                                  (kin_grad(ipt, 1) + kin_grad(ipt, 2))
    1953              :                      grad_pw(1)%array(i, j, k) = 0.5_dp*dvol_inv* &
    1954      1499203 :                                                  (grad_grad(ipt, 1, 1) + grad_grad(ipt, 1, 2))
    1955              :                      grad_pw(2)%array(i, j, k) = 0.5_dp*dvol_inv* &
    1956      1499203 :                                                  (grad_grad(ipt, 2, 1) + grad_grad(ipt, 2, 2))
    1957              :                      grad_pw(3)%array(i, j, k) = 0.5_dp*dvol_inv* &
    1958      1499203 :                                                  (grad_grad(ipt, 3, 1) + grad_grad(ipt, 3, 2))
    1959              :                   ELSE
    1960       688500 :                      vxc_rho(ispin)%array(i, j, k) = dvol_inv*density_grad(ipt, ispin)
    1961       688500 :                      vxc_tau(ispin)%array(i, j, k) = dvol_inv*kin_grad(ipt, ispin)
    1962       688500 :                      grad_pw(1)%array(i, j, k) = dvol_inv*grad_grad(ipt, 1, ispin)
    1963       688500 :                      grad_pw(2)%array(i, j, k) = dvol_inv*grad_grad(ipt, 2, ispin)
    1964       688500 :                      grad_pw(3)%array(i, j, k) = dvol_inv*grad_grad(ipt, 3, ispin)
    1965              :                   END IF
    1966              :                END DO
    1967              :             END DO
    1968              :          END DO
    1969              : 
    1970         1352 :          DO i = 1, 3
    1971         1352 :             CALL pw_scale(grad_pw(i), -1.0_dp)
    1972              :          END DO
    1973          338 :          CALL xc_pw_divergence(xc_deriv_method_id, grad_pw, tmp_g, vxc_g, vxc_rho(ispin))
    1974              : 
    1975         1642 :          DO i = 1, 3
    1976         1352 :             CALL pw_pool%give_back_pw(grad_pw(i))
    1977              :          END DO
    1978              :       END DO
    1979              : 
    1980          290 :       IF (ASSOCIATED(vxc_g%pw_grid)) CALL pw_pool%give_back_pw(vxc_g)
    1981          290 :       IF (ASSOCIATED(tmp_g%pw_grid)) CALL pw_pool%give_back_pw(tmp_g)
    1982              : 
    1983          290 :    END SUBROUTINE build_vxc_from_feature_grads
    1984              : 
    1985              : ! **************************************************************************************************
    1986              : !> \brief Print optional diagnostics for the CP2K-native SKALA GPW feature block.
    1987              : !> \param features ...
    1988              : !> \param print_active ...
    1989              : ! **************************************************************************************************
    1990           24 :    SUBROUTINE print_native_grid_diagnostics(features, print_active)
    1991              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1992              :       LOGICAL, INTENT(IN)                                :: print_active
    1993              : 
    1994              :       INTEGER                                            :: atom_rows_max, atom_rows_min, &
    1995              :                                                             chunk_rows_max, chunk_rows_min, iw
    1996              :       REAL(KIND=dp)                                      :: chunk_imbalance
    1997              : 
    1998           24 :       IF (.NOT. print_active) RETURN
    1999              : 
    2000           12 :       iw = cp_logger_get_default_io_unit()
    2001           12 :       IF (iw <= 0) RETURN
    2002              :       WRITE (UNIT=iw, FMT="(/,T2,A,1X,ES19.11)") &
    2003           12 :          "SKALA_GPW| Native grid feature electrons", features%electron_count
    2004              :       WRITE (UNIT=iw, FMT="(T2,A,1X,ES19.11)") &
    2005           12 :          "SKALA_GPW| Native grid feature spin moment", features%spin_moment
    2006              :       WRITE (UNIT=iw, FMT="(T2,A,1X,ES19.11)") &
    2007           12 :          "SKALA_GPW| Native grid feature weight sum", features%grid_weight_sum
    2008           12 :       IF (ALLOCATED(features%atomic_grid_sizes)) THEN
    2009           49 :          atom_rows_min = INT(MINVAL(features%atomic_grid_sizes))
    2010           49 :          atom_rows_max = INT(MAXVAL(features%atomic_grid_sizes))
    2011              :          WRITE (UNIT=iw, FMT="(T2,A,1X,I0,1X,A,1X,I0,1X,A,1X,I0)") &
    2012           12 :             "SKALA_GPW| Native grid atom row range", atom_rows_min, "to", &
    2013           61 :             atom_rows_max, "sum", INT(SUM(features%atomic_grid_sizes))
    2014              :       END IF
    2015           12 :       IF (features%uses_atom_chunks) THEN
    2016              :          WRITE (UNIT=iw, FMT="(T2,A,1X,I0,1X,A,1X,I0)") &
    2017            1 :             "SKALA_GPW| Native grid atom chunk rows", features%chunk_feature_count, &
    2018            2 :             "of", features%nflat
    2019            1 :          IF (ALLOCATED(features%chunk_grad_counts)) THEN
    2020            3 :             chunk_rows_min = MINVAL(features%chunk_grad_counts)/ngrad_per_point
    2021            3 :             chunk_rows_max = MAXVAL(features%chunk_grad_counts)/ngrad_per_point
    2022            1 :             chunk_imbalance = REAL(chunk_rows_max, KIND=dp)/REAL(MAX(1, chunk_rows_min), KIND=dp)
    2023              :             WRITE (UNIT=iw, FMT="(T2,A,1X,I0,1X,A,1X,I0,1X,A,1X,ES12.5)") &
    2024            1 :                "SKALA_GPW| Native grid atom chunk row range", chunk_rows_min, &
    2025            2 :                "to", chunk_rows_max, "imbalance", chunk_imbalance
    2026              :          END IF
    2027              :       END IF
    2028              : 
    2029              :    END SUBROUTINE print_native_grid_diagnostics
    2030              : 
    2031              : ! **************************************************************************************************
    2032              : !> \brief Configure CUDA device selection for the native SKALA GPW Torch path.
    2033              : !> \param use_cuda ...
    2034              : !> \param requested_device ...
    2035              : !> \param group ...
    2036              : !> \return selected CUDA device, or -1 for CPU fallback/no visible CUDA device
    2037              : ! **************************************************************************************************
    2038          322 :    FUNCTION configure_native_grid_cuda(use_cuda, requested_device, group) RESULT(selected_device)
    2039              :       LOGICAL, INTENT(IN)                                :: use_cuda
    2040              :       INTEGER, INTENT(IN)                                :: requested_device
    2041              : 
    2042              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    2043              : 
    2044              :       INTEGER                                            :: cuda_device_count, iw, pe, selected_device
    2045          322 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: selected_devices
    2046              : 
    2047          322 :       selected_device = -1
    2048              : 
    2049          322 :       IF (.NOT. use_cuda) RETURN
    2050              : 
    2051            0 :       IF (.NOT. torch_cuda_is_available()) THEN
    2052            0 :          cuda_device_count = 0
    2053              :       ELSE
    2054            0 :          cuda_device_count = torch_cuda_device_count()
    2055              :       END IF
    2056            0 :       IF (cuda_device_count > 0) THEN
    2057            0 :          IF (requested_device < 0) THEN
    2058            0 :             selected_device = MOD(group%mepos, cuda_device_count)
    2059              :          ELSE
    2060            0 :             selected_device = requested_device
    2061              :          END IF
    2062              :       END IF
    2063            0 :       IF (selected_device >= cuda_device_count) THEN
    2064              :          CALL cp_abort(__LOCATION__, &
    2065              :                        "GAUXC%NATIVE_GRID_CUDA_DEVICE selects a CUDA device outside the visible "// &
    2066            0 :                        "Torch CUDA device range.")
    2067              :       END IF
    2068            0 :       IF (selected_device >= 0) CALL offload_set_chosen_device(selected_device)
    2069              : 
    2070            0 :       ALLOCATE (selected_devices(group%num_pe))
    2071            0 :       CALL group%allgather(selected_device, selected_devices)
    2072              : 
    2073            0 :       IF (group%mepos /= 0) RETURN
    2074              :       IF (selected_device == logged_cuda_device .AND. &
    2075              :           cuda_device_count == logged_cuda_device_count .AND. &
    2076            0 :           group%num_pe == logged_cuda_nproc .AND. &
    2077              :           requested_device == logged_cuda_request) RETURN
    2078              : 
    2079            0 :       iw = cp_logger_get_default_io_unit()
    2080            0 :       IF (iw <= 0) RETURN
    2081            0 :       IF (selected_device >= 0) THEN
    2082              :          WRITE (UNIT=iw, FMT="(/,T2,A,1X,I0,1X,A,1X,I0,1X,A,1X,I0)") &
    2083            0 :             "SKALA_GPW| Native grid Torch CUDA device", selected_device, &
    2084            0 :             "of", cuda_device_count, "requested", requested_device
    2085              :       ELSE
    2086              :          WRITE (UNIT=iw, FMT="(/,T2,A)") &
    2087            0 :             "SKALA_GPW| Native grid Torch CUDA requested, but no Torch CUDA device is visible"
    2088              :       END IF
    2089              :       WRITE (UNIT=iw, FMT="(T2,A)", ADVANCE="NO") &
    2090            0 :          "SKALA_GPW| Native grid Torch CUDA rank devices"
    2091            0 :       DO pe = 1, group%num_pe
    2092            0 :          WRITE (UNIT=iw, FMT="(1X,I0,A,I0)", ADVANCE="NO") pe - 1, ":", selected_devices(pe)
    2093              :       END DO
    2094            0 :       WRITE (UNIT=iw, FMT=*)
    2095              : 
    2096            0 :       logged_cuda_device = selected_device
    2097            0 :       logged_cuda_device_count = cuda_device_count
    2098            0 :       logged_cuda_nproc = group%num_pe
    2099            0 :       logged_cuda_request = requested_device
    2100              : 
    2101          322 :    END FUNCTION configure_native_grid_cuda
    2102              : 
    2103              : ! **************************************************************************************************
    2104              : !> \brief Load and cache the TorchScript SKALA model.
    2105              : !> \param model_path ...
    2106              : !> \param cuda_device ...
    2107              : ! **************************************************************************************************
    2108          322 :    SUBROUTINE ensure_model_loaded(model_path, cuda_device)
    2109              :       CHARACTER(len=*), INTENT(IN)                       :: model_path
    2110              :       INTEGER, INTENT(IN)                                :: cuda_device
    2111              : 
    2112          322 :       IF (cached_model_loaded) THEN
    2113          234 :          IF (TRIM(cached_model_path) == TRIM(model_path) .AND. &
    2114              :              cached_model_cuda_device == cuda_device) RETURN
    2115            0 :          CALL skala_torch_model_release(cached_model)
    2116            0 :          cached_model_loaded = .FALSE.
    2117              :       END IF
    2118              : 
    2119           88 :       CALL skala_torch_model_load(cached_model, TRIM(model_path))
    2120           88 :       cached_model_path = model_path
    2121           88 :       cached_model_cuda_device = cuda_device
    2122           88 :       cached_model_loaded = .TRUE.
    2123              : 
    2124          322 :    END SUBROUTINE ensure_model_loaded
    2125              : 
    2126              : ! **************************************************************************************************
    2127              : !> \brief Resolve the SKALA TorchScript model path from the GAUXC subsection.
    2128              : !> \param xc_section ...
    2129              : !> \param model_path ...
    2130              : ! **************************************************************************************************
    2131          322 :    SUBROUTINE get_skala_model_path(xc_section, model_path)
    2132              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
    2133              :       CHARACTER(len=default_path_length), INTENT(OUT)    :: model_path
    2134              : 
    2135              :       CHARACTER(len=default_path_length)                 :: model_key
    2136              :       INTEGER                                            :: env_status
    2137              :       LOGICAL                                            :: native_grid_use_cuda
    2138              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
    2139              : 
    2140          322 :       gauxc_section => get_gauxc_section(xc_section)
    2141          322 :       IF (.NOT. ASSOCIATED(gauxc_section)) THEN
    2142            0 :          CPABORT("Native SKALA GPW requires an XC_FUNCTIONAL%GAUXC section")
    2143              :       END IF
    2144              : 
    2145          322 :       CALL section_vals_val_get(gauxc_section, "MODEL", c_val=model_path)
    2146          322 :       model_key = ADJUSTL(model_path)
    2147          322 :       CALL uppercase(model_key)
    2148          322 :       IF (TRIM(model_key) == "NONE" .OR. TRIM(model_key) == "") THEN
    2149            0 :          CPABORT("Native SKALA GPW requires GAUXC%MODEL SKALA or a TorchScript model path")
    2150          322 :       ELSE IF (TRIM(model_key) == "SKALA") THEN
    2151          322 :          CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_USE_CUDA", l_val=native_grid_use_cuda)
    2152          322 :          IF (native_grid_use_cuda) THEN
    2153            0 :             CALL GET_ENVIRONMENT_VARIABLE("GAUXC_SKALA_CUDA_MODEL", model_path, STATUS=env_status)
    2154            0 :             IF (env_status == 0 .AND. LEN_TRIM(model_path) > 0) RETURN
    2155              :          END IF
    2156          322 :          CALL GET_ENVIRONMENT_VARIABLE("GAUXC_SKALA_MODEL", model_path, STATUS=env_status)
    2157          322 :          IF (env_status /= 0 .OR. LEN_TRIM(model_path) == 0) THEN
    2158            0 :             IF (native_grid_use_cuda) THEN
    2159              :                CALL cp_abort(__LOCATION__, &
    2160            0 :                              "MODEL SKALA CUDA path requires GAUXC_SKALA_CUDA_MODEL or GAUXC_SKALA_MODEL")
    2161              :             ELSE
    2162              :                CALL cp_abort(__LOCATION__, &
    2163            0 :                              "MODEL SKALA requires the GAUXC_SKALA_MODEL environment variable")
    2164              :             END IF
    2165              :          END IF
    2166              :       END IF
    2167              : 
    2168              :    END SUBROUTINE get_skala_model_path
    2169              : 
    2170              : ! **************************************************************************************************
    2171              : !> \brief Return the first GAUXC functional subsection, if present.
    2172              : !> \param xc_section ...
    2173              : !> \return ...
    2174              : ! **************************************************************************************************
    2175       189079 :    FUNCTION get_gauxc_section(xc_section) RESULT(gauxc_section)
    2176              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
    2177              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
    2178              : 
    2179              :       INTEGER                                            :: ifun
    2180              :       TYPE(section_vals_type), POINTER                   :: functionals, xc_fun
    2181              : 
    2182       189079 :       NULLIFY (gauxc_section)
    2183       189079 :       IF (.NOT. ASSOCIATED(xc_section)) RETURN
    2184              : 
    2185       189079 :       functionals => section_vals_get_subs_vals(xc_section, "XC_FUNCTIONAL")
    2186       189079 :       IF (.NOT. ASSOCIATED(functionals)) RETURN
    2187              : 
    2188       189079 :       ifun = 0
    2189              :       DO
    2190       379208 :          ifun = ifun + 1
    2191       379208 :          xc_fun => section_vals_get_subs_vals2(functionals, i_section=ifun)
    2192       379208 :          IF (.NOT. ASSOCIATED(xc_fun)) EXIT
    2193       379208 :          IF (xc_fun%section%name == "GAUXC") THEN
    2194              :             gauxc_section => xc_fun
    2195              :             EXIT
    2196              :          END IF
    2197              :       END DO
    2198              : 
    2199              :    END FUNCTION get_gauxc_section
    2200              : 
    2201              : END MODULE skala_gpw_functional
        

Generated by: LCOV version 2.0-1