LCOV - code coverage report
Current view: top level - src/xc - skala_gpw_functional.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:9ccafda) Lines: 79.1 % 1385 1095
Test Date: 2026-10-15 00:55:44 Functions: 92.1 % 38 35

            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 OMP_LIB,                         ONLY: omp_get_max_threads,&
      13              :                                               omp_set_num_threads
      14              :    USE cell_types,                      ONLY: cell_type,&
      15              :                                               pbc
      16              :    USE cp_array_utils,                  ONLY: cp_3d_r_cp_type
      17              :    USE cp_log_handling,                 ONLY: cp_logger_get_default_io_unit
      18              :    USE input_section_types,             ONLY: section_get_rval,&
      19              :                                               section_vals_get_subs_vals,&
      20              :                                               section_vals_get_subs_vals2,&
      21              :                                               section_vals_type,&
      22              :                                               section_vals_val_get
      23              :    USE kinds,                           ONLY: default_path_length,&
      24              :                                               dp,&
      25              :                                               int_8
      26              :    USE message_passing,                 ONLY: mp_comm_type
      27              :    USE offload_api,                     ONLY: offload_set_chosen_device
      28              :    USE particle_types,                  ONLY: particle_type
      29              :    USE pw_grid_types,                   ONLY: pw_grid_type
      30              :    USE pw_methods,                      ONLY: pw_scale,&
      31              :                                               pw_zero
      32              :    USE pw_pool_types,                   ONLY: pw_pool_type
      33              :    USE pw_types,                        ONLY: pw_c1d_gs_type,&
      34              :                                               pw_r3d_rs_type
      35              :    USE qs_grid_atom,                    ONLY: grid_atom_type
      36              :    USE skala_gpw_features,              ONLY: &
      37              :         skala_gpw_atom_partition_hard, skala_gpw_atom_partition_smooth, &
      38              :         skala_gpw_atom_subchunk_count, skala_gpw_atom_subchunk_layout, skala_gpw_feature_build, &
      39              :         skala_gpw_feature_build_atom_subchunk_bounds, skala_gpw_feature_release, &
      40              :         skala_gpw_feature_type, skala_gpw_smooth_partition_derivatives, &
      41              :         smooth_partition_atomic_weight_scale_derivative
      42              :    USE skala_torch_api,                 ONLY: skala_torch_model_get_exc,&
      43              :                                               skala_torch_model_load,&
      44              :                                               skala_torch_model_release,&
      45              :                                               skala_torch_model_type
      46              :    USE string_utilities,                ONLY: uppercase
      47              :    USE torch_api,                       ONLY: &
      48              :         torch_cuda_device_count, torch_cuda_is_available, torch_dict_create, torch_dict_insert, &
      49              :         torch_dict_release, torch_dict_type, torch_tensor_backward_scalar, torch_tensor_data_ptr, &
      50              :         torch_tensor_from_array, torch_tensor_grad, torch_tensor_grad_batch3, torch_tensor_narrow, &
      51              :         torch_tensor_release, torch_tensor_reset_from_array, torch_tensor_to_device_leaf, &
      52              :         torch_tensor_type, torch_use_cuda
      53              :    USE xc_input_constants,              ONLY: skala_gapw_atom_composite_grid,&
      54              :                                               skala_gapw_common_grid,&
      55              :                                               skala_gapw_cp2k_default,&
      56              :                                               skala_gapw_direct_valence,&
      57              :                                               skala_gapw_paw_one_center,&
      58              :                                               skala_gapw_paw_one_center_split
      59              :    USE xc_rho_cflags_types,             ONLY: xc_rho_cflags_type
      60              :    USE xc_rho_set_types,                ONLY: xc_rho_set_create,&
      61              :                                               xc_rho_set_get,&
      62              :                                               xc_rho_set_release,&
      63              :                                               xc_rho_set_type,&
      64              :                                               xc_rho_set_update
      65              :    USE xc_util,                         ONLY: xc_pw_divergence,&
      66              :                                               xc_requires_tmp_g
      67              : #include "../base/base_uses.f90"
      68              : 
      69              :    IMPLICIT NONE
      70              : 
      71              :    PRIVATE
      72              : 
      73              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'skala_gpw_functional'
      74              :    INTEGER, PARAMETER, PRIVATE          :: atom_chunk_auto_max_rows = 400000, &
      75              :                                            atom_chunk_auto_min_rows = 100000, &
      76              :                                            atom_chunk_auto_row_quantum = 100000, &
      77              :                                            ncollapsed_grad_per_point = 5, ngrad_per_point = 10
      78              :    INTEGER, PARAMETER, PUBLIC           :: skala_gapw_density_partition_hard_minus_soft = 1, &
      79              :                                            skala_gapw_density_partition_hard_only = 2, &
      80              :                                            skala_gapw_density_partition_soft_only = 3, &
      81              :                                            skala_gapw_density_partition_none = 4
      82              : 
      83              :    TYPE skala_gapw_atom_cuda_tensor_cache_type
      84              :       TYPE(torch_tensor_type)                            :: atomic_coords_t, atomic_grid_weights_t, &
      85              :                                                             density_t, grad_t, grid_coords_t, &
      86              :                                                             grid_weights_t, kin_t
      87              :    END TYPE skala_gapw_atom_cuda_tensor_cache_type
      88              : 
      89              :    PUBLIC :: build_vxc_from_feature_grads, ensure_native_skala_grid_scope, get_gauxc_section, &
      90              :              skala_gapw_atom_composite_energy, skala_gapw_atom_vxc_of_r, &
      91              :              native_skala_gapw_atom_composite_requested, &
      92              :              native_skala_uses_atom_composite_grid, &
      93              :              native_skala_gapw_composite_direct_ao, native_skala_gapw_composite_reference, &
      94              :              native_skala_gapw_density_partition, skala_gpw_eval, skala_gpw_weight_derivative, &
      95              :              skala_gapw_representation, xc_section_uses_native_skala_evaluator, &
      96              :              xc_section_uses_native_skala_grid, xc_section_uses_gauxc_model
      97              : 
      98              :    TYPE(skala_torch_model_type), SAVE                  :: cached_model
      99              :    TYPE(skala_gapw_atom_cuda_tensor_cache_type), SAVE, TARGET :: cached_atom_cuda_tensors
     100              :    CHARACTER(len=default_path_length), SAVE            :: cached_model_path = ""
     101              :    LOGICAL, SAVE                                       :: cached_model_loaded = .FALSE.
     102              :    LOGICAL, SAVE                                       :: cached_model_fp64 = .FALSE.
     103              :    INTEGER, SAVE                                       :: cached_model_cuda_device = -3
     104              :    INTEGER, SAVE                                       :: logged_cuda_device = -3, &
     105              :                                                           logged_cuda_device_count = -1, &
     106              :                                                           logged_cuda_nproc = -1, &
     107              :                                                           logged_cuda_request = -3
     108              : 
     109              : CONTAINS
     110              : 
     111              : ! **************************************************************************************************
     112              : !> \brief Return true if the GAUXC subsection requests the CP2K-native GPW grid path.
     113              : !> \param xc_section ...
     114              : !> \return ...
     115              : ! **************************************************************************************************
     116       308416 :    FUNCTION xc_section_uses_native_skala_grid(xc_section) RESULT(uses_native_grid)
     117              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     118              :       LOGICAL                                            :: uses_native_grid
     119              : 
     120              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     121              : 
     122       308416 :       uses_native_grid = .FALSE.
     123       308416 :       gauxc_section => get_gauxc_section(xc_section)
     124       308416 :       IF (ASSOCIATED(gauxc_section)) THEN
     125         1814 :          CALL section_vals_val_get(gauxc_section, "NATIVE_GRID", l_val=uses_native_grid)
     126              :       END IF
     127              : 
     128       308416 :    END FUNCTION xc_section_uses_native_skala_grid
     129              : 
     130              : ! **************************************************************************************************
     131              : !> \brief Return the pseudopotential GAPW representation selected for an active model.
     132              : !> \param xc_section ...
     133              : !> \return ...
     134              : ! **************************************************************************************************
     135       201638 :    FUNCTION skala_gapw_representation(xc_section) RESULT(representation)
     136              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     137              :       INTEGER                                            :: representation
     138              : 
     139              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     140              : 
     141              :       representation = skala_gapw_cp2k_default
     142       200894 :       IF (.NOT. xc_section_uses_gauxc_model(xc_section)) RETURN
     143              : 
     144          744 :       gauxc_section => get_gauxc_section(xc_section)
     145          744 :       CPASSERT(ASSOCIATED(gauxc_section))
     146              :       CALL section_vals_val_get(gauxc_section, "PSEUDOPOTENTIAL_GAPW_REPRESENTATION", &
     147          744 :                                 i_val=representation)
     148              : 
     149              :       SELECT CASE (representation)
     150              :       CASE (skala_gapw_direct_valence, skala_gapw_paw_one_center, &
     151              :             skala_gapw_cp2k_default, skala_gapw_paw_one_center_split)
     152            0 :          CONTINUE
     153              :       CASE DEFAULT
     154          744 :          CALL cp_abort(__LOCATION__, "Unknown pseudopotential GAPW representation.")
     155              :       END SELECT
     156              : 
     157              :    END FUNCTION skala_gapw_representation
     158              : 
     159              : ! **************************************************************************************************
     160              : !> \brief Return true when SKALA must be evaluated by the CP2K-native grid machinery.
     161              : !> \param xc_section ...
     162              : !> \return ...
     163              : ! **************************************************************************************************
     164       169711 :    FUNCTION xc_section_uses_native_skala_evaluator(xc_section) RESULT(uses_native_evaluator)
     165              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     166              :       LOGICAL                                            :: uses_native_evaluator
     167              : 
     168              :       uses_native_evaluator = xc_section_uses_native_skala_grid(xc_section) .OR. &
     169              :                               skala_gapw_representation(xc_section) == &
     170       169711 :                               skala_gapw_paw_one_center
     171              : 
     172       169711 :    END FUNCTION xc_section_uses_native_skala_evaluator
     173              : 
     174              : ! **************************************************************************************************
     175              : !> \brief Return true if native SKALA should use the full GAPW ORB density on one common grid.
     176              : !> \param xc_section ...
     177              : !> \return ...
     178              : ! **************************************************************************************************
     179       157284 :    FUNCTION native_skala_gapw_composite_reference(xc_section) RESULT(use_composite_reference)
     180              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     181              :       LOGICAL                                            :: use_composite_reference
     182              : 
     183              :       LOGICAL                                            :: native_grid
     184              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     185              : 
     186       157284 :       use_composite_reference = .FALSE.
     187       157284 :       native_grid = .FALSE.
     188       157284 :       gauxc_section => get_gauxc_section(xc_section)
     189       157284 :       IF (ASSOCIATED(gauxc_section)) THEN
     190         1760 :          CALL section_vals_val_get(gauxc_section, "NATIVE_GRID", l_val=native_grid)
     191              :          CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_GAPW_COMPOSITE_REFERENCE", &
     192         1760 :                                    l_val=use_composite_reference)
     193              :       END IF
     194       157284 :       use_composite_reference = native_grid .AND. use_composite_reference
     195              : 
     196       157284 :    END FUNCTION native_skala_gapw_composite_reference
     197              : 
     198              : ! **************************************************************************************************
     199              : !> \brief Return true when the explicit atom-centered composite reference is requested.
     200              : !> \param xc_section ...
     201              : !> \return ...
     202              : ! **************************************************************************************************
     203         1866 :    FUNCTION native_skala_gapw_atom_composite_requested(xc_section) RESULT(use_atom_composite)
     204              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     205              :       LOGICAL                                            :: use_atom_composite
     206              : 
     207              :       LOGICAL                                            :: native_grid
     208              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     209              : 
     210         1866 :       use_atom_composite = .FALSE.
     211         1866 :       native_grid = .FALSE.
     212         1866 :       gauxc_section => get_gauxc_section(xc_section)
     213         1866 :       IF (ASSOCIATED(gauxc_section)) THEN
     214          352 :          CALL section_vals_val_get(gauxc_section, "NATIVE_GRID", l_val=native_grid)
     215              :          CALL section_vals_val_get(gauxc_section, &
     216              :                                    "NATIVE_GRID_GAPW_ATOM_COMPOSITE_REFERENCE", &
     217          352 :                                    l_val=use_atom_composite)
     218              :       END IF
     219         1866 :       use_atom_composite = native_grid .AND. use_atom_composite
     220              : 
     221         1866 :    END FUNCTION native_skala_gapw_atom_composite_requested
     222              : 
     223              : ! **************************************************************************************************
     224              : !> \brief Return true when native Skala uses atom-centered grids.
     225              : !> \param xc_section ...
     226              : !> \return ...
     227              : ! **************************************************************************************************
     228          616 :    FUNCTION native_skala_uses_atom_composite_grid(xc_section) RESULT(use_atom_composite)
     229              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     230              :       LOGICAL                                            :: use_atom_composite
     231              : 
     232              :       INTEGER                                            :: composite_grid
     233              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     234              : 
     235          616 :       use_atom_composite = .FALSE.
     236          616 :       composite_grid = skala_gapw_atom_composite_grid
     237          616 :       gauxc_section => get_gauxc_section(xc_section)
     238          616 :       IF (ASSOCIATED(gauxc_section)) THEN
     239              :          CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_LAYOUT", &
     240          616 :                                    i_val=composite_grid)
     241              :       END IF
     242              :       SELECT CASE (composite_grid)
     243              :       CASE (skala_gapw_common_grid)
     244          552 :          CONTINUE
     245              :       CASE (skala_gapw_atom_composite_grid)
     246          552 :          use_atom_composite = .TRUE.
     247              :       CASE DEFAULT
     248          616 :          CALL cp_abort(__LOCATION__, "Unknown native-grid layout.")
     249              :       END SELECT
     250              : 
     251          616 :    END FUNCTION native_skala_uses_atom_composite_grid
     252              : 
     253              : ! **************************************************************************************************
     254              : !> \brief Return true if the GAPW composite reference uses direct full-ORB collocation.
     255              : !> \param xc_section ...
     256              : !> \return ...
     257              : ! **************************************************************************************************
     258            8 :    FUNCTION native_skala_gapw_composite_direct_ao(xc_section) RESULT(use_direct_ao)
     259              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     260              :       LOGICAL                                            :: use_direct_ao
     261              : 
     262              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     263              : 
     264            8 :       use_direct_ao = .FALSE.
     265            8 :       gauxc_section => get_gauxc_section(xc_section)
     266            8 :       IF (ASSOCIATED(gauxc_section)) THEN
     267              :          CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_GAPW_COMPOSITE_DIRECT_AO", &
     268            8 :                                    l_val=use_direct_ao)
     269              :       END IF
     270            8 :       use_direct_ao = native_skala_gapw_composite_reference(xc_section) .AND. use_direct_ao
     271              : 
     272            8 :    END FUNCTION native_skala_gapw_composite_direct_ao
     273              : 
     274              : ! **************************************************************************************************
     275              : !> \brief Return true if the GAUXC subsection requests a model evaluation.
     276              : !> \param xc_section ...
     277              : !> \return ...
     278              : ! **************************************************************************************************
     279       235606 :    FUNCTION xc_section_uses_gauxc_model(xc_section) RESULT(uses_gauxc_model)
     280              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     281              :       LOGICAL                                            :: uses_gauxc_model
     282              : 
     283              :       CHARACTER(len=default_path_length)                 :: model_key, model_name, xc_key, xc_name
     284              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     285              : 
     286       235606 :       uses_gauxc_model = .FALSE.
     287       235606 :       gauxc_section => get_gauxc_section(xc_section)
     288       235606 :       IF (ASSOCIATED(gauxc_section)) THEN
     289         1366 :          CALL section_vals_val_get(gauxc_section, "MODEL", c_val=model_name)
     290         1366 :          CALL section_vals_val_get(gauxc_section, "FUNCTIONAL", c_val=xc_name)
     291         1366 :          model_key = ADJUSTL(model_name)
     292         1366 :          xc_key = ADJUSTL(xc_name)
     293         1366 :          CALL uppercase(model_key)
     294         1366 :          CALL uppercase(xc_key)
     295              :          uses_gauxc_model = (TRIM(model_key) /= "" .AND. TRIM(model_key) /= "NONE" .AND. &
     296         1366 :                              TRIM(model_key) /= TRIM(xc_key))
     297              :       END IF
     298              : 
     299       235606 :    END FUNCTION xc_section_uses_gauxc_model
     300              : 
     301              : ! **************************************************************************************************
     302              : !> \brief Return the hard/soft GAPW one-center density partition for native SKALA.
     303              : !> \param xc_section ...
     304              : !> \return ...
     305              : ! **************************************************************************************************
     306          270 :    FUNCTION native_skala_gapw_density_partition(xc_section) RESULT(partition)
     307              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     308              :       INTEGER                                            :: partition
     309              : 
     310              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     311              : 
     312          270 :       partition = skala_gapw_density_partition_hard_minus_soft
     313          270 :       gauxc_section => get_gauxc_section(xc_section)
     314          270 :       IF (ASSOCIATED(gauxc_section)) THEN
     315              :          CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_GAPW_DENSITY_PARTITION", &
     316          270 :                                    i_val=partition)
     317              :       END IF
     318              : 
     319              :       SELECT CASE (partition)
     320              :       CASE (skala_gapw_density_partition_hard_minus_soft, &
     321              :             skala_gapw_density_partition_hard_only, &
     322              :             skala_gapw_density_partition_soft_only, &
     323              :             skala_gapw_density_partition_none)
     324            0 :          CONTINUE
     325              :       CASE DEFAULT
     326              :          CALL cp_abort(__LOCATION__, &
     327          270 :                        "Unknown GAUXC%NATIVE_GRID_GAPW_DENSITY_PARTITION value.")
     328              :       END SELECT
     329              : 
     330          270 :    END FUNCTION native_skala_gapw_density_partition
     331              : 
     332              : ! **************************************************************************************************
     333              : !> \brief Enforce the currently implemented native SKALA GPW input scope.
     334              : !> \param xc_section ...
     335              : ! **************************************************************************************************
     336          600 :    SUBROUTINE ensure_native_skala_grid_scope(xc_section)
     337              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
     338              : 
     339              :       CHARACTER(len=default_path_length)                 :: model_key, model_name
     340              :       INTEGER                                            :: ifun, nfun
     341              :       LOGICAL                                            :: native_grid
     342              :       TYPE(section_vals_type), POINTER                   :: functionals, gauxc_section, xc_fun
     343              : 
     344          300 :       NULLIFY (gauxc_section)
     345          300 :       IF (.NOT. ASSOCIATED(xc_section)) THEN
     346            0 :          CPABORT("Native SKALA GPW requires an XC section")
     347              :       END IF
     348              : 
     349          300 :       functionals => section_vals_get_subs_vals(xc_section, "XC_FUNCTIONAL")
     350          300 :       IF (.NOT. ASSOCIATED(functionals)) THEN
     351            0 :          CPABORT("Native SKALA GPW requires an XC_FUNCTIONAL section")
     352              :       END IF
     353              : 
     354          300 :       nfun = 0
     355          300 :       ifun = 0
     356              :       DO
     357          600 :          ifun = ifun + 1
     358          600 :          xc_fun => section_vals_get_subs_vals2(functionals, i_section=ifun)
     359          600 :          IF (.NOT. ASSOCIATED(xc_fun)) EXIT
     360          300 :          nfun = nfun + 1
     361          600 :          IF (xc_fun%section%name == "GAUXC") gauxc_section => xc_fun
     362              :       END DO
     363              : 
     364          300 :       IF (.NOT. ASSOCIATED(gauxc_section)) THEN
     365            0 :          CPABORT("Native SKALA GPW requires an XC_FUNCTIONAL%GAUXC section")
     366              :       END IF
     367          300 :       IF (nfun /= 1) THEN
     368            0 :          CPABORT("Native SKALA GPW requires GAUXC to be the only XC functional")
     369              :       END IF
     370              : 
     371          300 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID", l_val=native_grid)
     372          300 :       IF (.NOT. native_grid .AND. &
     373            0 :           .NOT. xc_section_uses_native_skala_evaluator(xc_section)) RETURN
     374              : 
     375          300 :       CALL section_vals_val_get(gauxc_section, "MODEL", c_val=model_name)
     376          300 :       model_key = ADJUSTL(model_name)
     377          300 :       CALL uppercase(model_key)
     378          300 :       IF (TRIM(model_key) == "NONE" .OR. TRIM(model_key) == "") THEN
     379            0 :          CPABORT("Native SKALA GPW requires GAUXC%MODEL SKALA or a TorchScript model path")
     380              :       END IF
     381              : 
     382              :    END SUBROUTINE ensure_native_skala_grid_scope
     383              : 
     384              : ! **************************************************************************************************
     385              : !> \brief Evaluate SKALA energy and first derivatives on a CP2K GPW grid.
     386              : !> \param vxc_rho ...
     387              : !> \param vxc_tau ...
     388              : !> \param exc ...
     389              : !> \param rho_r ...
     390              : !> \param rho_g ...
     391              : !> \param tau ...
     392              : !> \param xc_section ...
     393              : !> \param weights ...
     394              : !> \param pw_pool ...
     395              : !> \param particle_set ...
     396              : !> \param cell ...
     397              : !> \param compute_virial ...
     398              : !> \param virial_xc ...
     399              : !> \param just_energy ...
     400              : !> \param atom_force ...
     401              : ! **************************************************************************************************
     402           48 :    SUBROUTINE skala_gpw_eval(vxc_rho, vxc_tau, exc, rho_r, rho_g, tau, xc_section, &
     403              :                              weights, pw_pool, particle_set, cell, compute_virial, virial_xc, &
     404           48 :                              just_energy, atom_force)
     405              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: vxc_rho, vxc_tau
     406              :       REAL(KIND=dp), INTENT(OUT)                         :: exc
     407              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: rho_r
     408              :       TYPE(pw_c1d_gs_type), DIMENSION(:), POINTER        :: rho_g
     409              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: tau
     410              :       TYPE(section_vals_type), POINTER                   :: xc_section
     411              :       TYPE(pw_r3d_rs_type), POINTER                      :: weights
     412              :       TYPE(pw_pool_type), POINTER                        :: pw_pool
     413              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     414              :       TYPE(cell_type), POINTER                           :: cell
     415              :       LOGICAL, INTENT(IN)                                :: compute_virial
     416              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(OUT)        :: virial_xc
     417              :       LOGICAL, INTENT(IN), OPTIONAL                      :: just_energy
     418              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT), &
     419              :          OPTIONAL                                        :: atom_force
     420              : 
     421              :       CHARACTER(len=default_path_length)                 :: model_path
     422              :       INTEGER :: i, ipt, ispin, iw, j, k, native_grid_atom_chunk_max_rows, &
     423              :          native_grid_atom_partition, native_grid_atom_subchunks, native_grid_cuda_device, nspins, &
     424              :          omp_max_threads_restore, phase_handle, selected_cuda_device, xc_deriv_method_id, &
     425              :          xc_rho_smooth_id
     426              :       INTEGER, DIMENSION(2, 3)                           :: bo
     427              :       LOGICAL :: has_atom_chunk_work, have_atom_coord_grad, lsd, my_just_energy, &
     428              :          native_grid_atom_chunk_routing, native_grid_atom_chunks, native_grid_diagnostics, &
     429              :          native_grid_use_cuda, needs_atom_force, use_atom_subchunks
     430              :       REAL(KIND=dp)                                      :: density_contraction, tau_contraction
     431           48 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: density_grad, kin_grad
     432           48 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: grad_grad
     433              :       REAL(KIND=dp), DIMENSION(3, 3)                     :: virial_before
     434              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     435           48 :       TYPE(skala_gpw_feature_type)                       :: features
     436              :       TYPE(torch_tensor_type)                            :: atom_coord_grad_t, &
     437              :                                                             atomic_grid_weight_grad_t, exc_tensor, &
     438              :                                                             grid_coord_grad_t, grid_weight_grad_t
     439              :       TYPE(xc_rho_cflags_type)                           :: needs
     440              :       TYPE(xc_rho_set_type)                              :: rho_set
     441              : 
     442           48 :       virial_xc = 0.0_dp
     443           48 :       exc = 0.0_dp
     444           48 :       my_just_energy = .FALSE.
     445           48 :       IF (PRESENT(just_energy)) my_just_energy = just_energy
     446           48 :       needs_atom_force = PRESENT(atom_force)
     447          112 :       IF (needs_atom_force) atom_force = 0.0_dp
     448           48 :       have_atom_coord_grad = .FALSE.
     449           48 :       omp_max_threads_restore = omp_get_max_threads()
     450              : 
     451           48 :       IF (compute_virial .AND. my_just_energy) THEN
     452              :          CALL cp_abort(__LOCATION__, &
     453            0 :                        "Native SKALA GPW stress/virial requires feature gradients.")
     454              :       END IF
     455           48 :       IF (.NOT. ASSOCIATED(rho_g)) THEN
     456              :          CALL cp_abort(__LOCATION__, &
     457            0 :                        "Native SKALA GPW requires the reciprocal-space density to form density gradients.")
     458              :       END IF
     459           48 :       IF (.NOT. ASSOCIATED(tau)) THEN
     460              :          CALL cp_abort(__LOCATION__, &
     461            0 :                        "Native SKALA GPW requires the kinetic-energy density.")
     462              :       END IF
     463              : 
     464           48 :       nspins = SIZE(rho_r)
     465           48 :       lsd = (nspins /= 1)
     466           48 :       CALL get_skala_model_path(xc_section, model_path)
     467           48 :       gauxc_section => get_gauxc_section(xc_section)
     468           48 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_USE_CUDA", l_val=native_grid_use_cuda)
     469              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_CUDA_DEVICE", &
     470           48 :                                 i_val=native_grid_cuda_device)
     471              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_CHUNKS", &
     472           48 :                                 l_val=native_grid_atom_chunks)
     473              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_CHUNK_ROUTING", &
     474           48 :                                 l_val=native_grid_atom_chunk_routing)
     475              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_CHUNK_MAX_ROWS", &
     476           48 :                                 i_val=native_grid_atom_chunk_max_rows)
     477              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_PARTITION", &
     478           48 :                                 i_val=native_grid_atom_partition)
     479           22 :       SELECT CASE (native_grid_atom_partition)
     480              :       CASE (1)
     481           22 :          native_grid_atom_partition = skala_gpw_atom_partition_hard
     482              :       CASE (2)
     483           26 :          native_grid_atom_partition = skala_gpw_atom_partition_smooth
     484              :       CASE DEFAULT
     485              :          CALL cp_abort(__LOCATION__, &
     486           48 :                        "Unknown GAUXC%NATIVE_GRID_ATOM_PARTITION value.")
     487              :       END SELECT
     488           48 :       native_grid_atom_chunk_routing = native_grid_atom_chunk_routing .OR. native_grid_atom_chunks
     489           48 :       native_grid_atom_chunks = native_grid_atom_chunks .OR. native_grid_atom_chunk_routing
     490           48 :       IF (native_grid_atom_chunk_max_rows < -1) THEN
     491              :          CALL cp_abort(__LOCATION__, &
     492            0 :                        "GAUXC%NATIVE_GRID_ATOM_CHUNK_MAX_ROWS must be -1, zero, or positive.")
     493              :       END IF
     494           48 :       IF (needs_atom_force .OR. compute_virial) THEN
     495            8 :          IF (native_grid_atom_partition == skala_gpw_atom_partition_hard) THEN
     496            0 :             native_grid_atom_partition = skala_gpw_atom_partition_smooth
     497              :          END IF
     498            8 :          native_grid_atom_chunk_routing = .FALSE.
     499            8 :          native_grid_atom_chunks = .FALSE.
     500              :       END IF
     501              :       ! The portable SKALA export used by the regtests builds ragged-index tensors on CPU.
     502           48 :       CALL torch_use_cuda(native_grid_use_cuda)
     503              :       selected_cuda_device = configure_native_grid_cuda( &
     504           48 :                              native_grid_use_cuda, native_grid_cuda_device, rho_r(1)%pw_grid%para%group)
     505           48 :       CALL ensure_model_loaded(model_path, selected_cuda_device, xc_section)
     506              : 
     507           48 :       IF (lsd) THEN
     508            6 :          needs%rho_spin = .TRUE.
     509            6 :          needs%drho_spin = .TRUE.
     510            6 :          needs%tau_spin = .TRUE.
     511              :       ELSE
     512           42 :          needs%rho = .TRUE.
     513           42 :          needs%drho = .TRUE.
     514           42 :          needs%tau = .TRUE.
     515              :       END IF
     516              : 
     517           48 :       CALL section_vals_val_get(xc_section, "XC_GRID%XC_DERIV", i_val=xc_deriv_method_id)
     518           48 :       CALL section_vals_val_get(xc_section, "XC_GRID%XC_SMOOTH_RHO", i_val=xc_rho_smooth_id)
     519              : 
     520              :       CALL xc_rho_set_create(rho_set, &
     521              :                              rho_r(1)%pw_grid%bounds_local, &
     522              :                              rho_cutoff=section_get_rval(xc_section, "density_cutoff"), &
     523              :                              drho_cutoff=section_get_rval(xc_section, "gradient_cutoff"), &
     524           48 :                              tau_cutoff=section_get_rval(xc_section, "tau_cutoff"))
     525              :       CALL xc_rho_set_update(rho_set, rho_r, rho_g, tau, needs, &
     526           48 :                              xc_deriv_method_id, xc_rho_smooth_id, pw_pool)
     527              : 
     528              :       CALL skala_gpw_feature_build(features, rho_set, rho_r, particle_set, cell, &
     529              :                                    requires_grad=(.NOT. my_just_energy), weights=weights, &
     530              :                                    requires_coordinate_grad=(needs_atom_force .OR. compute_virial), &
     531              :                                    requires_stress_grad=compute_virial, &
     532              :                                    use_atom_chunks=native_grid_atom_chunks, &
     533              :                                    route_atom_chunks=native_grid_atom_chunk_routing, &
     534           88 :                                    atom_partition=native_grid_atom_partition)
     535           48 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_DIAGNOSTICS", l_val=native_grid_diagnostics)
     536           48 :       IF (native_grid_diagnostics) THEN
     537           24 :          CALL print_native_grid_diagnostics(features, rho_r(1)%pw_grid%para%group%mepos == 0)
     538              :       END IF
     539              : 
     540           48 :       IF (features%uses_atom_chunks .AND. native_grid_atom_chunk_max_rows == -1) THEN
     541              :          native_grid_atom_chunk_max_rows = auto_atom_chunk_max_rows(features, &
     542           36 :                                                                     rho_r(1)%pw_grid%para%group)
     543              :       END IF
     544           48 :       IF (native_grid_diagnostics .AND. features%uses_atom_chunks .AND. &
     545              :           rho_r(1)%pw_grid%para%group%mepos == 0) THEN
     546           12 :          iw = cp_logger_get_default_io_unit()
     547           12 :          IF (iw > 0) THEN
     548              :             WRITE (UNIT=iw, FMT="(T2,A,1X,I0)") &
     549           12 :                "SKALA_GPW| Native grid atom chunk max rows", native_grid_atom_chunk_max_rows
     550              :          END IF
     551              :       END IF
     552           48 :       native_grid_atom_subchunks = 1
     553           48 :       IF (features%uses_atom_chunks .AND. native_grid_atom_chunk_max_rows > 0) THEN
     554           40 :          native_grid_atom_subchunks = skala_gpw_atom_subchunk_count(native_grid_atom_chunk_max_rows)
     555           40 :          CALL rho_r(1)%pw_grid%para%group%max(native_grid_atom_subchunks)
     556              :       END IF
     557           48 :       use_atom_subchunks = features%uses_atom_chunks .AND. native_grid_atom_subchunks > 1
     558           48 :       has_atom_chunk_work = .NOT. features%uses_atom_chunks .OR. features%chunk_feature_count > 0
     559           48 :       exc = 0.0_dp
     560           48 :       IF (use_atom_subchunks) THEN
     561              :          CALL evaluate_atom_subchunks(features, rho_r(1)%pw_grid%para%group, &
     562              :                                       native_grid_atom_chunk_max_rows, &
     563              :                                       compute_grads=(.NOT. my_just_energy), exc=exc, &
     564              :                                       density_grad=density_grad, grad_grad=grad_grad, &
     565            2 :                                       kin_grad=kin_grad, collapse_spin_grads=(nspins == 1))
     566           46 :       ELSE IF (has_atom_chunk_work) THEN
     567              :          CALL skala_torch_model_get_exc(cached_model, features%inputs, &
     568           46 :                                         features%grid_weights_t, exc_tensor, exc)
     569              :       END IF
     570           48 :       IF (features%uses_atom_chunks) CALL rho_r(1)%pw_grid%para%group%sum(exc)
     571              : 
     572           48 :       IF (.NOT. my_just_energy) THEN
     573           48 :          IF (.NOT. use_atom_subchunks) THEN
     574           46 :             IF (has_atom_chunk_work) THEN
     575           46 :                CALL timeset("skala_gpw_backward", phase_handle)
     576           46 :                CALL torch_tensor_backward_scalar(exc_tensor)
     577           46 :                CALL timestop(phase_handle)
     578              : 
     579           46 :                IF (compute_virial) THEN
     580            6 :                   IF (native_grid_diagnostics) virial_before = virial_xc
     581              :                   CALL build_weight_virial(virial_xc, features, exc, grid_weight_grad_t, &
     582              :                                            atomic_grid_weight_grad_t, &
     583              :                                            rho_r(1)%pw_grid%para%group%mepos == 0, &
     584            6 :                                            native_grid_diagnostics)
     585            6 :                   IF (native_grid_diagnostics) THEN
     586              :                      CALL print_virial_delta("weight-residual", virial_xc - virial_before, &
     587            0 :                                              rho_r(1)%pw_grid%para%group%mepos == 0)
     588              :                   END IF
     589              :                END IF
     590              :             END IF
     591              : 
     592           46 :             CALL timeset("skala_gpw_grad_fetch", phase_handle)
     593           46 :             IF (features%uses_atom_chunks) THEN
     594              :                CALL fetch_and_gather_atom_chunk_grads(features, rho_r(1)%pw_grid%para%group, &
     595           38 :                                                       density_grad, grad_grad, kin_grad)
     596              :             ELSE
     597            8 :                CALL fetch_local_feature_grads(features, density_grad, grad_grad, kin_grad)
     598              :             END IF
     599           46 :             CALL timestop(phase_handle)
     600              :          END IF
     601           48 :          IF (needs_atom_force) THEN
     602              :             CALL add_explicit_coordinate_force(atom_force, features, atom_coord_grad_t, &
     603            8 :                                                rho_r(1)%pw_grid%para%group%mepos == 0)
     604            8 :             IF (features%atom_partition == skala_gpw_atom_partition_smooth) THEN
     605              :                CALL add_smooth_partition_force(atom_force, features, particle_set, cell, rho_r, &
     606            8 :                                                grid_weight_grad_t, atomic_grid_weight_grad_t)
     607              :             END IF
     608              :             have_atom_coord_grad = .TRUE.
     609              :          END IF
     610              : 
     611           48 :          CALL timeset("skala_gpw_vxc_unpack", phase_handle)
     612           48 :          IF (native_grid_diagnostics) THEN
     613          240 :             bo = rho_r(1)%pw_grid%bounds_local
     614           24 :             density_contraction = 0.0_dp
     615           24 :             tau_contraction = 0.0_dp
     616           24 :             ipt = 0
     617         1004 :             DO k = bo(1, 3), bo(2, 3)
     618        41848 :                DO j = bo(1, 2), bo(2, 2)
     619       905054 :                   DO i = bo(1, 1), bo(2, 1)
     620       863230 :                      ipt = ipt + 1
     621       904074 :                      IF (nspins == 1) THEN
     622              :                         density_contraction = density_contraction + rho_r(1)%array(i, j, k)* &
     623       589855 :                                               0.5_dp*(density_grad(ipt, 1) + density_grad(ipt, 2))
     624              :                         tau_contraction = tau_contraction + tau(1)%array(i, j, k)* &
     625       589855 :                                           0.5_dp*(kin_grad(ipt, 1) + kin_grad(ipt, 2))
     626              :                      ELSE
     627       820125 :                         DO ispin = 1, nspins
     628              :                            density_contraction = density_contraction + &
     629       546750 :                                                  rho_r(ispin)%array(i, j, k)*density_grad(ipt, ispin)
     630              :                            tau_contraction = tau_contraction + &
     631       820125 :                                              tau(ispin)%array(i, j, k)*kin_grad(ipt, ispin)
     632              :                         END DO
     633              :                      END IF
     634              :                   END DO
     635              :                END DO
     636              :             END DO
     637           24 :             CALL rho_r(1)%pw_grid%para%group%sum(density_contraction)
     638           24 :             CALL rho_r(1)%pw_grid%para%group%sum(tau_contraction)
     639           24 :             IF (rho_r(1)%pw_grid%para%group%mepos == 0) THEN
     640           12 :                iw = cp_logger_get_default_io_unit()
     641           12 :                IF (iw > 0) THEN
     642              :                   WRITE (iw, "(T2,A,1X,ES20.10)") &
     643           12 :                      "SKALA_GPW| XC density-gradient contraction", density_contraction
     644              :                   WRITE (iw, "(T2,A,1X,ES20.10)") &
     645           12 :                      "SKALA_GPW| XC kinetic-gradient contraction", tau_contraction
     646              :                END IF
     647              :             END IF
     648              :          END IF
     649           48 :          IF (compute_virial) THEN
     650            6 :             IF (native_grid_diagnostics) virial_before = virial_xc
     651            6 :             CALL build_virial_from_feature_grads(virial_xc, rho_set, rho_r, grad_grad)
     652            6 :             IF (native_grid_diagnostics) THEN
     653              :                CALL print_virial_delta("feature-gradient", virial_xc - virial_before, &
     654            0 :                                        rho_r(1)%pw_grid%para%group%mepos == 0)
     655            0 :                virial_before = virial_xc
     656              :             END IF
     657            6 :             IF (.NOT. have_atom_coord_grad) THEN
     658            0 :                CALL torch_tensor_grad(features%coarse_0_atomic_coords_t, atom_coord_grad_t)
     659            0 :                have_atom_coord_grad = .TRUE.
     660              :             END IF
     661              :             CALL build_static_coordinate_virial(virial_xc, features, atom_coord_grad_t, &
     662              :                                                 grid_coord_grad_t, &
     663              :                                                 rho_r(1)%pw_grid%para%group%mepos == 0, &
     664            6 :                                                 native_grid_diagnostics)
     665            6 :             IF (native_grid_diagnostics) THEN
     666              :                CALL print_virial_delta("static-coordinates", virial_xc - virial_before, &
     667            0 :                                        rho_r(1)%pw_grid%para%group%mepos == 0)
     668            0 :                virial_before = virial_xc
     669              :             END IF
     670            6 :             IF (features%atom_partition == skala_gpw_atom_partition_smooth) THEN
     671              :                CALL build_smooth_partition_virial(virial_xc, features, particle_set, cell, rho_r, &
     672            6 :                                                   grid_weight_grad_t, atomic_grid_weight_grad_t)
     673            6 :                IF (native_grid_diagnostics) THEN
     674              :                   CALL print_virial_delta("smooth-partition", virial_xc - virial_before, &
     675            0 :                                           rho_r(1)%pw_grid%para%group%mepos == 0)
     676              :                   virial_before = virial_xc
     677              :                END IF
     678              :             END IF
     679              :          END IF
     680           48 :          CALL omp_set_num_threads(omp_max_threads_restore)
     681              :          CALL build_vxc_from_feature_grads(vxc_rho, vxc_tau, rho_r, pw_pool, &
     682              :                                            density_grad, grad_grad, kin_grad, &
     683           48 :                                            xc_deriv_method_id)
     684           48 :          CALL timestop(phase_handle)
     685              : 
     686           48 :          CALL timeset("skala_gpw_grad_release", phase_handle)
     687           48 :          DEALLOCATE (density_grad, grad_grad, kin_grad)
     688           48 :          IF (have_atom_coord_grad) CALL torch_tensor_release(atom_coord_grad_t)
     689           48 :          CALL timestop(phase_handle)
     690              :       END IF
     691              : 
     692           48 :       CALL timeset("skala_gpw_cleanup", phase_handle)
     693           48 :       IF (.NOT. use_atom_subchunks .AND. has_atom_chunk_work) CALL torch_tensor_release(exc_tensor)
     694           48 :       CALL skala_gpw_feature_release(features)
     695           48 :       CALL xc_rho_set_release(rho_set, pw_pool=pw_pool)
     696           48 :       CALL torch_use_cuda(.TRUE.)
     697           48 :       CALL omp_set_num_threads(omp_max_threads_restore)
     698           48 :       CALL timestop(phase_handle)
     699              : 
     700          960 :    END SUBROUTINE skala_gpw_eval
     701              : 
     702              : ! **************************************************************************************************
     703              : !> \brief Evaluate the derivative of native SKALA XC energy with respect to CP2K's external
     704              : !>        real-space integration-weight multiplier.
     705              : !> \param weight_deriv_r ...
     706              : !> \param rho_r ...
     707              : !> \param rho_g ...
     708              : !> \param tau ...
     709              : !> \param xc_section ...
     710              : !> \param weights ...
     711              : !> \param pw_pool ...
     712              : !> \param particle_set ...
     713              : !> \param cell ...
     714              : ! **************************************************************************************************
     715            0 :    SUBROUTINE skala_gpw_weight_derivative(weight_deriv_r, rho_r, rho_g, tau, xc_section, &
     716              :                                           weights, pw_pool, particle_set, cell)
     717              :       TYPE(pw_r3d_rs_type), INTENT(INOUT)                :: weight_deriv_r
     718              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: rho_r
     719              :       TYPE(pw_c1d_gs_type), DIMENSION(:), POINTER        :: rho_g
     720              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: tau
     721              :       TYPE(section_vals_type), POINTER                   :: xc_section
     722              :       TYPE(pw_r3d_rs_type), POINTER                      :: weights
     723              :       TYPE(pw_pool_type), POINTER                        :: pw_pool
     724              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     725              :       TYPE(cell_type), POINTER                           :: cell
     726              : 
     727              :       CHARACTER(len=default_path_length)                 :: model_path
     728              :       INTEGER :: feature_begin, feature_end, feature_pos, i, iatom, j, k, local_row, &
     729              :          native_grid_atom_partition, native_grid_cuda_device, natom, nspins, &
     730              :          omp_max_threads_restore, row, selected_cuda_device, xc_deriv_method_id, xc_rho_smooth_id
     731              :       INTEGER, DIMENSION(2, 3)                           :: bo
     732              :       LOGICAL                                            :: lsd, native_grid_use_cuda
     733            0 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: included
     734              :       REAL(KIND=dp)                                      :: exc, local_derivative
     735            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: partition_weights
     736            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: atom_coords_pbc
     737            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: dweights_datom, dweights_dstrain
     738              :       REAL(KIND=dp), DIMENSION(3)                        :: grid_point
     739            0 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: grid_weight_grad
     740              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     741            0 :       TYPE(skala_gpw_feature_type)                       :: features
     742              :       TYPE(torch_tensor_type)                            :: exc_tensor, grid_weight_grad_t
     743              :       TYPE(xc_rho_cflags_type)                           :: needs
     744              :       TYPE(xc_rho_set_type)                              :: rho_set
     745              : 
     746            0 :       CPASSERT(ASSOCIATED(rho_r))
     747            0 :       CPASSERT(ASSOCIATED(rho_g))
     748            0 :       CPASSERT(ASSOCIATED(tau))
     749            0 :       CALL pw_zero(weight_deriv_r)
     750            0 :       omp_max_threads_restore = omp_get_max_threads()
     751              : 
     752            0 :       nspins = SIZE(rho_r)
     753            0 :       lsd = (nspins /= 1)
     754            0 :       CALL get_skala_model_path(xc_section, model_path)
     755            0 :       gauxc_section => get_gauxc_section(xc_section)
     756            0 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_USE_CUDA", l_val=native_grid_use_cuda)
     757              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_CUDA_DEVICE", &
     758            0 :                                 i_val=native_grid_cuda_device)
     759              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_PARTITION", &
     760            0 :                                 i_val=native_grid_atom_partition)
     761            0 :       SELECT CASE (native_grid_atom_partition)
     762              :       CASE (1)
     763            0 :          native_grid_atom_partition = skala_gpw_atom_partition_hard
     764              :       CASE (2)
     765            0 :          native_grid_atom_partition = skala_gpw_atom_partition_smooth
     766              :       CASE DEFAULT
     767              :          CALL cp_abort(__LOCATION__, &
     768            0 :                        "Unknown GAUXC%NATIVE_GRID_ATOM_PARTITION value.")
     769              :       END SELECT
     770            0 :       IF (native_grid_atom_partition == skala_gpw_atom_partition_hard) THEN
     771            0 :          native_grid_atom_partition = skala_gpw_atom_partition_smooth
     772              :       END IF
     773              : 
     774            0 :       CALL torch_use_cuda(native_grid_use_cuda)
     775              :       selected_cuda_device = configure_native_grid_cuda( &
     776            0 :                              native_grid_use_cuda, native_grid_cuda_device, rho_r(1)%pw_grid%para%group)
     777            0 :       CALL ensure_model_loaded(model_path, selected_cuda_device, xc_section)
     778              : 
     779            0 :       IF (lsd) THEN
     780            0 :          needs%rho_spin = .TRUE.
     781            0 :          needs%drho_spin = .TRUE.
     782            0 :          needs%tau_spin = .TRUE.
     783              :       ELSE
     784            0 :          needs%rho = .TRUE.
     785            0 :          needs%drho = .TRUE.
     786            0 :          needs%tau = .TRUE.
     787              :       END IF
     788              : 
     789            0 :       CALL section_vals_val_get(xc_section, "XC_GRID%XC_DERIV", i_val=xc_deriv_method_id)
     790            0 :       CALL section_vals_val_get(xc_section, "XC_GRID%XC_SMOOTH_RHO", i_val=xc_rho_smooth_id)
     791              : 
     792              :       CALL xc_rho_set_create(rho_set, &
     793              :                              rho_r(1)%pw_grid%bounds_local, &
     794              :                              rho_cutoff=section_get_rval(xc_section, "density_cutoff"), &
     795              :                              drho_cutoff=section_get_rval(xc_section, "gradient_cutoff"), &
     796            0 :                              tau_cutoff=section_get_rval(xc_section, "tau_cutoff"))
     797              :       CALL xc_rho_set_update(rho_set, rho_r, rho_g, tau, needs, &
     798            0 :                              xc_deriv_method_id, xc_rho_smooth_id, pw_pool)
     799              : 
     800              :       CALL skala_gpw_feature_build(features, rho_set, rho_r, particle_set, cell, &
     801              :                                    requires_grad=.FALSE., weights=weights, &
     802              :                                    requires_coordinate_grad=.FALSE., &
     803              :                                    requires_stress_grad=.TRUE., &
     804              :                                    use_atom_chunks=.FALSE., route_atom_chunks=.FALSE., &
     805            0 :                                    atom_partition=native_grid_atom_partition)
     806              :       CALL skala_torch_model_get_exc(cached_model, features%inputs, features%grid_weights_t, &
     807            0 :                                      exc_tensor, exc)
     808            0 :       CALL torch_tensor_backward_scalar(exc_tensor)
     809            0 :       NULLIFY (grid_weight_grad)
     810            0 :       CALL torch_tensor_grad(features%grid_weights_t, grid_weight_grad_t)
     811            0 :       CALL torch_tensor_data_ptr(grid_weight_grad_t, grid_weight_grad)
     812              : 
     813            0 :       natom = SIZE(particle_set)
     814            0 :       IF (native_grid_atom_partition == skala_gpw_atom_partition_smooth) THEN
     815              :          ALLOCATE (atom_coords_pbc(3, natom), included(natom), partition_weights(natom), &
     816            0 :                    dweights_datom(3, natom, natom), dweights_dstrain(3, 3, natom))
     817            0 :          DO iatom = 1, natom
     818            0 :             atom_coords_pbc(:, iatom) = pbc(particle_set(iatom)%r, cell, positive_range=.TRUE.)
     819              :          END DO
     820              :       END IF
     821              : 
     822            0 :       bo = rho_r(1)%pw_grid%bounds_local
     823            0 :       local_row = 0
     824            0 :       DO k = bo(1, 3), bo(2, 3)
     825            0 :          DO j = bo(1, 2), bo(2, 2)
     826            0 :             DO i = bo(1, 1), bo(2, 1)
     827            0 :                local_row = local_row + 1
     828            0 :                feature_begin = features%local_feature_offsets(local_row)
     829            0 :                feature_end = features%local_feature_offsets(local_row + 1) - 1
     830            0 :                local_derivative = 0.0_dp
     831            0 :                IF (native_grid_atom_partition == skala_gpw_atom_partition_hard) THEN
     832            0 :                   CPASSERT(feature_end == feature_begin)
     833            0 :                   row = features%local_feature_rows(feature_begin)
     834            0 :                   local_derivative = grid_weight_grad(row)
     835              :                ELSE
     836            0 :                   grid_point = native_grid_coordinate(rho_r(1)%pw_grid, [i, j, k])
     837              :                   CALL skala_gpw_smooth_partition_derivatives( &
     838              :                      grid_point, atom_coords_pbc, cell, partition_weights, included, &
     839            0 :                      dweights_datom, dweights_dstrain)
     840            0 :                   CPASSERT(feature_end - feature_begin + 1 == COUNT(included))
     841            0 :                   feature_pos = feature_begin
     842            0 :                   DO iatom = 1, natom
     843            0 :                      IF (.NOT. included(iatom)) CYCLE
     844            0 :                      row = features%local_feature_rows(feature_pos)
     845              :                      local_derivative = local_derivative + &
     846            0 :                                         partition_weights(iatom)*grid_weight_grad(row)
     847            0 :                      feature_pos = feature_pos + 1
     848              :                   END DO
     849            0 :                   CPASSERT(feature_pos == feature_end + 1)
     850              :                END IF
     851            0 :                weight_deriv_r%array(i, j, k) = local_derivative
     852              :             END DO
     853              :          END DO
     854              :       END DO
     855            0 :       CPASSERT(local_row == features%nflat_local)
     856              : 
     857            0 :       IF (ALLOCATED(atom_coords_pbc)) THEN
     858            0 :          DEALLOCATE (atom_coords_pbc, dweights_datom, dweights_dstrain, included, &
     859            0 :                      partition_weights)
     860              :       END IF
     861            0 :       CALL torch_tensor_release(grid_weight_grad_t)
     862            0 :       CALL torch_tensor_release(exc_tensor)
     863            0 :       CALL skala_gpw_feature_release(features)
     864            0 :       CALL xc_rho_set_release(rho_set, pw_pool=pw_pool)
     865            0 :       CALL torch_use_cuda(.TRUE.)
     866            0 :       CALL omp_set_num_threads(omp_max_threads_restore)
     867              : 
     868            0 :    END SUBROUTINE skala_gpw_weight_derivative
     869              : 
     870              : ! **************************************************************************************************
     871              : !> \brief Evaluate SKALA on a GAPW one-center atomic grid.
     872              : !> \param xc_section ...
     873              : !> \param grid_atom ...
     874              : !> \param group ...
     875              : !> \param atom_coord ...
     876              : !> \param rho ...
     877              : !> \param drho ...
     878              : !> \param tau ...
     879              : !> \param weights ...
     880              : !> \param lsd ...
     881              : !> \param nspins ...
     882              : !> \param na ...
     883              : !> \param nr ...
     884              : !> \param exc ...
     885              : !> \param vxc ...
     886              : !> \param vxg ...
     887              : !> \param vtau ...
     888              : !> \param energy_only ...
     889              : !> \param atom_force ...
     890              : !> \param atom_virial ...
     891              : ! **************************************************************************************************
     892            4 :    SUBROUTINE skala_gapw_atom_vxc_of_r(xc_section, grid_atom, group, atom_coord, &
     893            4 :                                        rho, drho, tau, weights, lsd, nspins, na, nr, &
     894              :                                        exc, vxc, vxg, vtau, energy_only, atom_force, atom_virial)
     895              :       TYPE(section_vals_type), POINTER                   :: xc_section
     896              :       TYPE(grid_atom_type), POINTER                      :: grid_atom
     897              : 
     898              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
     899              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: atom_coord
     900              :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: rho, tau, vxc, vtau
     901              :       REAL(KIND=dp), DIMENSION(:, :, :, :), POINTER      :: drho, vxg
     902              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: weights
     903              :       LOGICAL, INTENT(IN)                                :: lsd
     904              :       INTEGER, INTENT(IN)                                :: nspins, na, nr
     905              :       REAL(KIND=dp), INTENT(OUT)                         :: exc
     906              :       LOGICAL, INTENT(IN), OPTIONAL                      :: energy_only
     907              :       REAL(KIND=dp), DIMENSION(3), INTENT(OUT), &
     908              :          OPTIONAL                                        :: atom_force
     909              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(OUT), &
     910              :          OPTIONAL                                        :: atom_virial
     911              : 
     912              :       CHARACTER(len=default_path_length)                 :: model_path
     913              :       INTEGER                                            :: ia, idir, ir, native_grid_cuda_device, &
     914              :                                                             jdir, nflat, omp_max_threads_restore, row, &
     915              :                                                             selected_cuda_device
     916            4 :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:)     :: atomic_grid_sizes
     917            4 :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:, :)  :: atomic_grid_size_bound_shape
     918              :       LOGICAL                                            :: need_coord_grad, my_energy_only, native_grid_use_cuda
     919              :       REAL(KIND=dp)                                      :: tmp
     920            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: atomic_grid_weights, grid_weights
     921            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: coarse_0_atomic_coords, density, &
     922            4 :                                                             grid_coords, kin
     923            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: grad
     924            4 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: atom_coord_grad, density_grad, &
     925            4 :                                                             grid_coord_grad, kin_grad
     926            4 :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: grad_grad
     927              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
     928              :       TYPE(torch_dict_type)                              :: inputs
     929              :       TYPE(torch_tensor_type)                            :: atomic_grid_size_bound_shape_t, &
     930              :                                                             atomic_grid_sizes_t, &
     931              :                                                             atomic_grid_weights_t, &
     932              :                                                             atom_coord_grad_t, &
     933              :                                                             coarse_0_atomic_coords_t, density_t, &
     934              :                                                             density_grad_t, exc_tensor, grad_t, &
     935              :                                                             grad_grad_t, grid_coord_grad_t, &
     936              :                                                             grid_coords_t, grid_weights_t, kin_t, &
     937              :                                                             kin_grad_t
     938              : 
     939            0 :       CPASSERT(ASSOCIATED(xc_section))
     940            4 :       CPASSERT(ASSOCIATED(grid_atom))
     941            4 :       CPASSERT(ASSOCIATED(rho))
     942            4 :       CPASSERT(ASSOCIATED(drho))
     943            4 :       CPASSERT(ASSOCIATED(tau))
     944            4 :       omp_max_threads_restore = omp_get_max_threads()
     945              : 
     946            4 :       my_energy_only = .FALSE.
     947            4 :       IF (PRESENT(energy_only)) my_energy_only = energy_only
     948            4 :       need_coord_grad = PRESENT(atom_force) .OR. PRESENT(atom_virial)
     949            4 :       exc = 0.0_dp
     950            4 :       IF (PRESENT(atom_force)) atom_force = 0.0_dp
     951            4 :       IF (PRESENT(atom_virial)) atom_virial = 0.0_dp
     952            4 :       IF (.NOT. my_energy_only) THEN
     953        10208 :          vxc = 0.0_dp
     954        40208 :          vxg = 0.0_dp
     955        10208 :          vtau = 0.0_dp
     956              :       END IF
     957              : 
     958            4 :       CALL get_skala_model_path(xc_section, model_path)
     959            4 :       gauxc_section => get_gauxc_section(xc_section)
     960            4 :       CPASSERT(ASSOCIATED(gauxc_section))
     961            4 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_USE_CUDA", l_val=native_grid_use_cuda)
     962              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_CUDA_DEVICE", &
     963            4 :                                 i_val=native_grid_cuda_device)
     964            4 :       CALL torch_use_cuda(native_grid_use_cuda)
     965              :       selected_cuda_device = configure_native_grid_cuda( &
     966            4 :                              native_grid_use_cuda, native_grid_cuda_device, group)
     967            4 :       CALL ensure_model_loaded(model_path, selected_cuda_device, xc_section)
     968              : 
     969            4 :       nflat = na*nr
     970              :       ALLOCATE (density(nflat, 2), grad(nflat, 3, 2), kin(nflat, 2), &
     971              :                 grid_coords(3, nflat), grid_weights(nflat), &
     972              :                 atomic_grid_weights(nflat), atomic_grid_sizes(1), &
     973           52 :                 coarse_0_atomic_coords(3, 1), atomic_grid_size_bound_shape(0, nflat))
     974            4 :       density = 0.0_dp
     975            4 :       grad = 0.0_dp
     976            4 :       kin = 0.0_dp
     977            4 :       grid_coords = 0.0_dp
     978            4 :       grid_weights = 0.0_dp
     979            4 :       atomic_grid_weights = 0.0_dp
     980            4 :       atomic_grid_sizes(1) = INT(nflat, KIND=int_8)
     981              :       atomic_grid_size_bound_shape = 0_int_8
     982           16 :       coarse_0_atomic_coords(:, 1) = atom_coord
     983              : 
     984              :       row = 0
     985          204 :       DO ir = 1, nr
     986        10204 :          DO ia = 1, na
     987        10000 :             row = row + 1
     988              :             grid_coords(1, row) = atom_coord(1) + grid_atom%rad(ir)* &
     989        10000 :                                   grid_atom%sin_pol(ia)*grid_atom%cos_azi(ia)
     990              :             grid_coords(2, row) = atom_coord(2) + grid_atom%rad(ir)* &
     991        10000 :                                   grid_atom%sin_pol(ia)*grid_atom%sin_azi(ia)
     992        10000 :             grid_coords(3, row) = atom_coord(3) + grid_atom%rad(ir)*grid_atom%cos_pol(ia)
     993        10000 :             grid_weights(row) = weights(ia, ir)
     994        10000 :             atomic_grid_weights(row) = weights(ia, ir)
     995        10200 :             IF (nspins == 1) THEN
     996        30000 :                density(row, :) = 0.5_dp*rho(ia, ir, 1)
     997        40000 :                DO idir = 1, 3
     998       100000 :                   grad(row, idir, :) = 0.5_dp*drho(idir, ia, ir, 1)
     999              :                END DO
    1000        30000 :                kin(row, :) = 0.5_dp*tau(ia, ir, 1)
    1001              :             ELSE
    1002            0 :                density(row, :) = rho(ia, ir, 1:2)
    1003            0 :                DO idir = 1, 3
    1004            0 :                   grad(row, idir, :) = drho(idir, ia, ir, 1:2)
    1005              :                END DO
    1006            0 :                kin(row, :) = tau(ia, ir, 1:2)
    1007              :             END IF
    1008              :          END DO
    1009              :       END DO
    1010              : 
    1011            4 :       CALL torch_tensor_from_array(grid_coords_t, grid_coords)
    1012            4 :       CALL torch_tensor_to_device_leaf(grid_coords_t, need_coord_grad)
    1013            4 :       CALL torch_tensor_from_array(grid_weights_t, grid_weights)
    1014            4 :       CALL torch_tensor_to_device_leaf(grid_weights_t, .FALSE.)
    1015            4 :       CALL torch_tensor_from_array(atomic_grid_weights_t, atomic_grid_weights)
    1016            4 :       CALL torch_tensor_to_device_leaf(atomic_grid_weights_t, .FALSE.)
    1017            4 :       CALL torch_tensor_from_array(atomic_grid_sizes_t, atomic_grid_sizes)
    1018            4 :       CALL torch_tensor_to_device_leaf(atomic_grid_sizes_t, .FALSE.)
    1019              :       CALL torch_tensor_from_array(atomic_grid_size_bound_shape_t, &
    1020            4 :                                    atomic_grid_size_bound_shape)
    1021            4 :       CALL torch_tensor_to_device_leaf(atomic_grid_size_bound_shape_t, .FALSE.)
    1022            4 :       CALL torch_tensor_from_array(coarse_0_atomic_coords_t, coarse_0_atomic_coords)
    1023            4 :       CALL torch_tensor_to_device_leaf(coarse_0_atomic_coords_t, need_coord_grad)
    1024            4 :       CALL torch_tensor_from_array(density_t, density)
    1025            4 :       CALL torch_tensor_to_device_leaf(density_t,.NOT. my_energy_only)
    1026            4 :       CALL torch_tensor_from_array(grad_t, grad)
    1027            4 :       CALL torch_tensor_to_device_leaf(grad_t,.NOT. my_energy_only)
    1028            4 :       CALL torch_tensor_from_array(kin_t, kin)
    1029            4 :       CALL torch_tensor_to_device_leaf(kin_t,.NOT. my_energy_only)
    1030              : 
    1031            4 :       CALL torch_dict_create(inputs)
    1032            4 :       CALL torch_dict_insert(inputs, "grid_coords", grid_coords_t)
    1033            4 :       CALL torch_dict_insert(inputs, "grid_weights", grid_weights_t)
    1034            4 :       CALL torch_dict_insert(inputs, "atomic_grid_weights", atomic_grid_weights_t)
    1035            4 :       CALL torch_dict_insert(inputs, "atomic_grid_sizes", atomic_grid_sizes_t)
    1036              :       CALL torch_dict_insert(inputs, "atomic_grid_size_bound_shape", &
    1037            4 :                              atomic_grid_size_bound_shape_t)
    1038            4 :       CALL torch_dict_insert(inputs, "density", density_t)
    1039            4 :       CALL torch_dict_insert(inputs, "grad", grad_t)
    1040            4 :       CALL torch_dict_insert(inputs, "kin", kin_t)
    1041            4 :       CALL torch_dict_insert(inputs, "coarse_0_atomic_coords", coarse_0_atomic_coords_t)
    1042              : 
    1043            4 :       CALL skala_torch_model_get_exc(cached_model, inputs, grid_weights_t, exc_tensor, exc)
    1044              : 
    1045            4 :       IF (.NOT. my_energy_only) THEN
    1046            4 :          NULLIFY (atom_coord_grad, density_grad, grad_grad, grid_coord_grad, kin_grad)
    1047            4 :          CALL torch_tensor_backward_scalar(exc_tensor)
    1048            4 :          IF (need_coord_grad) THEN
    1049            4 :             CALL torch_tensor_grad(grid_coords_t, grid_coord_grad_t)
    1050            4 :             CALL torch_tensor_grad(coarse_0_atomic_coords_t, atom_coord_grad_t)
    1051            4 :             CALL torch_tensor_data_ptr(grid_coord_grad_t, grid_coord_grad)
    1052            4 :             CALL torch_tensor_data_ptr(atom_coord_grad_t, atom_coord_grad)
    1053            4 :             IF (PRESENT(atom_force)) THEN
    1054           16 :                atom_force(:) = atom_coord_grad(:, 1)
    1055        10004 :                DO row = 1, nflat
    1056        40004 :                   atom_force(:) = atom_force(:) + grid_coord_grad(:, row)
    1057              :                END DO
    1058              :             END IF
    1059            4 :             IF (PRESENT(atom_virial)) THEN
    1060        10004 :                DO row = 1, nflat
    1061        40004 :                   DO idir = 1, 3
    1062       130000 :                      DO jdir = 1, 3
    1063        90000 :                         tmp = grid_coord_grad(idir, row)*coarse_0_atomic_coords(jdir, 1)
    1064       120000 :                         atom_virial(idir, jdir) = atom_virial(idir, jdir) + tmp
    1065              :                      END DO
    1066              :                   END DO
    1067              :                END DO
    1068           16 :                DO idir = 1, 3
    1069           52 :                   DO jdir = 1, 3
    1070           36 :                      tmp = atom_coord_grad(idir, 1)*coarse_0_atomic_coords(jdir, 1)
    1071           48 :                      atom_virial(idir, jdir) = atom_virial(idir, jdir) + tmp
    1072              :                   END DO
    1073              :                END DO
    1074              :             END IF
    1075              :          END IF
    1076              :          CALL torch_tensor_grad_batch3(density_t, grad_t, kin_t, density_grad_t, &
    1077            4 :                                        grad_grad_t, kin_grad_t)
    1078            4 :          CALL torch_tensor_data_ptr(density_grad_t, density_grad)
    1079            4 :          CALL torch_tensor_data_ptr(grad_grad_t, grad_grad)
    1080            4 :          CALL torch_tensor_data_ptr(kin_grad_t, kin_grad)
    1081              : 
    1082            4 :          row = 0
    1083          204 :          DO ir = 1, nr
    1084        10204 :             DO ia = 1, na
    1085        10000 :                row = row + 1
    1086        10200 :                IF (lsd) THEN
    1087            0 :                   vxc(ia, ir, 1:2) = density_grad(row, 1:2)
    1088            0 :                   DO idir = 1, 3
    1089            0 :                      vxg(idir, ia, ir, 1:2) = grad_grad(row, idir, 1:2)
    1090              :                   END DO
    1091            0 :                   vtau(ia, ir, 1:2) = kin_grad(row, 1:2)
    1092              :                ELSE
    1093        10000 :                   vxc(ia, ir, 1) = 0.5_dp*(density_grad(row, 1) + density_grad(row, 2))
    1094        40000 :                   DO idir = 1, 3
    1095              :                      vxg(idir, ia, ir, 1) = &
    1096        40000 :                         0.5_dp*(grad_grad(row, idir, 1) + grad_grad(row, idir, 2))
    1097              :                   END DO
    1098        10000 :                   vtau(ia, ir, 1) = 0.5_dp*(kin_grad(row, 1) + kin_grad(row, 2))
    1099              :                END IF
    1100              :             END DO
    1101              :          END DO
    1102              : 
    1103            4 :          CALL torch_tensor_release(density_grad_t)
    1104            4 :          CALL torch_tensor_release(grad_grad_t)
    1105            4 :          CALL torch_tensor_release(kin_grad_t)
    1106            4 :          IF (need_coord_grad) THEN
    1107            4 :             CALL torch_tensor_release(grid_coord_grad_t)
    1108            4 :             CALL torch_tensor_release(atom_coord_grad_t)
    1109              :          END IF
    1110              :       END IF
    1111              : 
    1112            4 :       CALL torch_tensor_release(exc_tensor)
    1113            4 :       CALL torch_tensor_release(density_t)
    1114            4 :       CALL torch_tensor_release(grad_t)
    1115            4 :       CALL torch_tensor_release(kin_t)
    1116            4 :       CALL torch_tensor_release(grid_coords_t)
    1117            4 :       CALL torch_tensor_release(grid_weights_t)
    1118            4 :       CALL torch_tensor_release(atomic_grid_weights_t)
    1119            4 :       CALL torch_tensor_release(atomic_grid_sizes_t)
    1120            4 :       CALL torch_tensor_release(atomic_grid_size_bound_shape_t)
    1121            4 :       CALL torch_tensor_release(coarse_0_atomic_coords_t)
    1122            4 :       CALL torch_dict_release(inputs)
    1123            0 :       DEALLOCATE (atomic_grid_size_bound_shape, atomic_grid_sizes, atomic_grid_weights, &
    1124            4 :                   coarse_0_atomic_coords, density, grad, grid_coords, grid_weights, kin)
    1125            4 :       CALL torch_use_cuda(.TRUE.)
    1126            4 :       CALL omp_set_num_threads(omp_max_threads_restore)
    1127              : 
    1128           12 :    END SUBROUTINE skala_gapw_atom_vxc_of_r
    1129              : 
    1130              : ! **************************************************************************************************
    1131              : !> \brief Evaluate a rank-local set of complete atom blocks and sum their SKALA energies.
    1132              : !> \param xc_section ...
    1133              : !> \param group ...
    1134              : !> \param density ...
    1135              : !> \param grad ...
    1136              : !> \param kin ...
    1137              : !> \param grid_coords ...
    1138              : !> \param grid_weights ...
    1139              : !> \param atomic_grid_weights ...
    1140              : !> \param atomic_grid_sizes ...
    1141              : !> \param atomic_coords ...
    1142              : !> \param exc ...
    1143              : !> \param density_grad_out ...
    1144              : !> \param grad_grad_out ...
    1145              : !> \param kin_grad_out ...
    1146              : !> \param grid_coord_grad_out ...
    1147              : !> \param grid_weight_grad_out ...
    1148              : !> \param atomic_grid_weight_grad_out ...
    1149              : !> \param atom_coord_grad_out ...
    1150              : ! **************************************************************************************************
    1151          252 :    SUBROUTINE skala_gapw_atom_composite_energy(xc_section, group, density, grad, kin, &
    1152              :                                                grid_coords, grid_weights, atomic_grid_weights, &
    1153              :                                                atomic_grid_sizes, atomic_coords, exc, &
    1154              :                                                density_grad_out, grad_grad_out, kin_grad_out, &
    1155              :                                                grid_coord_grad_out, grid_weight_grad_out, &
    1156              :                                                atomic_grid_weight_grad_out, atom_coord_grad_out)
    1157              :       TYPE(section_vals_type), POINTER                   :: xc_section
    1158              : 
    1159              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    1160              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), INTENT(IN) :: density, grid_coords, &
    1161              :                                                                  atomic_coords, kin
    1162              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), INTENT(IN) :: grad
    1163              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), INTENT(IN) :: grid_weights, atomic_grid_weights
    1164              :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:), INTENT(IN) :: atomic_grid_sizes
    1165              :       REAL(KIND=dp), INTENT(OUT)                         :: exc
    1166              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), INTENT(OUT), OPTIONAL :: density_grad_out, &
    1167              :                                                                             kin_grad_out, &
    1168              :                                                                             grid_coord_grad_out, &
    1169              :                                                                             atom_coord_grad_out
    1170              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), INTENT(OUT), OPTIONAL :: grad_grad_out
    1171              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), INTENT(OUT), OPTIONAL :: grid_weight_grad_out, &
    1172              :                                                                          atomic_grid_weight_grad_out
    1173              : 
    1174              :       CHARACTER(len=default_path_length)                 :: model_path
    1175              :       INTEGER :: atom_begin, atom_count, chunk_max_rows, chunk_row_count, chunk_row_start, &
    1176              :                  ichunk, local_natom, local_nrow, max_grid_size, native_grid_cuda_device, &
    1177              :                  nchunks, omp_max_threads_restore, phase_handle, selected_cuda_device
    1178          252 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: chunk_atom_begin, chunk_atom_count, &
    1179          252 :                                                             chunk_row_begin, chunk_row_counts
    1180          252 :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:, :)  :: atomic_grid_size_bound_shape
    1181              :       LOGICAL :: active_rank, native_grid_atom_chunks, native_grid_use_cuda, &
    1182              :                  need_coordinate_derivatives, need_derivatives
    1183              :       REAL(KIND=dp)                                      :: chunk_exc
    1184          252 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: atomic_grid_weight_grad, grid_weight_grad
    1185          252 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: atom_coord_grad, density_grad, &
    1186          252 :                                                             grid_coord_grad, kin_grad
    1187          252 :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: grad_grad
    1188              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
    1189              :       TYPE(torch_dict_type)                              :: inputs
    1190              :       TYPE(torch_tensor_type)                            :: atomic_grid_size_bound_shape_t, &
    1191              :                                                             atomic_grid_sizes_t, &
    1192              :                                                             atomic_grid_sizes_chunk_t, &
    1193              :                                                             atomic_grid_weight_grad_t, &
    1194              :                                                             atomic_grid_weights_chunk_t, &
    1195              :                                                             atom_coord_grad_t, atomic_coords_chunk_t, &
    1196              :                                                             density_chunk_t, density_grad_t, &
    1197              :                                                             exc_tensor, grad_chunk_t, grad_grad_t, &
    1198              :                                                             grid_coord_grad_t, grid_coords_chunk_t, &
    1199              :                                                             grid_weight_grad_t, grid_weights_chunk_t, &
    1200              :                                                             kin_chunk_t, kin_grad_t
    1201              :       TYPE(torch_tensor_type), TARGET                    :: atomic_coords_local_t, &
    1202              :                                                             atomic_grid_weights_local_t, &
    1203              :                                                             density_local_t, grad_local_t, &
    1204              :                                                             grid_coords_local_t, grid_weights_local_t, &
    1205              :                                                             kin_local_t
    1206              :       TYPE(torch_tensor_type), POINTER                   :: atomic_coords_t, atomic_grid_weights_t, &
    1207              :                                                             density_t, grad_t, grid_coords_t, &
    1208              :                                                             grid_weights_t, kin_t
    1209              : 
    1210            0 :       CPASSERT(ASSOCIATED(xc_section))
    1211          252 :       CPASSERT(SIZE(density, 1) == SIZE(grid_weights))
    1212          252 :       CPASSERT(SIZE(density, 1) == SIZE(atomic_grid_weights))
    1213          252 :       CPASSERT(SIZE(density, 1) == SIZE(grid_coords, 2))
    1214          252 :       CPASSERT(SIZE(density, 1) == SIZE(grad, 1))
    1215          252 :       CPASSERT(SIZE(density, 1) == SIZE(kin, 1))
    1216          252 :       CPASSERT(SIZE(density, 2) == 2)
    1217          252 :       CPASSERT(SIZE(grad, 2) == 3)
    1218          252 :       CPASSERT(SIZE(grad, 3) == 2)
    1219          252 :       CPASSERT(SIZE(kin, 2) == 2)
    1220          252 :       CPASSERT(SIZE(atomic_grid_sizes) == SIZE(atomic_coords, 2))
    1221          505 :       CPASSERT(SUM(atomic_grid_sizes) == INT(SIZE(density, 1), KIND=int_8))
    1222          252 :       need_derivatives = PRESENT(density_grad_out)
    1223          252 :       CPASSERT(PRESENT(grad_grad_out) .EQV. need_derivatives)
    1224          252 :       CPASSERT(PRESENT(kin_grad_out) .EQV. need_derivatives)
    1225          252 :       need_coordinate_derivatives = PRESENT(grid_coord_grad_out)
    1226          252 :       CPASSERT(PRESENT(grid_weight_grad_out) .EQV. need_coordinate_derivatives)
    1227          252 :       CPASSERT(PRESENT(atomic_grid_weight_grad_out) .EQV. need_coordinate_derivatives)
    1228          252 :       CPASSERT(PRESENT(atom_coord_grad_out) .EQV. need_coordinate_derivatives)
    1229          252 :       CPASSERT((.NOT. need_coordinate_derivatives) .OR. need_derivatives)
    1230          252 :       omp_max_threads_restore = omp_get_max_threads()
    1231              : 
    1232          252 :       local_nrow = SIZE(density, 1)
    1233          252 :       local_natom = SIZE(atomic_grid_sizes)
    1234          252 :       active_rank = local_natom > 0
    1235          252 :       CPASSERT(active_rank .EQV. (local_nrow > 0))
    1236              : 
    1237          252 :       CALL get_skala_model_path(xc_section, model_path)
    1238          252 :       gauxc_section => get_gauxc_section(xc_section)
    1239          252 :       CPASSERT(ASSOCIATED(gauxc_section))
    1240          252 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_USE_CUDA", l_val=native_grid_use_cuda)
    1241              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_CUDA_DEVICE", &
    1242          252 :                                 i_val=native_grid_cuda_device)
    1243              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_CHUNKS", &
    1244          252 :                                 l_val=native_grid_atom_chunks)
    1245              :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_ATOM_CHUNK_MAX_ROWS", &
    1246          252 :                                 i_val=chunk_max_rows)
    1247          252 :       IF (chunk_max_rows < -1) THEN
    1248              :          CALL cp_abort(__LOCATION__, &
    1249            0 :                        "GAUXC%NATIVE_GRID_ATOM_CHUNK_MAX_ROWS must be -1, zero, or positive.")
    1250              :       END IF
    1251          252 :       IF (.NOT. native_grid_atom_chunks) chunk_max_rows = 0
    1252          252 :       IF (chunk_max_rows == -1) THEN
    1253          242 :          chunk_max_rows = auto_atom_composite_chunk_max_rows(atomic_grid_sizes, group)
    1254              :       END IF
    1255          252 :       CALL torch_use_cuda(native_grid_use_cuda)
    1256              :       selected_cuda_device = configure_native_grid_cuda( &
    1257          252 :                              native_grid_use_cuda, native_grid_cuda_device, group)
    1258          252 :       exc = 0.0_dp
    1259          252 :       IF (active_rank) THEN
    1260          230 :          CALL timeset("skala_atom_tensor_setup", phase_handle)
    1261          230 :          CALL ensure_model_loaded(model_path, selected_cuda_device, xc_section)
    1262              :          CALL atom_composite_chunk_layout(atomic_grid_sizes, chunk_max_rows, &
    1263              :                                           chunk_atom_begin, chunk_atom_count, &
    1264          230 :                                           chunk_row_begin, chunk_row_counts)
    1265          230 :          nchunks = SIZE(chunk_atom_begin)
    1266          230 :          CPASSERT(nchunks > 0)
    1267              : 
    1268          230 :          IF (native_grid_use_cuda) THEN
    1269            0 :             grid_coords_t => cached_atom_cuda_tensors%grid_coords_t
    1270            0 :             grid_weights_t => cached_atom_cuda_tensors%grid_weights_t
    1271            0 :             atomic_grid_weights_t => cached_atom_cuda_tensors%atomic_grid_weights_t
    1272            0 :             atomic_coords_t => cached_atom_cuda_tensors%atomic_coords_t
    1273            0 :             density_t => cached_atom_cuda_tensors%density_t
    1274            0 :             grad_t => cached_atom_cuda_tensors%grad_t
    1275            0 :             kin_t => cached_atom_cuda_tensors%kin_t
    1276              :             CALL torch_tensor_reset_from_array( &
    1277            0 :                grid_coords_t, grid_coords, need_coordinate_derivatives)
    1278              :             CALL torch_tensor_reset_from_array( &
    1279            0 :                grid_weights_t, grid_weights, need_coordinate_derivatives)
    1280              :             CALL torch_tensor_reset_from_array( &
    1281            0 :                atomic_grid_weights_t, atomic_grid_weights, need_coordinate_derivatives)
    1282              :             CALL torch_tensor_reset_from_array( &
    1283            0 :                atomic_coords_t, atomic_coords, need_coordinate_derivatives)
    1284            0 :             CALL torch_tensor_reset_from_array(density_t, density, need_derivatives)
    1285            0 :             CALL torch_tensor_reset_from_array(grad_t, grad, need_derivatives)
    1286            0 :             CALL torch_tensor_reset_from_array(kin_t, kin, need_derivatives)
    1287              :          ELSE
    1288          230 :             grid_coords_t => grid_coords_local_t
    1289          230 :             grid_weights_t => grid_weights_local_t
    1290          230 :             atomic_grid_weights_t => atomic_grid_weights_local_t
    1291          230 :             atomic_coords_t => atomic_coords_local_t
    1292          230 :             density_t => density_local_t
    1293          230 :             grad_t => grad_local_t
    1294          230 :             kin_t => kin_local_t
    1295          230 :             CALL torch_tensor_from_array(grid_coords_t, grid_coords)
    1296          230 :             CALL torch_tensor_to_device_leaf(grid_coords_t, need_coordinate_derivatives)
    1297          230 :             CALL torch_tensor_from_array(grid_weights_t, grid_weights)
    1298          230 :             CALL torch_tensor_to_device_leaf(grid_weights_t, need_coordinate_derivatives)
    1299          230 :             CALL torch_tensor_from_array(atomic_grid_weights_t, atomic_grid_weights)
    1300          230 :             CALL torch_tensor_to_device_leaf(atomic_grid_weights_t, need_coordinate_derivatives)
    1301          230 :             CALL torch_tensor_from_array(atomic_coords_t, atomic_coords)
    1302          230 :             CALL torch_tensor_to_device_leaf(atomic_coords_t, need_coordinate_derivatives)
    1303          230 :             CALL torch_tensor_from_array(density_t, density)
    1304          230 :             CALL torch_tensor_to_device_leaf(density_t, need_derivatives)
    1305          230 :             CALL torch_tensor_from_array(grad_t, grad)
    1306          230 :             CALL torch_tensor_to_device_leaf(grad_t, need_derivatives)
    1307          230 :             CALL torch_tensor_from_array(kin_t, kin)
    1308          230 :             CALL torch_tensor_to_device_leaf(kin_t, need_derivatives)
    1309              :          END IF
    1310          230 :          CALL torch_tensor_from_array(atomic_grid_sizes_t, atomic_grid_sizes)
    1311          230 :          CALL torch_tensor_to_device_leaf(atomic_grid_sizes_t, .FALSE.)
    1312          230 :          CALL timestop(phase_handle)
    1313              : 
    1314          460 :          DO ichunk = 1, nchunks
    1315          230 :             atom_begin = chunk_atom_begin(ichunk)
    1316          230 :             atom_count = chunk_atom_count(ichunk)
    1317          230 :             chunk_row_start = chunk_row_begin(ichunk)
    1318          230 :             chunk_row_count = chunk_row_counts(ichunk)
    1319              :             max_grid_size = INT(MAXVAL(atomic_grid_sizes( &
    1320          483 :                                        atom_begin:atom_begin + atom_count - 1)))
    1321          460 :             ALLOCATE (atomic_grid_size_bound_shape(0, max_grid_size))
    1322              :             atomic_grid_size_bound_shape = 0_int_8
    1323              : 
    1324              :             CALL torch_tensor_narrow(grid_coords_t, 0, chunk_row_start - 1, &
    1325          230 :                                      chunk_row_count, grid_coords_chunk_t)
    1326              :             CALL torch_tensor_narrow(grid_weights_t, 0, chunk_row_start - 1, &
    1327          230 :                                      chunk_row_count, grid_weights_chunk_t)
    1328              :             CALL torch_tensor_narrow(atomic_grid_weights_t, 0, chunk_row_start - 1, &
    1329          230 :                                      chunk_row_count, atomic_grid_weights_chunk_t)
    1330              :             CALL torch_tensor_narrow(atomic_grid_sizes_t, 0, atom_begin - 1, &
    1331          230 :                                      atom_count, atomic_grid_sizes_chunk_t)
    1332              :             CALL torch_tensor_narrow(atomic_coords_t, 0, atom_begin - 1, &
    1333          230 :                                      atom_count, atomic_coords_chunk_t)
    1334              :             CALL torch_tensor_narrow(density_t, 1, chunk_row_start - 1, &
    1335          230 :                                      chunk_row_count, density_chunk_t)
    1336              :             CALL torch_tensor_narrow(grad_t, 2, chunk_row_start - 1, &
    1337          230 :                                      chunk_row_count, grad_chunk_t)
    1338              :             CALL torch_tensor_narrow(kin_t, 1, chunk_row_start - 1, &
    1339          230 :                                      chunk_row_count, kin_chunk_t)
    1340              :             CALL torch_tensor_from_array(atomic_grid_size_bound_shape_t, &
    1341          230 :                                          atomic_grid_size_bound_shape)
    1342          230 :             CALL torch_tensor_to_device_leaf(atomic_grid_size_bound_shape_t, .FALSE.)
    1343              : 
    1344          230 :             CALL torch_dict_create(inputs)
    1345          230 :             CALL torch_dict_insert(inputs, "grid_coords", grid_coords_chunk_t)
    1346          230 :             CALL torch_dict_insert(inputs, "grid_weights", grid_weights_chunk_t)
    1347          230 :             CALL torch_dict_insert(inputs, "atomic_grid_weights", atomic_grid_weights_chunk_t)
    1348          230 :             CALL torch_dict_insert(inputs, "atomic_grid_sizes", atomic_grid_sizes_chunk_t)
    1349              :             CALL torch_dict_insert(inputs, "atomic_grid_size_bound_shape", &
    1350          230 :                                    atomic_grid_size_bound_shape_t)
    1351          230 :             CALL torch_dict_insert(inputs, "density", density_chunk_t)
    1352          230 :             CALL torch_dict_insert(inputs, "grad", grad_chunk_t)
    1353          230 :             CALL torch_dict_insert(inputs, "kin", kin_chunk_t)
    1354          230 :             CALL torch_dict_insert(inputs, "coarse_0_atomic_coords", atomic_coords_chunk_t)
    1355              : 
    1356          230 :             CALL timeset("skala_atom_torch_forward", phase_handle)
    1357              :             CALL skala_torch_model_get_exc(cached_model, inputs, grid_weights_chunk_t, &
    1358          230 :                                            exc_tensor, chunk_exc)
    1359          230 :             CALL timestop(phase_handle)
    1360          230 :             exc = exc + chunk_exc
    1361          230 :             IF (need_derivatives) THEN
    1362          230 :                CALL timeset("skala_atom_torch_backward", phase_handle)
    1363          230 :                CALL torch_tensor_backward_scalar(exc_tensor)
    1364          230 :                CALL timestop(phase_handle)
    1365              :             END IF
    1366              : 
    1367          230 :             CALL torch_tensor_release(exc_tensor)
    1368          230 :             CALL torch_dict_release(inputs)
    1369          230 :             CALL torch_tensor_release(grid_coords_chunk_t)
    1370          230 :             CALL torch_tensor_release(grid_weights_chunk_t)
    1371          230 :             CALL torch_tensor_release(atomic_grid_weights_chunk_t)
    1372          230 :             CALL torch_tensor_release(atomic_grid_sizes_chunk_t)
    1373          230 :             CALL torch_tensor_release(atomic_coords_chunk_t)
    1374          230 :             CALL torch_tensor_release(density_chunk_t)
    1375          230 :             CALL torch_tensor_release(grad_chunk_t)
    1376          230 :             CALL torch_tensor_release(kin_chunk_t)
    1377          230 :             CALL torch_tensor_release(atomic_grid_size_bound_shape_t)
    1378          690 :             DEALLOCATE (atomic_grid_size_bound_shape)
    1379              :          END DO
    1380              :       END IF
    1381          252 :       CALL group%sum(exc)
    1382              : 
    1383          252 :       IF (need_derivatives) THEN
    1384            0 :          ALLOCATE (density_grad_out(local_nrow, 2), grad_grad_out(local_nrow, 3, 2), &
    1385         1698 :                    kin_grad_out(local_nrow, 2))
    1386          252 :          density_grad_out = 0.0_dp
    1387          252 :          grad_grad_out = 0.0_dp
    1388          252 :          kin_grad_out = 0.0_dp
    1389          252 :          IF (active_rank) THEN
    1390          230 :             NULLIFY (density_grad, grad_grad, kin_grad)
    1391          230 :             CALL timeset("skala_atom_grad_fetch", phase_handle)
    1392              :             CALL torch_tensor_grad_batch3(density_t, grad_t, kin_t, density_grad_t, &
    1393          230 :                                           grad_grad_t, kin_grad_t)
    1394          230 :             CALL torch_tensor_data_ptr(density_grad_t, density_grad)
    1395          230 :             CALL torch_tensor_data_ptr(grad_grad_t, grad_grad)
    1396          230 :             CALL torch_tensor_data_ptr(kin_grad_t, kin_grad)
    1397       857210 :             density_grad_out(:, :) = density_grad
    1398      2571630 :             grad_grad_out(:, :, :) = grad_grad
    1399       857210 :             kin_grad_out(:, :) = kin_grad
    1400          230 :             CALL timestop(phase_handle)
    1401              :          END IF
    1402              : 
    1403          252 :          IF (need_coordinate_derivatives) THEN
    1404           58 :             IF (active_rank) CALL timeset("skala_atom_coord_grad_fetch", phase_handle)
    1405            0 :             ALLOCATE (grid_coord_grad_out(3, local_nrow), grid_weight_grad_out(local_nrow), &
    1406          420 :                       atomic_grid_weight_grad_out(local_nrow), atom_coord_grad_out(3, local_natom))
    1407           58 :             grid_coord_grad_out = 0.0_dp
    1408           58 :             grid_weight_grad_out = 0.0_dp
    1409           58 :             atomic_grid_weight_grad_out = 0.0_dp
    1410           58 :             atom_coord_grad_out = 0.0_dp
    1411           58 :             IF (active_rank) THEN
    1412           47 :                NULLIFY (atomic_grid_weight_grad, atom_coord_grad, grid_coord_grad, grid_weight_grad)
    1413           47 :                CALL torch_tensor_grad(grid_coords_t, grid_coord_grad_t)
    1414           47 :                CALL torch_tensor_grad(grid_weights_t, grid_weight_grad_t)
    1415           47 :                CALL torch_tensor_grad(atomic_grid_weights_t, atomic_grid_weight_grad_t)
    1416           47 :                CALL torch_tensor_grad(atomic_coords_t, atom_coord_grad_t)
    1417           47 :                CALL torch_tensor_data_ptr(grid_coord_grad_t, grid_coord_grad)
    1418           47 :                CALL torch_tensor_data_ptr(grid_weight_grad_t, grid_weight_grad)
    1419           47 :                CALL torch_tensor_data_ptr(atomic_grid_weight_grad_t, atomic_grid_weight_grad)
    1420           47 :                CALL torch_tensor_data_ptr(atom_coord_grad_t, atom_coord_grad)
    1421       420207 :                grid_coord_grad_out(:, :) = grid_coord_grad
    1422       105087 :                grid_weight_grad_out(:) = grid_weight_grad
    1423       105087 :                atomic_grid_weight_grad_out(:) = atomic_grid_weight_grad
    1424          279 :                atom_coord_grad_out(:, :) = atom_coord_grad
    1425           47 :                CALL torch_tensor_release(grid_coord_grad_t)
    1426           47 :                CALL torch_tensor_release(grid_weight_grad_t)
    1427           47 :                CALL torch_tensor_release(atomic_grid_weight_grad_t)
    1428           47 :                CALL torch_tensor_release(atom_coord_grad_t)
    1429           47 :                CALL timestop(phase_handle)
    1430              :             END IF
    1431              :          END IF
    1432          241 :          IF (active_rank) THEN
    1433          230 :             CALL torch_tensor_release(density_grad_t)
    1434          230 :             CALL torch_tensor_release(grad_grad_t)
    1435          230 :             CALL torch_tensor_release(kin_grad_t)
    1436              :          END IF
    1437              :       END IF
    1438              : 
    1439          241 :       IF (active_rank) THEN
    1440          230 :          CALL timeset("skala_atom_tensor_release", phase_handle)
    1441          230 :          IF (.NOT. native_grid_use_cuda) THEN
    1442          230 :             CALL torch_tensor_release(density_t)
    1443          230 :             CALL torch_tensor_release(grad_t)
    1444          230 :             CALL torch_tensor_release(kin_t)
    1445          230 :             CALL torch_tensor_release(grid_coords_t)
    1446          230 :             CALL torch_tensor_release(grid_weights_t)
    1447          230 :             CALL torch_tensor_release(atomic_grid_weights_t)
    1448          230 :             CALL torch_tensor_release(atomic_coords_t)
    1449              :          END IF
    1450          230 :          CALL torch_tensor_release(atomic_grid_sizes_t)
    1451          230 :          DEALLOCATE (chunk_atom_begin, chunk_atom_count, chunk_row_begin, chunk_row_counts)
    1452          230 :          CALL timestop(phase_handle)
    1453              :       END IF
    1454          252 :       CALL torch_use_cuda(.TRUE.)
    1455          252 :       CALL omp_set_num_threads(omp_max_threads_restore)
    1456              : 
    1457          504 :    END SUBROUTINE skala_gapw_atom_composite_energy
    1458              : 
    1459              : ! **************************************************************************************************
    1460              : !> \brief Select an automatic row cap for atom-composite Torch calls.
    1461              : !> \param atomic_grid_sizes ...
    1462              : !> \param group ...
    1463              : !> \return ...
    1464              : ! **************************************************************************************************
    1465          242 :    FUNCTION auto_atom_composite_chunk_max_rows(atomic_grid_sizes, group) RESULT(max_rows)
    1466              :       INTEGER(KIND=int_8), DIMENSION(:), INTENT(IN)      :: atomic_grid_sizes
    1467              : 
    1468              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    1469              : 
    1470              :       INTEGER                                            :: local_max_atom_rows, local_natom, &
    1471              :                                                             local_padded_rows, max_rows
    1472              : 
    1473          242 :       local_padded_rows = 0
    1474          242 :       local_natom = SIZE(atomic_grid_sizes)
    1475          242 :       IF (local_natom > 0) THEN
    1476          463 :          local_max_atom_rows = INT(MAXVAL(atomic_grid_sizes))
    1477          220 :          IF (local_natom > atom_chunk_auto_max_rows .OR. &
    1478              :              local_max_atom_rows > atom_chunk_auto_max_rows/local_natom) THEN
    1479            0 :             local_padded_rows = atom_chunk_auto_max_rows + 1
    1480              :          ELSE
    1481          220 :             local_padded_rows = local_natom*local_max_atom_rows
    1482              :          END IF
    1483              :       END IF
    1484          242 :       CALL group%max(local_padded_rows)
    1485          242 :       IF (local_padded_rows <= atom_chunk_auto_max_rows) THEN
    1486          242 :          max_rows = 0
    1487          242 :          RETURN
    1488              :       END IF
    1489          242 :       max_rows = atom_chunk_auto_max_rows
    1490              : 
    1491              :    END FUNCTION auto_atom_composite_chunk_max_rows
    1492              : 
    1493              : ! **************************************************************************************************
    1494              : !> \brief Split contiguous atom-composite rows without dividing an atomic block.
    1495              : !> \param atomic_grid_sizes ...
    1496              : !> \param max_rows ...
    1497              : !> \param atom_begin ...
    1498              : !> \param atom_count ...
    1499              : !> \param row_begin ...
    1500              : !> \param row_count ...
    1501              : ! **************************************************************************************************
    1502          230 :    SUBROUTINE atom_composite_chunk_layout(atomic_grid_sizes, max_rows, atom_begin, atom_count, &
    1503              :                                           row_begin, row_count)
    1504              :       INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:), &
    1505              :          INTENT(IN)                                      :: atomic_grid_sizes
    1506              :       INTEGER, INTENT(IN)                                :: max_rows
    1507              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: atom_begin, atom_count, row_begin, &
    1508              :                                                             row_count
    1509              : 
    1510              :       INTEGER :: atom_begin_tmp, atom_count_tmp, atom_rows, chunk_atoms, chunk_max_atom_rows, &
    1511              :          iatom, insert_at, natom, nchunks, padded_rows_tmp, row_begin_tmp, row_count_tmp, rows, &
    1512              :          subchunk
    1513          230 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: padded_rows
    1514              : 
    1515          230 :       natom = SIZE(atomic_grid_sizes)
    1516          230 :       CPASSERT(natom > 0)
    1517          483 :       CPASSERT(ALL(atomic_grid_sizes > 0_int_8))
    1518          230 :       IF (max_rows <= 0) THEN
    1519          220 :          ALLOCATE (atom_begin(1), atom_count(1), row_begin(1), row_count(1))
    1520          220 :          atom_begin(1) = 1
    1521          220 :          atom_count(1) = natom
    1522          220 :          row_begin(1) = 1
    1523          463 :          row_count(1) = INT(SUM(atomic_grid_sizes))
    1524              :          RETURN
    1525              :       END IF
    1526              : 
    1527              :       nchunks = 1
    1528              :       chunk_atoms = 0
    1529              :       chunk_max_atom_rows = 0
    1530           20 :       DO iatom = 1, natom
    1531           10 :          atom_rows = INT(atomic_grid_sizes(iatom))
    1532           10 :          IF (chunk_atoms > 0 .AND. &
    1533              :              MAX(chunk_max_atom_rows, atom_rows) > max_rows/(chunk_atoms + 1)) THEN
    1534            0 :             nchunks = nchunks + 1
    1535            0 :             chunk_atoms = 0
    1536            0 :             chunk_max_atom_rows = 0
    1537              :          END IF
    1538           10 :          chunk_atoms = chunk_atoms + 1
    1539           20 :          chunk_max_atom_rows = MAX(chunk_max_atom_rows, atom_rows)
    1540              :       END DO
    1541           60 :       ALLOCATE (atom_begin(nchunks), atom_count(nchunks), row_begin(nchunks), row_count(nchunks))
    1542              : 
    1543           10 :       subchunk = 1
    1544           10 :       atom_begin(subchunk) = 1
    1545           10 :       row_begin(subchunk) = 1
    1546           10 :       chunk_atoms = 0
    1547           10 :       chunk_max_atom_rows = 0
    1548           10 :       rows = 0
    1549           20 :       DO iatom = 1, natom
    1550           10 :          atom_rows = INT(atomic_grid_sizes(iatom))
    1551           10 :          IF (chunk_atoms > 0 .AND. &
    1552              :              MAX(chunk_max_atom_rows, atom_rows) > max_rows/(chunk_atoms + 1)) THEN
    1553            0 :             atom_count(subchunk) = iatom - atom_begin(subchunk)
    1554            0 :             row_count(subchunk) = rows
    1555            0 :             subchunk = subchunk + 1
    1556            0 :             atom_begin(subchunk) = iatom
    1557            0 :             row_begin(subchunk) = row_begin(subchunk - 1) + row_count(subchunk - 1)
    1558            0 :             chunk_atoms = 0
    1559            0 :             chunk_max_atom_rows = 0
    1560            0 :             rows = 0
    1561              :          END IF
    1562           10 :          chunk_atoms = chunk_atoms + 1
    1563           10 :          chunk_max_atom_rows = MAX(chunk_max_atom_rows, atom_rows)
    1564           20 :          rows = rows + atom_rows
    1565              :       END DO
    1566           10 :       atom_count(subchunk) = natom - atom_begin(subchunk) + 1
    1567           10 :       row_count(subchunk) = rows
    1568           10 :       CPASSERT(subchunk == nchunks)
    1569              : 
    1570           20 :       ALLOCATE (padded_rows(nchunks))
    1571           20 :       DO subchunk = 1, nchunks
    1572           10 :          iatom = atom_begin(subchunk) + atom_count(subchunk) - 1
    1573              :          padded_rows(subchunk) = atom_count(subchunk)* &
    1574           30 :                                  INT(MAXVAL(atomic_grid_sizes(atom_begin(subchunk):iatom)))
    1575              :       END DO
    1576              : 
    1577              :       ! Largest blocks run first so CUDA allocations can be reused by all following calls.
    1578           10 :       DO subchunk = 2, nchunks
    1579            0 :          padded_rows_tmp = padded_rows(subchunk)
    1580            0 :          atom_begin_tmp = atom_begin(subchunk)
    1581            0 :          atom_count_tmp = atom_count(subchunk)
    1582            0 :          row_begin_tmp = row_begin(subchunk)
    1583            0 :          row_count_tmp = row_count(subchunk)
    1584            0 :          insert_at = subchunk
    1585            0 :          DO WHILE (insert_at > 1 .AND. padded_rows(insert_at - 1) < padded_rows_tmp)
    1586            0 :             padded_rows(insert_at) = padded_rows(insert_at - 1)
    1587            0 :             atom_begin(insert_at) = atom_begin(insert_at - 1)
    1588            0 :             atom_count(insert_at) = atom_count(insert_at - 1)
    1589            0 :             row_begin(insert_at) = row_begin(insert_at - 1)
    1590            0 :             row_count(insert_at) = row_count(insert_at - 1)
    1591            0 :             insert_at = insert_at - 1
    1592              :          END DO
    1593            0 :          padded_rows(insert_at) = padded_rows_tmp
    1594            0 :          atom_begin(insert_at) = atom_begin_tmp
    1595            0 :          atom_count(insert_at) = atom_count_tmp
    1596            0 :          row_begin(insert_at) = row_begin_tmp
    1597           10 :          row_count(insert_at) = row_count_tmp
    1598              :       END DO
    1599           10 :       DEALLOCATE (padded_rows)
    1600              : 
    1601              :    END SUBROUTINE atom_composite_chunk_layout
    1602              : 
    1603              : ! **************************************************************************************************
    1604              : !> \brief Add the explicit SKALA derivative with respect to atom-center coordinates.
    1605              : !> \param atom_force ...
    1606              : !> \param features ...
    1607              : !> \param atom_coord_grad_t ...
    1608              : !> \param root_rank ...
    1609              : ! **************************************************************************************************
    1610            8 :    SUBROUTINE add_explicit_coordinate_force(atom_force, features, atom_coord_grad_t, root_rank)
    1611              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: atom_force
    1612              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1613              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: atom_coord_grad_t
    1614              :       LOGICAL, INTENT(IN)                                :: root_rank
    1615              : 
    1616            8 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: atom_coord_grad
    1617              : 
    1618            8 :       NULLIFY (atom_coord_grad)
    1619            8 :       CALL torch_tensor_grad(features%coarse_0_atomic_coords_t, atom_coord_grad_t)
    1620            8 :       IF (root_rank) THEN
    1621            4 :          CALL torch_tensor_data_ptr(atom_coord_grad_t, atom_coord_grad)
    1622            4 :          CPASSERT(SIZE(atom_force, 1) == SIZE(atom_coord_grad, 1))
    1623            4 :          CPASSERT(SIZE(atom_force, 2) == SIZE(atom_coord_grad, 2))
    1624           36 :          atom_force(:, :) = atom_force(:, :) + atom_coord_grad(:, :)
    1625              :       END IF
    1626              : 
    1627            8 :    END SUBROUTINE add_explicit_coordinate_force
    1628              : 
    1629              : ! **************************************************************************************************
    1630              : !> \brief Add the force from SMOOTH native-grid atom partition weights.
    1631              : !> \param atom_force ...
    1632              : !> \param features ...
    1633              : !> \param particle_set ...
    1634              : !> \param cell ...
    1635              : !> \param rho_r ...
    1636              : !> \param grid_weight_grad_t ...
    1637              : !> \param atomic_grid_weight_grad_t ...
    1638              : ! **************************************************************************************************
    1639            8 :    SUBROUTINE add_smooth_partition_force(atom_force, features, particle_set, cell, rho_r, &
    1640              :                                          grid_weight_grad_t, atomic_grid_weight_grad_t)
    1641              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: atom_force
    1642              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1643              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1644              :       TYPE(cell_type), POINTER                           :: cell
    1645              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: rho_r
    1646              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: grid_weight_grad_t, &
    1647              :                                                             atomic_grid_weight_grad_t
    1648              : 
    1649              :       INTEGER                                            :: feature_begin, feature_end, feature_pos, &
    1650              :                                                             i, iatom, j, jatom, k, local_row, &
    1651              :                                                             natom, row
    1652              :       INTEGER, DIMENSION(2, 3)                           :: bo
    1653              :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: included
    1654              :       REAL(KIND=dp)                                      :: grid_base_weight, weight_grad
    1655              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: weights
    1656              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: atom_coords_pbc
    1657              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: dweights_datom, dweights_dstrain
    1658              :       REAL(KIND=dp), DIMENSION(3)                        :: grid_point
    1659            8 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: atomic_grid_weight_grad, grid_weight_grad
    1660              : 
    1661            8 :       NULLIFY (atomic_grid_weight_grad, grid_weight_grad)
    1662            8 :       CALL torch_tensor_grad(features%grid_weights_t, grid_weight_grad_t)
    1663            8 :       CALL torch_tensor_grad(features%atomic_grid_weights_t, atomic_grid_weight_grad_t)
    1664            8 :       CALL torch_tensor_data_ptr(grid_weight_grad_t, grid_weight_grad)
    1665            8 :       CALL torch_tensor_data_ptr(atomic_grid_weight_grad_t, atomic_grid_weight_grad)
    1666              : 
    1667            8 :       natom = SIZE(particle_set)
    1668            8 :       CPASSERT(SIZE(atom_force, 1) == 3)
    1669            8 :       CPASSERT(SIZE(atom_force, 2) == natom)
    1670              :       ALLOCATE (atom_coords_pbc(3, natom), included(natom), weights(natom), &
    1671           96 :                 dweights_datom(3, natom, natom), dweights_dstrain(3, 3, natom))
    1672           24 :       DO iatom = 1, natom
    1673           24 :          atom_coords_pbc(:, iatom) = pbc(particle_set(iatom)%r, cell, positive_range=.TRUE.)
    1674              :       END DO
    1675              : 
    1676           80 :       bo = rho_r(1)%pw_grid%bounds_local
    1677            8 :       local_row = 0
    1678          182 :       DO k = bo(1, 3), bo(2, 3)
    1679         4088 :          DO j = bo(1, 2), bo(2, 2)
    1680        48927 :             DO i = bo(1, 1), bo(2, 1)
    1681        44847 :                local_row = local_row + 1
    1682       179388 :                grid_point = native_grid_coordinate(rho_r(1)%pw_grid, [i, j, k])
    1683              :                CALL skala_gpw_smooth_partition_derivatives(grid_point, atom_coords_pbc, cell, &
    1684              :                                                            weights, included, dweights_datom, &
    1685        44847 :                                                            dweights_dstrain)
    1686        44847 :                feature_begin = features%local_feature_offsets(local_row)
    1687        44847 :                feature_end = features%local_feature_offsets(local_row + 1) - 1
    1688       134541 :                CPASSERT(feature_end - feature_begin + 1 == COUNT(included))
    1689        44847 :                grid_base_weight = 0.0_dp
    1690       134287 :                DO feature_pos = feature_begin, feature_end
    1691        89440 :                   row = features%local_feature_rows(feature_pos)
    1692       134287 :                   grid_base_weight = grid_base_weight + features%grid_weights(row)
    1693              :                END DO
    1694              :                feature_pos = feature_begin
    1695       134541 :                DO iatom = 1, natom
    1696        89694 :                   IF (.NOT. included(iatom)) CYCLE
    1697        89440 :                   row = features%local_feature_rows(feature_pos)
    1698              :                   weight_grad = grid_base_weight*grid_weight_grad(row) + &
    1699              :                                 rho_r(1)%pw_grid%dvol*atomic_grid_weight_grad(row)* &
    1700        89440 :                                 smooth_partition_atomic_weight_scale_derivative(weights(iatom))
    1701       268320 :                   DO jatom = 1, natom
    1702              :                      atom_force(:, jatom) = atom_force(:, jatom) + &
    1703       804960 :                                             weight_grad*dweights_datom(:, jatom, iatom)
    1704              :                   END DO
    1705       134541 :                   feature_pos = feature_pos + 1
    1706              :                END DO
    1707        48753 :                CPASSERT(feature_pos == feature_end + 1)
    1708              :             END DO
    1709              :          END DO
    1710              :       END DO
    1711            8 :       CPASSERT(local_row == features%nflat_local)
    1712              : 
    1713            8 :       DEALLOCATE (atom_coords_pbc, dweights_datom, dweights_dstrain, included, weights)
    1714            8 :       CALL torch_tensor_release(grid_weight_grad_t)
    1715            8 :       CALL torch_tensor_release(atomic_grid_weight_grad_t)
    1716              : 
    1717            8 :    END SUBROUTINE add_smooth_partition_force
    1718              : 
    1719              : ! **************************************************************************************************
    1720              : !> \brief Add the virial from SMOOTH native-grid atom partition weights.
    1721              : !> \param virial_xc ...
    1722              : !> \param features ...
    1723              : !> \param particle_set ...
    1724              : !> \param cell ...
    1725              : !> \param rho_r ...
    1726              : !> \param grid_weight_grad_t ...
    1727              : !> \param atomic_grid_weight_grad_t ...
    1728              : ! **************************************************************************************************
    1729            6 :    SUBROUTINE build_smooth_partition_virial(virial_xc, features, particle_set, cell, rho_r, &
    1730              :                                             grid_weight_grad_t, atomic_grid_weight_grad_t)
    1731              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(INOUT)      :: virial_xc
    1732              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1733              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1734              :       TYPE(cell_type), POINTER                           :: cell
    1735              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: rho_r
    1736              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: grid_weight_grad_t, &
    1737              :                                                             atomic_grid_weight_grad_t
    1738              : 
    1739              :       INTEGER                                            :: feature_begin, feature_end, feature_pos, &
    1740              :                                                             i, iatom, idir, j, jdir, k, local_row, &
    1741              :                                                             natom, row
    1742              :       INTEGER, DIMENSION(2, 3)                           :: bo
    1743              :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: included
    1744              :       REAL(KIND=dp)                                      :: grid_base_weight, tmp, weight_grad
    1745              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: weights
    1746              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: atom_coords_pbc
    1747              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: dweights_datom, dweights_dstrain
    1748              :       REAL(KIND=dp), DIMENSION(3)                        :: grid_point
    1749            6 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: atomic_grid_weight_grad, grid_weight_grad
    1750              : 
    1751            6 :       NULLIFY (atomic_grid_weight_grad, grid_weight_grad)
    1752            6 :       CALL torch_tensor_grad(features%grid_weights_t, grid_weight_grad_t)
    1753            6 :       CALL torch_tensor_grad(features%atomic_grid_weights_t, atomic_grid_weight_grad_t)
    1754            6 :       CALL torch_tensor_data_ptr(grid_weight_grad_t, grid_weight_grad)
    1755            6 :       CALL torch_tensor_data_ptr(atomic_grid_weight_grad_t, atomic_grid_weight_grad)
    1756              : 
    1757            6 :       natom = SIZE(particle_set)
    1758              :       ALLOCATE (atom_coords_pbc(3, natom), included(natom), weights(natom), &
    1759           72 :                 dweights_datom(3, natom, natom), dweights_dstrain(3, 3, natom))
    1760           18 :       DO iatom = 1, natom
    1761           18 :          atom_coords_pbc(:, iatom) = pbc(particle_set(iatom)%r, cell, positive_range=.TRUE.)
    1762              :       END DO
    1763              : 
    1764           60 :       bo = rho_r(1)%pw_grid%bounds_local
    1765            6 :       local_row = 0
    1766          150 :       DO k = bo(1, 3), bo(2, 3)
    1767         3606 :          DO j = bo(1, 2), bo(2, 2)
    1768        45072 :             DO i = bo(1, 1), bo(2, 1)
    1769        41472 :                local_row = local_row + 1
    1770       165888 :                grid_point = native_grid_coordinate(rho_r(1)%pw_grid, [i, j, k])
    1771              :                CALL skala_gpw_smooth_partition_derivatives(grid_point, atom_coords_pbc, cell, &
    1772              :                                                            weights, included, dweights_datom, &
    1773        41472 :                                                            dweights_dstrain)
    1774        41472 :                feature_begin = features%local_feature_offsets(local_row)
    1775        41472 :                feature_end = features%local_feature_offsets(local_row + 1) - 1
    1776       124416 :                CPASSERT(feature_end - feature_begin + 1 == COUNT(included))
    1777        41472 :                grid_base_weight = 0.0_dp
    1778       124206 :                DO feature_pos = feature_begin, feature_end
    1779        82734 :                   row = features%local_feature_rows(feature_pos)
    1780       124206 :                   grid_base_weight = grid_base_weight + features%grid_weights(row)
    1781              :                END DO
    1782              :                feature_pos = feature_begin
    1783       124416 :                DO iatom = 1, natom
    1784        82944 :                   IF (.NOT. included(iatom)) CYCLE
    1785        82734 :                   row = features%local_feature_rows(feature_pos)
    1786              :                   weight_grad = grid_base_weight*grid_weight_grad(row) + &
    1787              :                                 rho_r(1)%pw_grid%dvol*atomic_grid_weight_grad(row)* &
    1788        82734 :                                 smooth_partition_atomic_weight_scale_derivative(weights(iatom))
    1789       330936 :                   DO idir = 1, 3
    1790       827340 :                      DO jdir = 1, idir
    1791       496404 :                         tmp = weight_grad*dweights_dstrain(idir, jdir, iatom)
    1792       496404 :                         virial_xc(jdir, idir) = virial_xc(jdir, idir) + tmp
    1793       744606 :                         IF (idir /= jdir) virial_xc(idir, jdir) = virial_xc(idir, jdir) + tmp
    1794              :                      END DO
    1795              :                   END DO
    1796       124416 :                   feature_pos = feature_pos + 1
    1797              :                END DO
    1798        44928 :                CPASSERT(feature_pos == feature_end + 1)
    1799              :             END DO
    1800              :          END DO
    1801              :       END DO
    1802            6 :       CPASSERT(local_row == features%nflat_local)
    1803              : 
    1804            6 :       DEALLOCATE (atom_coords_pbc, dweights_datom, dweights_dstrain, included, weights)
    1805            6 :       CALL torch_tensor_release(grid_weight_grad_t)
    1806            6 :       CALL torch_tensor_release(atomic_grid_weight_grad_t)
    1807              : 
    1808            6 :    END SUBROUTINE build_smooth_partition_virial
    1809              : 
    1810              : ! **************************************************************************************************
    1811              : !> \brief Return the Cartesian coordinate of a regular GPW grid point.
    1812              : !> \param pw_grid ...
    1813              : !> \param index ...
    1814              : !> \return ...
    1815              : ! **************************************************************************************************
    1816        86319 :    FUNCTION native_grid_coordinate(pw_grid, index) RESULT(coord)
    1817              :       TYPE(pw_grid_type), POINTER                        :: pw_grid
    1818              :       INTEGER, DIMENSION(3), INTENT(IN)                  :: index
    1819              :       REAL(KIND=dp), DIMENSION(3)                        :: coord
    1820              : 
    1821              :       INTEGER, DIMENSION(3)                              :: relative_index
    1822              : 
    1823       345276 :       relative_index = index - pw_grid%bounds(1, :)
    1824              :       coord = REAL(relative_index(1), KIND=dp)*pw_grid%dh(:, 1) + &
    1825              :               REAL(relative_index(2), KIND=dp)*pw_grid%dh(:, 2) + &
    1826       345276 :               REAL(relative_index(3), KIND=dp)*pw_grid%dh(:, 3)
    1827              : 
    1828        86319 :    END FUNCTION native_grid_coordinate
    1829              : 
    1830              : ! **************************************************************************************************
    1831              : !> \brief Evaluate a rank-local atom chunk as multiple atom-contiguous Torch subchunks.
    1832              : !> \param features ...
    1833              : !> \param group ...
    1834              : !> \param max_rows ...
    1835              : !> \param compute_grads ...
    1836              : !> \param exc ...
    1837              : !> \param density_grad ...
    1838              : !> \param grad_grad ...
    1839              : !> \param kin_grad ...
    1840              : !> \param collapse_spin_grads ...
    1841              : ! **************************************************************************************************
    1842            2 :    SUBROUTINE evaluate_atom_subchunks(features, group, max_rows, compute_grads, exc, &
    1843              :                                       density_grad, grad_grad, kin_grad, collapse_spin_grads)
    1844              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1845              : 
    1846              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    1847              :       INTEGER, INTENT(IN)                                :: max_rows
    1848              :       LOGICAL, INTENT(IN)                                :: compute_grads, collapse_spin_grads
    1849              :       REAL(KIND=dp), INTENT(OUT)                         :: exc
    1850              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    1851              :          INTENT(OUT)                                     :: density_grad, kin_grad
    1852              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
    1853              :          INTENT(OUT)                                     :: grad_grad
    1854              : 
    1855              :       INTEGER                                            :: isubchunk, nroute_grad_per_point, &
    1856              :                                                             nroute_recv_points, nroute_send_points, &
    1857              :                                                             nsubchunks, phase_handle, subphase_handle
    1858            2 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: route_grad_return_recv_counts, &
    1859            2 :                                                             route_grad_return_recv_displs, &
    1860            2 :                                                             route_grad_return_send_counts, &
    1861            2 :                                                             route_grad_return_send_displs, &
    1862            2 :                                                             subchunk_atom_begin, &
    1863            2 :                                                             subchunk_atom_count, &
    1864            2 :                                                             subchunk_row_begin, &
    1865            2 :                                                             subchunk_row_count
    1866              :       REAL(KIND=dp)                                      :: subchunk_exc
    1867            2 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: recv_grad_buffer, send_grad_buffer
    1868            2 :       TYPE(skala_gpw_feature_type)                       :: subchunk
    1869              :       TYPE(torch_tensor_type)                            :: subchunk_exc_tensor
    1870              : 
    1871            0 :       CPASSERT(features%uses_atom_chunks)
    1872            2 :       CPASSERT(max_rows > 0)
    1873              :       CALL skala_gpw_atom_subchunk_layout(max_rows, subchunk_atom_begin, subchunk_atom_count, &
    1874            2 :                                           subchunk_row_begin, subchunk_row_count)
    1875            2 :       nsubchunks = SIZE(subchunk_atom_begin)
    1876              : 
    1877            2 :       exc = 0.0_dp
    1878            2 :       IF (compute_grads) THEN
    1879            2 :          CPASSERT(features%uses_atom_chunk_routing)
    1880            6 :          nroute_recv_points = SUM(features%route_point_recv_counts)
    1881            2 :          nroute_send_points = SIZE(features%route_send_local_rows)
    1882            6 :          CPASSERT(SUM(features%route_point_send_counts) == nroute_send_points)
    1883            2 :          CPASSERT(SIZE(features%route_chunk_offsets) == nroute_recv_points + 1)
    1884            2 :          nroute_grad_per_point = ngrad_per_point
    1885            2 :          IF (collapse_spin_grads) nroute_grad_per_point = ncollapsed_grad_per_point
    1886              :          ALLOCATE (send_grad_buffer(MAX(1, nroute_grad_per_point*nroute_recv_points)), &
    1887              :                    recv_grad_buffer(MAX(1, nroute_grad_per_point*nroute_send_points)), &
    1888              :                    route_grad_return_send_counts(SIZE(features%route_point_recv_counts)), &
    1889              :                    route_grad_return_send_displs(SIZE(features%route_point_recv_displs)), &
    1890              :                    route_grad_return_recv_counts(SIZE(features%route_point_send_counts)), &
    1891           26 :                    route_grad_return_recv_displs(SIZE(features%route_point_send_displs)))
    1892              :          route_grad_return_send_counts(:) = &
    1893            6 :             nroute_grad_per_point*features%route_point_recv_counts
    1894              :          route_grad_return_send_displs(:) = &
    1895            6 :             nroute_grad_per_point*features%route_point_recv_displs
    1896              :          route_grad_return_recv_counts(:) = &
    1897            6 :             nroute_grad_per_point*features%route_point_send_counts
    1898              :          route_grad_return_recv_displs(:) = &
    1899            6 :             nroute_grad_per_point*features%route_point_send_displs
    1900              :       END IF
    1901              : 
    1902            2 :       CALL timeset("skala_gpw_atom_subchunks", phase_handle)
    1903            6 :       DO isubchunk = 1, nsubchunks
    1904            4 :          CALL timeset("skala_gpw_atom_subchunk_build", subphase_handle)
    1905              :          CALL skala_gpw_feature_build_atom_subchunk_bounds(features, subchunk, &
    1906              :                                                            subchunk_atom_begin(isubchunk), &
    1907              :                                                            subchunk_atom_count(isubchunk), &
    1908              :                                                            subchunk_row_begin(isubchunk), &
    1909              :                                                            subchunk_row_count(isubchunk), &
    1910            4 :                                                            compute_grads)
    1911            4 :          CALL timestop(subphase_handle)
    1912            4 :          CALL timeset("skala_gpw_atom_subchunk_forward", subphase_handle)
    1913              :          CALL skala_torch_model_get_exc(cached_model, subchunk%inputs, &
    1914              :                                         subchunk%grid_weights_t, subchunk_exc_tensor, &
    1915            4 :                                         subchunk_exc)
    1916            4 :          CALL timestop(subphase_handle)
    1917            4 :          exc = exc + subchunk_exc
    1918            4 :          IF (compute_grads) THEN
    1919            4 :             CALL timeset("skala_gpw_atom_subchunk_backward", subphase_handle)
    1920            4 :             CALL torch_tensor_backward_scalar(subchunk_exc_tensor)
    1921            4 :             CALL timestop(subphase_handle)
    1922              :          END IF
    1923            4 :          CALL timeset("skala_gpw_atom_subchunk_release", subphase_handle)
    1924            4 :          CALL torch_tensor_release(subchunk_exc_tensor)
    1925            4 :          CALL skala_gpw_feature_release(subchunk)
    1926           18 :          CALL timestop(subphase_handle)
    1927              :       END DO
    1928            2 :       IF (compute_grads .AND. features%chunk_feature_count > 0) THEN
    1929            2 :          CALL timeset("skala_gpw_atom_subchunk_grad_pack", subphase_handle)
    1930            2 :          CALL pack_atom_chunk_grads(features, send_grad_buffer, .TRUE., collapse_spin_grads)
    1931            2 :          CALL timestop(subphase_handle)
    1932              :       END IF
    1933            2 :       CALL timestop(phase_handle)
    1934              : 
    1935            2 :       IF (compute_grads) THEN
    1936            2 :          CALL timeset("skala_gpw_grad_route_comm", phase_handle)
    1937              :          CALL group%alltoall(send_grad_buffer, route_grad_return_send_counts, &
    1938              :                              route_grad_return_send_displs, recv_grad_buffer, &
    1939            2 :                              route_grad_return_recv_counts, route_grad_return_recv_displs)
    1940            2 :          CALL timestop(phase_handle)
    1941              : 
    1942            2 :          CALL timeset("skala_gpw_grad_route_scatter", phase_handle)
    1943              :          CALL scatter_routed_atom_chunk_grads(features, recv_grad_buffer, collapse_spin_grads, &
    1944            2 :                                               density_grad, grad_grad, kin_grad)
    1945            2 :          CALL timestop(phase_handle)
    1946              : 
    1947            0 :          DEALLOCATE (recv_grad_buffer, route_grad_return_recv_counts, &
    1948            0 :                      route_grad_return_recv_displs, route_grad_return_send_counts, &
    1949            2 :                      route_grad_return_send_displs, send_grad_buffer)
    1950              :       END IF
    1951            2 :       DEALLOCATE (subchunk_atom_begin, subchunk_atom_count, subchunk_row_begin, subchunk_row_count)
    1952              : 
    1953            4 :    END SUBROUTINE evaluate_atom_subchunks
    1954              : 
    1955              : ! **************************************************************************************************
    1956              : !> \brief Select an automatic atom-subchunk row cap.
    1957              : !> \param features ...
    1958              : !> \param group ...
    1959              : !> \return ...
    1960              : ! **************************************************************************************************
    1961           36 :    FUNCTION auto_atom_chunk_max_rows(features, group) RESULT(max_rows)
    1962              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1963              : 
    1964              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    1965              :       INTEGER                                            :: max_rows
    1966              : 
    1967              :       INTEGER                                            :: local_rows_max, target_rows
    1968              : 
    1969           36 :       local_rows_max = features%chunk_feature_count
    1970           36 :       CALL group%max(local_rows_max)
    1971           36 :       IF (local_rows_max <= 0) THEN
    1972           36 :          max_rows = 0
    1973              :          RETURN
    1974              :       END IF
    1975              : 
    1976           36 :       IF (group%num_pe > 1) THEN
    1977           36 :          target_rows = CEILING(REAL(local_rows_max, KIND=dp)/2.0_dp)
    1978              :          max_rows = atom_chunk_auto_row_quantum* &
    1979           36 :                     ((target_rows + atom_chunk_auto_row_quantum - 1)/atom_chunk_auto_row_quantum)
    1980              :       ELSE
    1981            0 :          target_rows = NINT(REAL(local_rows_max, KIND=dp)/4.0_dp)
    1982              :          max_rows = atom_chunk_auto_row_quantum* &
    1983              :                     MAX(1, NINT(REAL(target_rows, KIND=dp)/ &
    1984            0 :                                 REAL(atom_chunk_auto_row_quantum, KIND=dp)))
    1985              :       END IF
    1986           36 :       max_rows = MAX(atom_chunk_auto_min_rows, MIN(atom_chunk_auto_max_rows, max_rows))
    1987              : 
    1988           36 :    END FUNCTION auto_atom_chunk_max_rows
    1989              : 
    1990              : ! **************************************************************************************************
    1991              : !> \brief Map full Torch feature gradients back to this rank's local grid order.
    1992              : !> \param features ...
    1993              : !> \param density_grad ...
    1994              : !> \param grad_grad ...
    1995              : !> \param kin_grad ...
    1996              : ! **************************************************************************************************
    1997            8 :    SUBROUTINE fetch_local_feature_grads(features, density_grad, grad_grad, kin_grad)
    1998              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    1999              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2000              :          INTENT(OUT)                                     :: density_grad
    2001              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
    2002              :          INTENT(OUT)                                     :: grad_grad
    2003              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2004              :          INTENT(OUT)                                     :: kin_grad
    2005              : 
    2006              :       INTEGER                                            :: feature_pos, i, j, k, local_row, row
    2007            8 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: density_grad_all, kin_grad_all
    2008            8 :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: grad_grad_all
    2009              :       TYPE(torch_tensor_type)                            :: density_grad_t, grad_grad_t, kin_grad_t
    2010              : 
    2011            8 :       NULLIFY (density_grad_all, grad_grad_all, kin_grad_all)
    2012              :       CALL get_feature_grad_views(features, density_grad_t, grad_grad_t, kin_grad_t, &
    2013            8 :                                   density_grad_all, grad_grad_all, kin_grad_all)
    2014            8 :       CPASSERT(SIZE(density_grad_all, 1) == features%nflat)
    2015            8 :       CPASSERT(SIZE(density_grad_all, 2) == 2)
    2016            8 :       CPASSERT(SIZE(grad_grad_all, 1) == features%nflat)
    2017            8 :       CPASSERT(SIZE(grad_grad_all, 2) == 3)
    2018            8 :       CPASSERT(SIZE(grad_grad_all, 3) == 2)
    2019            8 :       CPASSERT(SIZE(kin_grad_all, 1) == features%nflat)
    2020            8 :       CPASSERT(SIZE(kin_grad_all, 2) == 2)
    2021              : 
    2022            0 :       ALLOCATE (density_grad(features%nflat_local, 2), &
    2023            0 :                 grad_grad(features%nflat_local, 3, 2), &
    2024           56 :                 kin_grad(features%nflat_local, 2))
    2025            8 :       density_grad = 0.0_dp
    2026            8 :       grad_grad = 0.0_dp
    2027            8 :       kin_grad = 0.0_dp
    2028            8 :       local_row = 0
    2029          198 :       DO k = LBOUND(features%feature_index, 3), UBOUND(features%feature_index, 3)
    2030         4436 :          DO j = LBOUND(features%feature_index, 2), UBOUND(features%feature_index, 2)
    2031        56739 :             DO i = LBOUND(features%feature_index, 1), UBOUND(features%feature_index, 1)
    2032        44847 :                local_row = local_row + 1
    2033       134287 :                DO feature_pos = features%local_feature_offsets(local_row), &
    2034        48753 :                   features%local_feature_offsets(local_row + 1) - 1
    2035        89440 :                   row = features%local_feature_rows(feature_pos)
    2036        89440 :                   CPASSERT(row >= 1 .AND. row <= features%nflat)
    2037              :                   density_grad(local_row, :) = density_grad(local_row, :) + &
    2038       268320 :                                                density_grad_all(row, :)
    2039              :                   grad_grad(local_row, :, :) = grad_grad(local_row, :, :) + &
    2040       804960 :                                                grad_grad_all(row, :, :)
    2041       313167 :                   kin_grad(local_row, :) = kin_grad(local_row, :) + kin_grad_all(row, :)
    2042              :                END DO
    2043              :             END DO
    2044              :          END DO
    2045              :       END DO
    2046            8 :       CPASSERT(local_row == features%nflat_local)
    2047              : 
    2048            8 :       CALL torch_tensor_release(density_grad_t)
    2049            8 :       CALL torch_tensor_release(grad_grad_t)
    2050            8 :       CALL torch_tensor_release(kin_grad_t)
    2051              : 
    2052            8 :    END SUBROUTINE fetch_local_feature_grads
    2053              : 
    2054              : ! **************************************************************************************************
    2055              : !> \brief Pack atom-chunk Torch gradients into CP2K communication buffers.
    2056              : !> \param features ...
    2057              : !> \param TARGET ...
    2058              : !> \param route_to_return_positions ...
    2059              : !> \param collapse_spin_grads ...
    2060              : ! **************************************************************************************************
    2061           40 :    SUBROUTINE pack_atom_chunk_grads(features, TARGET, route_to_return_positions, &
    2062              :                                     collapse_spin_grads)
    2063              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    2064              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
    2065              :          INTENT(INOUT)                                   :: target
    2066              :       LOGICAL, INTENT(IN)                                :: route_to_return_positions
    2067              :       LOGICAL, INTENT(IN), OPTIONAL                      :: collapse_spin_grads
    2068              : 
    2069              :       INTEGER                                            :: base, feature_pos, irow, &
    2070              :                                                             ngrad_buffer_per_point, point_pos, &
    2071              :                                                             target_points
    2072              :       LOGICAL                                            :: my_collapse_spin_grads
    2073           40 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: chunk_density_grad, chunk_kin_grad
    2074           40 :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: chunk_grad_grad
    2075              :       TYPE(torch_tensor_type)                            :: density_grad_t, grad_grad_t, kin_grad_t
    2076              : 
    2077           40 :       my_collapse_spin_grads = .FALSE.
    2078           80 :       IF (PRESENT(collapse_spin_grads)) my_collapse_spin_grads = collapse_spin_grads
    2079           40 :       ngrad_buffer_per_point = ngrad_per_point
    2080           40 :       IF (my_collapse_spin_grads) ngrad_buffer_per_point = ncollapsed_grad_per_point
    2081              : 
    2082           40 :       NULLIFY (chunk_density_grad, chunk_grad_grad, chunk_kin_grad)
    2083              :       CALL get_feature_grad_views(features, density_grad_t, grad_grad_t, kin_grad_t, &
    2084           40 :                                   chunk_density_grad, chunk_grad_grad, chunk_kin_grad)
    2085           40 :       CPASSERT(MOD(SIZE(TARGET), ngrad_buffer_per_point) == 0)
    2086           40 :       target_points = SIZE(TARGET)/ngrad_buffer_per_point
    2087           40 :       CPASSERT(SIZE(chunk_density_grad, 1) == features%chunk_feature_count)
    2088           40 :       CPASSERT(SIZE(chunk_grad_grad, 1) == features%chunk_feature_count)
    2089           40 :       CPASSERT(SIZE(chunk_grad_grad, 2) == 3)
    2090           40 :       CPASSERT(SIZE(chunk_kin_grad, 1) == features%chunk_feature_count)
    2091           40 :       IF (features%uses_collapsed_rks_dynamic) THEN
    2092           34 :          CPASSERT(my_collapse_spin_grads)
    2093           34 :          CPASSERT(SIZE(chunk_density_grad, 2) == 1)
    2094           34 :          CPASSERT(SIZE(chunk_grad_grad, 3) == 1)
    2095           34 :          CPASSERT(SIZE(chunk_kin_grad, 2) == 1)
    2096              :       ELSE
    2097            6 :          CPASSERT(SIZE(chunk_density_grad, 2) == 2)
    2098            6 :          CPASSERT(SIZE(chunk_grad_grad, 3) == 2)
    2099            6 :          CPASSERT(SIZE(chunk_kin_grad, 2) == 2)
    2100              :       END IF
    2101              : 
    2102           40 :       IF (route_to_return_positions) THEN
    2103           40 :          CPASSERT(target_points == SIZE(features%route_chunk_offsets) - 1)
    2104              : !$OMP PARALLEL DO DEFAULT(NONE) &
    2105              : !$OMP SHARED(chunk_density_grad, chunk_grad_grad, chunk_kin_grad, features, &
    2106              : !$OMP        my_collapse_spin_grads, ngrad_buffer_per_point, TARGET, target_points) &
    2107           40 : !$OMP PRIVATE(base, feature_pos, irow, point_pos)
    2108              :          DO point_pos = 1, target_points
    2109              :             base = ngrad_buffer_per_point*(point_pos - 1)
    2110              :             TARGET(base + 1:base + ngrad_buffer_per_point) = 0.0_dp
    2111              :             DO feature_pos = features%route_chunk_offsets(point_pos), &
    2112              :                features%route_chunk_offsets(point_pos + 1) - 1
    2113              :                irow = features%route_chunk_rows(feature_pos)
    2114              :                CPASSERT(irow >= 1 .AND. irow <= features%chunk_feature_count)
    2115              :                IF (my_collapse_spin_grads) THEN
    2116              :                   IF (features%uses_collapsed_rks_dynamic) THEN
    2117              :                      TARGET(base + 1) = TARGET(base + 1) + &
    2118              :                                         0.5_dp*chunk_density_grad(irow, 1)
    2119              :                      TARGET(base + 2) = TARGET(base + 2) + &
    2120              :                                         0.5_dp*chunk_grad_grad(irow, 1, 1)
    2121              :                      TARGET(base + 3) = TARGET(base + 3) + &
    2122              :                                         0.5_dp*chunk_grad_grad(irow, 2, 1)
    2123              :                      TARGET(base + 4) = TARGET(base + 4) + &
    2124              :                                         0.5_dp*chunk_grad_grad(irow, 3, 1)
    2125              :                      TARGET(base + 5) = TARGET(base + 5) + &
    2126              :                                         0.5_dp*chunk_kin_grad(irow, 1)
    2127              :                   ELSE
    2128              :                      TARGET(base + 1) = TARGET(base + 1) + &
    2129              :                                         0.5_dp*(chunk_density_grad(irow, 1) + &
    2130              :                                                 chunk_density_grad(irow, 2))
    2131              :                      TARGET(base + 2) = TARGET(base + 2) + &
    2132              :                                         0.5_dp*(chunk_grad_grad(irow, 1, 1) + &
    2133              :                                                 chunk_grad_grad(irow, 1, 2))
    2134              :                      TARGET(base + 3) = TARGET(base + 3) + &
    2135              :                                         0.5_dp*(chunk_grad_grad(irow, 2, 1) + &
    2136              :                                                 chunk_grad_grad(irow, 2, 2))
    2137              :                      TARGET(base + 4) = TARGET(base + 4) + &
    2138              :                                         0.5_dp*(chunk_grad_grad(irow, 3, 1) + &
    2139              :                                                 chunk_grad_grad(irow, 3, 2))
    2140              :                      TARGET(base + 5) = TARGET(base + 5) + &
    2141              :                                         0.5_dp*(chunk_kin_grad(irow, 1) + &
    2142              :                                                 chunk_kin_grad(irow, 2))
    2143              :                   END IF
    2144              :                ELSE
    2145              :                   TARGET(base + 1:base + 2) = TARGET(base + 1:base + 2) + &
    2146              :                                               chunk_density_grad(irow, :)
    2147              :                   TARGET(base + 3) = TARGET(base + 3) + chunk_grad_grad(irow, 1, 1)
    2148              :                   TARGET(base + 4) = TARGET(base + 4) + chunk_grad_grad(irow, 2, 1)
    2149              :                   TARGET(base + 5) = TARGET(base + 5) + chunk_grad_grad(irow, 3, 1)
    2150              :                   TARGET(base + 6) = TARGET(base + 6) + chunk_grad_grad(irow, 1, 2)
    2151              :                   TARGET(base + 7) = TARGET(base + 7) + chunk_grad_grad(irow, 2, 2)
    2152              :                   TARGET(base + 8) = TARGET(base + 8) + chunk_grad_grad(irow, 3, 2)
    2153              :                   TARGET(base + 9:base + 10) = TARGET(base + 9:base + 10) + &
    2154              :                                                chunk_kin_grad(irow, :)
    2155              :                END IF
    2156              :             END DO
    2157              :          END DO
    2158              : !$OMP END PARALLEL DO
    2159              :       ELSE
    2160            0 :          CPASSERT(target_points >= features%chunk_feature_count)
    2161              : !$OMP PARALLEL DO DEFAULT(NONE) &
    2162              : !$OMP SHARED(chunk_density_grad, chunk_grad_grad, chunk_kin_grad, features, &
    2163              : !$OMP        my_collapse_spin_grads, ngrad_buffer_per_point, TARGET) &
    2164            0 : !$OMP PRIVATE(base, irow)
    2165              :          DO irow = 1, features%chunk_feature_count
    2166              :             base = ngrad_buffer_per_point*(irow - 1)
    2167              :             IF (my_collapse_spin_grads) THEN
    2168              :                IF (features%uses_collapsed_rks_dynamic) THEN
    2169              :                   TARGET(base + 1) = 0.5_dp*chunk_density_grad(irow, 1)
    2170              :                   TARGET(base + 2) = 0.5_dp*chunk_grad_grad(irow, 1, 1)
    2171              :                   TARGET(base + 3) = 0.5_dp*chunk_grad_grad(irow, 2, 1)
    2172              :                   TARGET(base + 4) = 0.5_dp*chunk_grad_grad(irow, 3, 1)
    2173              :                   TARGET(base + 5) = 0.5_dp*chunk_kin_grad(irow, 1)
    2174              :                ELSE
    2175              :                   TARGET(base + 1) = 0.5_dp*(chunk_density_grad(irow, 1) + &
    2176              :                                              chunk_density_grad(irow, 2))
    2177              :                   TARGET(base + 2) = 0.5_dp*(chunk_grad_grad(irow, 1, 1) + &
    2178              :                                              chunk_grad_grad(irow, 1, 2))
    2179              :                   TARGET(base + 3) = 0.5_dp*(chunk_grad_grad(irow, 2, 1) + &
    2180              :                                              chunk_grad_grad(irow, 2, 2))
    2181              :                   TARGET(base + 4) = 0.5_dp*(chunk_grad_grad(irow, 3, 1) + &
    2182              :                                              chunk_grad_grad(irow, 3, 2))
    2183              :                   TARGET(base + 5) = 0.5_dp*(chunk_kin_grad(irow, 1) + &
    2184              :                                              chunk_kin_grad(irow, 2))
    2185              :                END IF
    2186              :             ELSE
    2187              :                TARGET(base + 1:base + 2) = chunk_density_grad(irow, :)
    2188              :                TARGET(base + 3) = chunk_grad_grad(irow, 1, 1)
    2189              :                TARGET(base + 4) = chunk_grad_grad(irow, 2, 1)
    2190              :                TARGET(base + 5) = chunk_grad_grad(irow, 3, 1)
    2191              :                TARGET(base + 6) = chunk_grad_grad(irow, 1, 2)
    2192              :                TARGET(base + 7) = chunk_grad_grad(irow, 2, 2)
    2193              :                TARGET(base + 8) = chunk_grad_grad(irow, 3, 2)
    2194              :                TARGET(base + 9:base + 10) = chunk_kin_grad(irow, :)
    2195              :             END IF
    2196              :          END DO
    2197              : !$OMP END PARALLEL DO
    2198              :       END IF
    2199              : 
    2200           40 :       CALL torch_tensor_release(density_grad_t)
    2201           40 :       CALL torch_tensor_release(grad_grad_t)
    2202           40 :       CALL torch_tensor_release(kin_grad_t)
    2203              : 
    2204           40 :    END SUBROUTINE pack_atom_chunk_grads
    2205              : 
    2206              : ! **************************************************************************************************
    2207              : !> \brief Scatter routed atom-chunk gradients into local grid-row order.
    2208              : !> \param features ...
    2209              : !> \param recv_grad_buffer ...
    2210              : !> \param collapse_spin_grads ...
    2211              : !> \param density_grad ...
    2212              : !> \param grad_grad ...
    2213              : !> \param kin_grad ...
    2214              : ! **************************************************************************************************
    2215           40 :    SUBROUTINE scatter_routed_atom_chunk_grads(features, recv_grad_buffer, collapse_spin_grads, &
    2216              :                                               density_grad, grad_grad, kin_grad)
    2217              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    2218              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: recv_grad_buffer
    2219              :       LOGICAL, INTENT(IN)                                :: collapse_spin_grads
    2220              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2221              :          INTENT(OUT)                                     :: density_grad
    2222              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
    2223              :          INTENT(OUT)                                     :: grad_grad
    2224              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2225              :          INTENT(OUT)                                     :: kin_grad
    2226              : 
    2227              :       INTEGER                                            :: base, local_row, nflat_local, &
    2228              :                                                             nroute_grad_per_point, nroute_points, &
    2229              :                                                             point_pos, row_route_pos
    2230              : 
    2231           40 :       nflat_local = features%nflat_local
    2232           40 :       nroute_points = SIZE(features%route_send_local_rows)
    2233           40 :       nroute_grad_per_point = ngrad_per_point
    2234           40 :       IF (collapse_spin_grads) nroute_grad_per_point = ncollapsed_grad_per_point
    2235           40 :       CPASSERT(SIZE(recv_grad_buffer) >= nroute_grad_per_point*nroute_points)
    2236            0 :       ALLOCATE (density_grad(nflat_local, 2), grad_grad(nflat_local, 3, 2), &
    2237          280 :                 kin_grad(nflat_local, 2))
    2238              : 
    2239              : !$OMP PARALLEL DO DEFAULT(NONE) &
    2240              : !$OMP SHARED(collapse_spin_grads, density_grad, features, grad_grad, kin_grad, nflat_local, &
    2241              : !$OMP        nroute_grad_per_point, nroute_points, recv_grad_buffer) &
    2242           40 : !$OMP PRIVATE(base, local_row, point_pos, row_route_pos)
    2243              :       DO local_row = 1, nflat_local
    2244              :          density_grad(local_row, :) = 0.0_dp
    2245              :          grad_grad(local_row, :, :) = 0.0_dp
    2246              :          kin_grad(local_row, :) = 0.0_dp
    2247              :          DO row_route_pos = features%route_row_offsets(local_row), &
    2248              :             features%route_row_offsets(local_row + 1) - 1
    2249              :             point_pos = features%route_row_positions(row_route_pos)
    2250              :             CPASSERT(point_pos >= 1 .AND. point_pos <= nroute_points)
    2251              :             base = nroute_grad_per_point*(point_pos - 1)
    2252              :             IF (collapse_spin_grads) THEN
    2253              :                density_grad(local_row, :) = density_grad(local_row, :) + &
    2254              :                                             recv_grad_buffer(base + 1)
    2255              :                grad_grad(local_row, 1, :) = grad_grad(local_row, 1, :) + &
    2256              :                                             recv_grad_buffer(base + 2)
    2257              :                grad_grad(local_row, 2, :) = grad_grad(local_row, 2, :) + &
    2258              :                                             recv_grad_buffer(base + 3)
    2259              :                grad_grad(local_row, 3, :) = grad_grad(local_row, 3, :) + &
    2260              :                                             recv_grad_buffer(base + 4)
    2261              :                kin_grad(local_row, :) = kin_grad(local_row, :) + recv_grad_buffer(base + 5)
    2262              :             ELSE
    2263              :                density_grad(local_row, :) = density_grad(local_row, :) + &
    2264              :                                             recv_grad_buffer(base + 1:base + 2)
    2265              :                grad_grad(local_row, 1, 1) = grad_grad(local_row, 1, 1) + &
    2266              :                                             recv_grad_buffer(base + 3)
    2267              :                grad_grad(local_row, 2, 1) = grad_grad(local_row, 2, 1) + &
    2268              :                                             recv_grad_buffer(base + 4)
    2269              :                grad_grad(local_row, 3, 1) = grad_grad(local_row, 3, 1) + &
    2270              :                                             recv_grad_buffer(base + 5)
    2271              :                grad_grad(local_row, 1, 2) = grad_grad(local_row, 1, 2) + &
    2272              :                                             recv_grad_buffer(base + 6)
    2273              :                grad_grad(local_row, 2, 2) = grad_grad(local_row, 2, 2) + &
    2274              :                                             recv_grad_buffer(base + 7)
    2275              :                grad_grad(local_row, 3, 2) = grad_grad(local_row, 3, 2) + &
    2276              :                                             recv_grad_buffer(base + 8)
    2277              :                kin_grad(local_row, :) = kin_grad(local_row, :) + &
    2278              :                                         recv_grad_buffer(base + 9:base + 10)
    2279              :             END IF
    2280              :          END DO
    2281              :       END DO
    2282              : !$OMP END PARALLEL DO
    2283              : 
    2284           40 :    END SUBROUTINE scatter_routed_atom_chunk_grads
    2285              : 
    2286              : ! **************************************************************************************************
    2287              : !> \brief Return CPU views of autograd outputs for the SKALA dynamic feature tensors.
    2288              : !> \param features ...
    2289              : !> \param density_grad_t ...
    2290              : !> \param grad_grad_t ...
    2291              : !> \param kin_grad_t ...
    2292              : !> \param density_grad ...
    2293              : !> \param grad_grad ...
    2294              : !> \param kin_grad ...
    2295              : ! **************************************************************************************************
    2296           48 :    SUBROUTINE get_feature_grad_views(features, density_grad_t, grad_grad_t, kin_grad_t, &
    2297              :                                      density_grad, grad_grad, kin_grad)
    2298              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    2299              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: density_grad_t, grad_grad_t, kin_grad_t
    2300              :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: density_grad
    2301              :       REAL(KIND=dp), DIMENSION(:, :, :), POINTER         :: grad_grad
    2302              :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: kin_grad
    2303              : 
    2304           48 :       NULLIFY (density_grad, grad_grad, kin_grad)
    2305              :       CALL torch_tensor_grad_batch3(features%density_t, features%grad_t, features%kin_t, &
    2306           48 :                                     density_grad_t, grad_grad_t, kin_grad_t)
    2307           48 :       CALL torch_tensor_data_ptr(density_grad_t, density_grad)
    2308           48 :       CALL torch_tensor_data_ptr(grad_grad_t, grad_grad)
    2309           48 :       CALL torch_tensor_data_ptr(kin_grad_t, kin_grad)
    2310              : 
    2311           48 :    END SUBROUTINE get_feature_grad_views
    2312              : 
    2313              : ! **************************************************************************************************
    2314              : !> \brief Fetch atom-chunk gradients and route them back to their local grid owners.
    2315              : !> \param features ...
    2316              : !> \param group ...
    2317              : !> \param density_grad ...
    2318              : !> \param grad_grad ...
    2319              : !> \param kin_grad ...
    2320              : ! **************************************************************************************************
    2321           38 :    SUBROUTINE fetch_and_gather_atom_chunk_grads(features, group, density_grad, grad_grad, &
    2322              :                                                 kin_grad)
    2323              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    2324              : 
    2325              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    2326              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2327              :          INTENT(OUT)                                     :: density_grad, kin_grad
    2328              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
    2329              :          INTENT(OUT)                                     :: grad_grad
    2330              : 
    2331              :       INTEGER                                            :: base, feature_pos, i, j, k, local_row, &
    2332              :                                                             nflat_local, nroute_grad_per_point, &
    2333              :                                                             nroute_recv_points, nroute_send_points, &
    2334              :                                                             phase_handle, row
    2335           38 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: route_grad_return_recv_counts, &
    2336           38 :                                                             route_grad_return_recv_displs, &
    2337           38 :                                                             route_grad_return_send_counts, &
    2338           38 :                                                             route_grad_return_send_displs
    2339           38 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: chunk_grad_buffer, global_grad_buffer, &
    2340           38 :                                                             recv_grad_buffer, send_grad_buffer
    2341              : 
    2342           38 :       CPASSERT(features%uses_atom_chunks)
    2343              : 
    2344           38 :       nflat_local = features%nflat_local
    2345           38 :       IF (features%uses_atom_chunk_routing) THEN
    2346          114 :          nroute_recv_points = SUM(features%route_point_recv_counts)
    2347           38 :          nroute_send_points = SIZE(features%route_send_local_rows)
    2348          114 :          CPASSERT(SUM(features%route_point_send_counts) == nroute_send_points)
    2349           38 :          CPASSERT(SIZE(features%route_chunk_offsets) == nroute_recv_points + 1)
    2350              : 
    2351           38 :          nroute_grad_per_point = ngrad_per_point
    2352           38 :          IF (features%uses_collapsed_rks_dynamic) THEN
    2353           32 :             nroute_grad_per_point = ncollapsed_grad_per_point
    2354              :          END IF
    2355              :          ALLOCATE (send_grad_buffer(MAX(1, nroute_grad_per_point*nroute_recv_points)), &
    2356              :                    recv_grad_buffer(MAX(1, nroute_grad_per_point*nroute_send_points)), &
    2357              :                    route_grad_return_send_counts(SIZE(features%route_point_recv_counts)), &
    2358              :                    route_grad_return_send_displs(SIZE(features%route_point_recv_displs)), &
    2359              :                    route_grad_return_recv_counts(SIZE(features%route_point_send_counts)), &
    2360          494 :                    route_grad_return_recv_displs(SIZE(features%route_point_send_displs)))
    2361              :          route_grad_return_send_counts(:) = &
    2362          114 :             nroute_grad_per_point*features%route_point_recv_counts
    2363              :          route_grad_return_send_displs(:) = &
    2364          114 :             nroute_grad_per_point*features%route_point_recv_displs
    2365              :          route_grad_return_recv_counts(:) = &
    2366          114 :             nroute_grad_per_point*features%route_point_send_counts
    2367              :          route_grad_return_recv_displs(:) = &
    2368          114 :             nroute_grad_per_point*features%route_point_send_displs
    2369              : 
    2370           38 :          IF (features%chunk_feature_count > 0) THEN
    2371           38 :             CALL timeset("skala_gpw_grad_torch_pack", phase_handle)
    2372              :             CALL pack_atom_chunk_grads(features, send_grad_buffer, .TRUE., &
    2373           38 :                                        features%uses_collapsed_rks_dynamic)
    2374           38 :             CALL timestop(phase_handle)
    2375              :          END IF
    2376              : 
    2377           38 :          CALL timeset("skala_gpw_grad_route_comm", phase_handle)
    2378              :          CALL group%alltoall(send_grad_buffer, route_grad_return_send_counts, &
    2379              :                              route_grad_return_send_displs, recv_grad_buffer, &
    2380           38 :                              route_grad_return_recv_counts, route_grad_return_recv_displs)
    2381           38 :          CALL timestop(phase_handle)
    2382              : 
    2383           38 :          CALL timeset("skala_gpw_grad_route_scatter", phase_handle)
    2384              :          CALL scatter_routed_atom_chunk_grads(features, recv_grad_buffer, &
    2385              :                                               features%uses_collapsed_rks_dynamic, &
    2386           38 :                                               density_grad, grad_grad, kin_grad)
    2387           38 :          CALL timestop(phase_handle)
    2388              : 
    2389            0 :          DEALLOCATE (recv_grad_buffer, route_grad_return_recv_counts, &
    2390            0 :                      route_grad_return_recv_displs, route_grad_return_send_counts, &
    2391          114 :                      route_grad_return_send_displs, send_grad_buffer)
    2392              :       ELSE
    2393              :          ALLOCATE (chunk_grad_buffer(MAX(1, ngrad_per_point*features%chunk_feature_count)), &
    2394            0 :                    global_grad_buffer(ngrad_per_point*features%nflat))
    2395            0 :          IF (features%chunk_feature_count > 0) THEN
    2396            0 :             CALL timeset("skala_gpw_grad_torch_pack", phase_handle)
    2397            0 :             CALL pack_atom_chunk_grads(features, chunk_grad_buffer, .FALSE.)
    2398            0 :             CALL timestop(phase_handle)
    2399              :          END IF
    2400              : 
    2401            0 :          CALL timeset("skala_gpw_grad_allgatherv", phase_handle)
    2402              :          CALL group%allgatherv(chunk_grad_buffer, global_grad_buffer, &
    2403            0 :                                features%chunk_grad_counts, features%chunk_grad_displs)
    2404            0 :          CALL timestop(phase_handle)
    2405              : 
    2406            0 :          CALL timeset("skala_gpw_grad_scatter", phase_handle)
    2407            0 :          ALLOCATE (density_grad(nflat_local, 2), grad_grad(nflat_local, 3, 2), &
    2408            0 :                    kin_grad(nflat_local, 2))
    2409            0 :          density_grad = 0.0_dp
    2410            0 :          grad_grad = 0.0_dp
    2411            0 :          kin_grad = 0.0_dp
    2412            0 :          local_row = 0
    2413            0 :          DO k = LBOUND(features%feature_index, 3), UBOUND(features%feature_index, 3)
    2414            0 :             DO j = LBOUND(features%feature_index, 2), UBOUND(features%feature_index, 2)
    2415            0 :                DO i = LBOUND(features%feature_index, 1), UBOUND(features%feature_index, 1)
    2416            0 :                   local_row = local_row + 1
    2417            0 :                   DO feature_pos = features%local_feature_offsets(local_row), &
    2418            0 :                      features%local_feature_offsets(local_row + 1) - 1
    2419            0 :                      row = features%local_feature_rows(feature_pos)
    2420            0 :                      CPASSERT(row >= 1 .AND. row <= features%nflat)
    2421            0 :                      base = ngrad_per_point*(row - 1)
    2422              :                      density_grad(local_row, :) = density_grad(local_row, :) + &
    2423            0 :                                                   global_grad_buffer(base + 1:base + 2)
    2424              :                      grad_grad(local_row, 1, 1) = grad_grad(local_row, 1, 1) + &
    2425            0 :                                                   global_grad_buffer(base + 3)
    2426              :                      grad_grad(local_row, 2, 1) = grad_grad(local_row, 2, 1) + &
    2427            0 :                                                   global_grad_buffer(base + 4)
    2428              :                      grad_grad(local_row, 3, 1) = grad_grad(local_row, 3, 1) + &
    2429            0 :                                                   global_grad_buffer(base + 5)
    2430              :                      grad_grad(local_row, 1, 2) = grad_grad(local_row, 1, 2) + &
    2431            0 :                                                   global_grad_buffer(base + 6)
    2432              :                      grad_grad(local_row, 2, 2) = grad_grad(local_row, 2, 2) + &
    2433            0 :                                                   global_grad_buffer(base + 7)
    2434              :                      grad_grad(local_row, 3, 2) = grad_grad(local_row, 3, 2) + &
    2435            0 :                                                   global_grad_buffer(base + 8)
    2436              :                      kin_grad(local_row, :) = kin_grad(local_row, :) + &
    2437            0 :                                               global_grad_buffer(base + 9:base + 10)
    2438              :                   END DO
    2439              :                END DO
    2440              :             END DO
    2441              :          END DO
    2442            0 :          CALL timestop(phase_handle)
    2443            0 :          DEALLOCATE (chunk_grad_buffer, global_grad_buffer)
    2444              : 
    2445              :       END IF
    2446              : 
    2447           38 :    END SUBROUTINE fetch_and_gather_atom_chunk_grads
    2448              : 
    2449              : ! **************************************************************************************************
    2450              : !> \brief Build the native SKALA XC virial from feature gradients.
    2451              : !> \param virial_xc ...
    2452              : !> \param rho_set ...
    2453              : !> \param rho_r ...
    2454              : !> \param grad_grad ...
    2455              : ! **************************************************************************************************
    2456            6 :    SUBROUTINE build_virial_from_feature_grads(virial_xc, rho_set, rho_r, grad_grad)
    2457              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(INOUT)      :: virial_xc
    2458              :       TYPE(xc_rho_set_type), INTENT(IN)                  :: rho_set
    2459              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: rho_r
    2460              :       REAL(KIND=dp), DIMENSION(:, :, :), INTENT(IN)      :: grad_grad
    2461              : 
    2462              :       INTEGER                                            :: i, idir, ipt, ispin, j, jdir, k, nspins
    2463              :       INTEGER, DIMENSION(2, 3)                           :: bo
    2464              :       REAL(KIND=dp)                                      :: grad_i, tmp
    2465           72 :       TYPE(cp_3d_r_cp_type), DIMENSION(3)                :: drho, drhoa, drhob
    2466              : 
    2467            6 :       nspins = SIZE(rho_r)
    2468           60 :       bo = rho_r(1)%pw_grid%bounds_local
    2469            6 :       ipt = 0
    2470              : 
    2471            6 :       IF (nspins == 1) THEN
    2472            6 :          CALL xc_rho_set_get(rho_set, drho=drho)
    2473          150 :          DO k = bo(1, 3), bo(2, 3)
    2474         3606 :             DO j = bo(1, 2), bo(2, 2)
    2475        45072 :                DO i = bo(1, 1), bo(2, 1)
    2476        41472 :                   ipt = ipt + 1
    2477       169344 :                   DO idir = 1, 3
    2478       124416 :                      grad_i = 0.5_dp*(grad_grad(ipt, idir, 1) + grad_grad(ipt, idir, 2))
    2479       539136 :                      DO jdir = 1, 3
    2480       373248 :                         tmp = -grad_i*drho(jdir)%array(i, j, k)
    2481       497664 :                         virial_xc(jdir, idir) = virial_xc(jdir, idir) + tmp
    2482              :                      END DO
    2483              :                   END DO
    2484              :                END DO
    2485              :             END DO
    2486              :          END DO
    2487              :       ELSE
    2488            0 :          CALL xc_rho_set_get(rho_set, drhoa=drhoa, drhob=drhob)
    2489            0 :          DO k = bo(1, 3), bo(2, 3)
    2490            0 :             DO j = bo(1, 2), bo(2, 2)
    2491            0 :                DO i = bo(1, 1), bo(2, 1)
    2492            0 :                   ipt = ipt + 1
    2493            0 :                   DO idir = 1, 3
    2494            0 :                      DO jdir = 1, 3
    2495              :                         tmp = 0.0_dp
    2496            0 :                         DO ispin = 1, 2
    2497            0 :                            IF (ispin == 1) THEN
    2498            0 :                               tmp = tmp - grad_grad(ipt, idir, ispin)*drhoa(jdir)%array(i, j, k)
    2499              :                            ELSE
    2500            0 :                               tmp = tmp - grad_grad(ipt, idir, ispin)*drhob(jdir)%array(i, j, k)
    2501              :                            END IF
    2502              :                         END DO
    2503            0 :                         virial_xc(jdir, idir) = virial_xc(jdir, idir) + tmp
    2504              :                      END DO
    2505              :                   END DO
    2506              :                END DO
    2507              :             END DO
    2508              :          END DO
    2509              :       END IF
    2510              : 
    2511            6 :    END SUBROUTINE build_virial_from_feature_grads
    2512              : 
    2513              : ! **************************************************************************************************
    2514              : !> \brief Print a native SKALA XC virial contribution for diagnostics.
    2515              : !> \param label ...
    2516              : !> \param delta ...
    2517              : !> \param root_rank ...
    2518              : ! **************************************************************************************************
    2519            0 :    SUBROUTINE print_virial_delta(label, delta, root_rank)
    2520              :       CHARACTER(LEN=*), INTENT(IN)                       :: label
    2521              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(IN)         :: delta
    2522              :       LOGICAL, INTENT(IN)                                :: root_rank
    2523              : 
    2524              :       INTEGER                                            :: i, iw
    2525              : 
    2526            0 :       IF (.NOT. root_rank) RETURN
    2527            0 :       iw = cp_logger_get_default_io_unit()
    2528            0 :       IF (iw <= 0) RETURN
    2529            0 :       WRITE (iw, "(T2,A,1X,A)") "SKALA_GPW| XC virial contribution", TRIM(label)
    2530            0 :       DO i = 1, 3
    2531            0 :          WRITE (iw, "(T2,A,1X,3ES20.10)") "SKALA_GPW|", delta(i, 1:3)
    2532              :       END DO
    2533              : 
    2534              :    END SUBROUTINE print_virial_delta
    2535              : 
    2536              : ! **************************************************************************************************
    2537              : !> \brief Add explicit SKALA coordinate-feature contributions to the XC virial.
    2538              : !> \param virial_xc ...
    2539              : !> \param features ...
    2540              : !> \param atom_coord_grad_t ...
    2541              : !> \param grid_coord_grad_t ...
    2542              : !> \param root_rank ...
    2543              : !> \param print_components ...
    2544              : ! **************************************************************************************************
    2545            6 :    SUBROUTINE build_static_coordinate_virial(virial_xc, features, atom_coord_grad_t, &
    2546              :                                              grid_coord_grad_t, root_rank, print_components)
    2547              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(INOUT)      :: virial_xc
    2548              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    2549              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: atom_coord_grad_t, grid_coord_grad_t
    2550              :       LOGICAL, INTENT(IN)                                :: root_rank
    2551              :       LOGICAL, INTENT(IN), OPTIONAL                      :: print_components
    2552              : 
    2553              :       INTEGER                                            :: feature_pos, i, iatom, idir, iw, j, &
    2554              :                                                             jdir, k, local_row, row
    2555              :       LOGICAL                                            :: my_print_components
    2556              :       REAL(KIND=dp)                                      :: tmp
    2557              :       REAL(KIND=dp), DIMENSION(3)                        :: atom_grad_sum, grid_grad_sum
    2558              :       REAL(KIND=dp), DIMENSION(3, 3)                     :: atom_virial, grid_virial
    2559            6 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: atom_coord_grad, grid_coord_grad
    2560              : 
    2561            6 :       my_print_components = .FALSE.
    2562            6 :       IF (PRESENT(print_components)) my_print_components = print_components
    2563              : 
    2564            6 :       NULLIFY (atom_coord_grad, grid_coord_grad)
    2565            6 :       CALL torch_tensor_grad(features%grid_coords_t, grid_coord_grad_t)
    2566            6 :       CALL torch_tensor_data_ptr(grid_coord_grad_t, grid_coord_grad)
    2567            6 :       CALL torch_tensor_data_ptr(atom_coord_grad_t, atom_coord_grad)
    2568              : 
    2569            6 :       grid_virial = 0.0_dp
    2570            6 :       atom_virial = 0.0_dp
    2571            6 :       grid_grad_sum = 0.0_dp
    2572            6 :       atom_grad_sum = 0.0_dp
    2573            6 :       local_row = 0
    2574          162 :       DO k = LBOUND(features%feature_index, 3), UBOUND(features%feature_index, 3)
    2575         3894 :          DO j = LBOUND(features%feature_index, 2), UBOUND(features%feature_index, 2)
    2576        51984 :             DO i = LBOUND(features%feature_index, 1), UBOUND(features%feature_index, 1)
    2577        41472 :                local_row = local_row + 1
    2578       124206 :                DO feature_pos = features%local_feature_offsets(local_row), &
    2579        44928 :                   features%local_feature_offsets(local_row + 1) - 1
    2580        82734 :                   row = features%local_feature_rows(feature_pos)
    2581       330936 :                   grid_grad_sum(:) = grid_grad_sum(:) + grid_coord_grad(:, row)
    2582       372408 :                   DO idir = 1, 3
    2583      1075542 :                      DO jdir = 1, 3
    2584       744606 :                         tmp = grid_coord_grad(idir, row)*features%grid_coords(jdir, row)
    2585       744606 :                         grid_virial(idir, jdir) = grid_virial(idir, jdir) + tmp
    2586       992808 :                         virial_xc(idir, jdir) = virial_xc(idir, jdir) + tmp
    2587              :                      END DO
    2588              :                   END DO
    2589              :                END DO
    2590              :             END DO
    2591              :          END DO
    2592              :       END DO
    2593            6 :       CPASSERT(local_row == features%nflat_local)
    2594              : 
    2595            6 :       IF (root_rank) THEN
    2596            9 :          DO iatom = 1, SIZE(features%coarse_0_atomic_coords, 2)
    2597           24 :             atom_grad_sum(:) = atom_grad_sum(:) + atom_coord_grad(:, iatom)
    2598           27 :             DO idir = 1, 3
    2599           78 :                DO jdir = 1, 3
    2600           54 :                   tmp = atom_coord_grad(idir, iatom)*features%coarse_0_atomic_coords(jdir, iatom)
    2601           54 :                   atom_virial(idir, jdir) = atom_virial(idir, jdir) + tmp
    2602           72 :                   virial_xc(idir, jdir) = virial_xc(idir, jdir) + tmp
    2603              :                END DO
    2604              :             END DO
    2605              :          END DO
    2606              :       END IF
    2607              : 
    2608            6 :       IF (my_print_components .AND. root_rank) THEN
    2609            0 :          iw = cp_logger_get_default_io_unit()
    2610            0 :          IF (iw > 0) THEN
    2611            0 :             CALL print_virial_delta("static-grid", grid_virial, .TRUE.)
    2612            0 :             CALL print_virial_delta("static-atom", atom_virial, .TRUE.)
    2613            0 :             WRITE (iw, "(T2,A,1X,3ES20.10)") "SKALA_GPW| XC coordinate gradient grid sum", &
    2614            0 :                grid_grad_sum
    2615            0 :             WRITE (iw, "(T2,A,1X,3ES20.10)") "SKALA_GPW| XC coordinate gradient atom sum", &
    2616            0 :                atom_grad_sum
    2617            0 :             WRITE (iw, "(T2,A,1X,3ES20.10)") "SKALA_GPW| XC coordinate gradient total sum", &
    2618            0 :                grid_grad_sum + atom_grad_sum
    2619              :          END IF
    2620              :       END IF
    2621              : 
    2622            6 :       CALL torch_tensor_release(grid_coord_grad_t)
    2623              : 
    2624            6 :    END SUBROUTINE build_static_coordinate_virial
    2625              : 
    2626              : ! **************************************************************************************************
    2627              : !> \brief Add residual SKALA weight-feature contributions to the XC virial.
    2628              : !> \param virial_xc ...
    2629              : !> \param features ...
    2630              : !> \param exc ...
    2631              : !> \param grid_weight_grad_t ...
    2632              : !> \param atomic_grid_weight_grad_t ...
    2633              : !> \param root_rank ...
    2634              : !> \param print_components ...
    2635              : ! **************************************************************************************************
    2636            6 :    SUBROUTINE build_weight_virial(virial_xc, features, exc, grid_weight_grad_t, &
    2637              :                                   atomic_grid_weight_grad_t, root_rank, print_components)
    2638              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(INOUT)      :: virial_xc
    2639              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    2640              :       REAL(KIND=dp), INTENT(IN)                          :: exc
    2641              :       TYPE(torch_tensor_type), INTENT(INOUT)             :: grid_weight_grad_t, &
    2642              :                                                             atomic_grid_weight_grad_t
    2643              :       LOGICAL, INTENT(IN)                                :: root_rank
    2644              :       LOGICAL, INTENT(IN), OPTIONAL                      :: print_components
    2645              : 
    2646              :       INTEGER                                            :: feature_pos, i, idir, iw, j, k, &
    2647              :                                                             local_row, row
    2648              :       LOGICAL                                            :: my_print_components
    2649              :       REAL(KIND=dp)                                      :: atomic_tmp, exc_tmp, grid_tmp, tmp
    2650            6 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: atomic_grid_weight_grad, grid_weight_grad
    2651              : 
    2652            6 :       my_print_components = .FALSE.
    2653            6 :       IF (PRESENT(print_components)) my_print_components = print_components
    2654              : 
    2655            6 :       NULLIFY (atomic_grid_weight_grad, grid_weight_grad)
    2656            6 :       CALL torch_tensor_grad(features%grid_weights_t, grid_weight_grad_t)
    2657            6 :       CALL torch_tensor_grad(features%atomic_grid_weights_t, atomic_grid_weight_grad_t)
    2658            6 :       CALL torch_tensor_data_ptr(grid_weight_grad_t, grid_weight_grad)
    2659            6 :       CALL torch_tensor_data_ptr(atomic_grid_weight_grad_t, atomic_grid_weight_grad)
    2660              : 
    2661            6 :       grid_tmp = 0.0_dp
    2662            6 :       atomic_tmp = 0.0_dp
    2663            6 :       local_row = 0
    2664          162 :       DO k = LBOUND(features%feature_index, 3), UBOUND(features%feature_index, 3)
    2665         3894 :          DO j = LBOUND(features%feature_index, 2), UBOUND(features%feature_index, 2)
    2666        51984 :             DO i = LBOUND(features%feature_index, 1), UBOUND(features%feature_index, 1)
    2667        41472 :                local_row = local_row + 1
    2668       124206 :                DO feature_pos = features%local_feature_offsets(local_row), &
    2669        44928 :                   features%local_feature_offsets(local_row + 1) - 1
    2670        82734 :                   row = features%local_feature_rows(feature_pos)
    2671        82734 :                   grid_tmp = grid_tmp + grid_weight_grad(row)*features%grid_weights(row)
    2672              :                   atomic_tmp = atomic_tmp + &
    2673       124206 :                                atomic_grid_weight_grad(row)*features%atomic_grid_weights(row)
    2674              :                END DO
    2675              :             END DO
    2676              :          END DO
    2677              :       END DO
    2678            6 :       CPASSERT(local_row == features%nflat_local)
    2679            6 :       exc_tmp = 0.0_dp
    2680            6 :       IF (root_rank) exc_tmp = -exc
    2681            6 :       tmp = grid_tmp + atomic_tmp + exc_tmp
    2682              : 
    2683            6 :       IF (my_print_components .AND. root_rank) THEN
    2684            0 :          iw = cp_logger_get_default_io_unit()
    2685            0 :          IF (iw > 0) THEN
    2686            0 :             WRITE (iw, "(T2,A,1X,ES20.10)") "SKALA_GPW| XC virial weight grid", grid_tmp
    2687            0 :             WRITE (iw, "(T2,A,1X,ES20.10)") "SKALA_GPW| XC virial weight atomic", atomic_tmp
    2688            0 :             WRITE (iw, "(T2,A,1X,ES20.10)") "SKALA_GPW| XC virial weight final", exc_tmp
    2689            0 :             WRITE (iw, "(T2,A,1X,ES20.10)") "SKALA_GPW| XC virial weight residual", tmp
    2690              :          END IF
    2691              :       END IF
    2692              : 
    2693           24 :       DO idir = 1, 3
    2694           24 :          virial_xc(idir, idir) = virial_xc(idir, idir) + tmp
    2695              :       END DO
    2696              : 
    2697            6 :       CALL torch_tensor_release(grid_weight_grad_t)
    2698            6 :       CALL torch_tensor_release(atomic_grid_weight_grad_t)
    2699              : 
    2700            6 :    END SUBROUTINE build_weight_virial
    2701              : 
    2702              : ! **************************************************************************************************
    2703              : !> \brief Fill CP2K VXC real-space arrays from Torch feature gradients.
    2704              : !> \param vxc_rho ...
    2705              : !> \param vxc_tau ...
    2706              : !> \param rho_r ...
    2707              : !> \param pw_pool ...
    2708              : !> \param density_grad ...
    2709              : !> \param grad_grad ...
    2710              : !> \param kin_grad ...
    2711              : !> \param xc_deriv_method_id ...
    2712              : !> \param global_grid_layout ...
    2713              : ! **************************************************************************************************
    2714          300 :    SUBROUTINE build_vxc_from_feature_grads(vxc_rho, vxc_tau, rho_r, pw_pool, &
    2715          300 :                                            density_grad, grad_grad, kin_grad, &
    2716              :                                            xc_deriv_method_id, global_grid_layout)
    2717              :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: vxc_rho, vxc_tau, rho_r
    2718              :       TYPE(pw_pool_type), POINTER                        :: pw_pool
    2719              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: density_grad
    2720              :       REAL(KIND=dp), DIMENSION(:, :, :), INTENT(IN)      :: grad_grad
    2721              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: kin_grad
    2722              :       INTEGER, INTENT(IN)                                :: xc_deriv_method_id
    2723              :       LOGICAL, INTENT(IN), OPTIONAL                      :: global_grid_layout
    2724              : 
    2725              :       INTEGER                                            :: i, ipt, ispin, j, k, nspins
    2726              :       INTEGER, DIMENSION(2, 3)                           :: bo
    2727              :       LOGICAL                                            :: my_global_grid_layout, valid_spin_shape
    2728              :       REAL(KIND=dp)                                      :: dvol_inv
    2729              :       TYPE(pw_c1d_gs_type)                               :: tmp_g, vxc_g
    2730         1200 :       TYPE(pw_r3d_rs_type), DIMENSION(3)                 :: grad_pw
    2731              : 
    2732          300 :       nspins = SIZE(rho_r)
    2733         3000 :       bo = rho_r(1)%pw_grid%bounds_local
    2734          300 :       dvol_inv = 1.0_dp/rho_r(1)%pw_grid%dvol
    2735          300 :       my_global_grid_layout = .FALSE.
    2736          300 :       IF (PRESENT(global_grid_layout)) my_global_grid_layout = global_grid_layout
    2737          252 :       IF (my_global_grid_layout) THEN
    2738         1008 :          CPASSERT(SIZE(density_grad, 1) == PRODUCT(rho_r(1)%pw_grid%npts))
    2739              :       END IF
    2740          300 :       CPASSERT(SIZE(kin_grad, 2) == SIZE(density_grad, 2))
    2741          300 :       CPASSERT(SIZE(grad_grad, 3) == SIZE(density_grad, 2))
    2742          300 :       CPASSERT(SIZE(grad_grad, 2) == 3)
    2743              :       valid_spin_shape = SIZE(density_grad, 2) == nspins .OR. &
    2744          300 :                          (nspins == 1 .AND. SIZE(density_grad, 2) == 2)
    2745            0 :       CPASSERT(valid_spin_shape)
    2746              : 
    2747         1896 :       ALLOCATE (vxc_rho(nspins), vxc_tau(nspins))
    2748          648 :       DO ispin = 1, nspins
    2749          348 :          CALL pw_pool%create_pw(vxc_rho(ispin))
    2750          348 :          CALL pw_pool%create_pw(vxc_tau(ispin))
    2751          348 :          CALL pw_zero(vxc_rho(ispin))
    2752          648 :          CALL pw_zero(vxc_tau(ispin))
    2753              :       END DO
    2754              : 
    2755          300 :       IF (xc_requires_tmp_g(xc_deriv_method_id) .OR. rho_r(1)%pw_grid%spherical) THEN
    2756          300 :          CALL pw_pool%create_pw(vxc_g)
    2757          300 :          IF (.NOT. rho_r(1)%pw_grid%spherical) CALL pw_pool%create_pw(tmp_g)
    2758              :       END IF
    2759              : 
    2760          648 :       DO ispin = 1, nspins
    2761         1392 :          DO i = 1, 3
    2762         1044 :             CALL pw_pool%create_pw(grad_pw(i))
    2763         1392 :             CALL pw_zero(grad_pw(i))
    2764              :          END DO
    2765              : 
    2766          348 :          ipt = 0
    2767         7448 :          DO k = bo(1, 3), bo(2, 3)
    2768       176620 :             DO j = bo(1, 2), bo(2, 2)
    2769      2579354 :                DO i = bo(1, 1), bo(2, 1)
    2770      2403082 :                   IF (my_global_grid_layout) THEN
    2771              :                      ipt = 1 + i - rho_r(1)%pw_grid%bounds(1, 1) + &
    2772              :                            rho_r(1)%pw_grid%npts(1)*( &
    2773              :                            j - rho_r(1)%pw_grid%bounds(1, 2) + &
    2774              :                            rho_r(1)%pw_grid%npts(2)*( &
    2775      1163283 :                            k - rho_r(1)%pw_grid%bounds(1, 3)))
    2776              :                   ELSE
    2777      1239799 :                      ipt = ipt + 1
    2778              :                   END IF
    2779      2572254 :                   IF (nspins == 1) THEN
    2780      1714582 :                      IF (SIZE(density_grad, 2) == 1) THEN
    2781      1021533 :                         vxc_rho(1)%array(i, j, k) = 0.5_dp*dvol_inv*density_grad(ipt, 1)
    2782      1021533 :                         vxc_tau(1)%array(i, j, k) = 0.5_dp*dvol_inv*kin_grad(ipt, 1)
    2783      1021533 :                         grad_pw(1)%array(i, j, k) = 0.5_dp*dvol_inv*grad_grad(ipt, 1, 1)
    2784      1021533 :                         grad_pw(2)%array(i, j, k) = 0.5_dp*dvol_inv*grad_grad(ipt, 2, 1)
    2785      1021533 :                         grad_pw(3)%array(i, j, k) = 0.5_dp*dvol_inv*grad_grad(ipt, 3, 1)
    2786              :                      ELSE
    2787              :                         vxc_rho(1)%array(i, j, k) = 0.5_dp*dvol_inv* &
    2788       693049 :                                                     (density_grad(ipt, 1) + density_grad(ipt, 2))
    2789              :                         vxc_tau(1)%array(i, j, k) = 0.5_dp*dvol_inv* &
    2790       693049 :                                                     (kin_grad(ipt, 1) + kin_grad(ipt, 2))
    2791              :                         grad_pw(1)%array(i, j, k) = 0.5_dp*dvol_inv* &
    2792       693049 :                                                     (grad_grad(ipt, 1, 1) + grad_grad(ipt, 1, 2))
    2793              :                         grad_pw(2)%array(i, j, k) = 0.5_dp*dvol_inv* &
    2794       693049 :                                                     (grad_grad(ipt, 2, 1) + grad_grad(ipt, 2, 2))
    2795              :                         grad_pw(3)%array(i, j, k) = 0.5_dp*dvol_inv* &
    2796       693049 :                                                     (grad_grad(ipt, 3, 1) + grad_grad(ipt, 3, 2))
    2797              :                      END IF
    2798              :                   ELSE
    2799       688500 :                      vxc_rho(ispin)%array(i, j, k) = dvol_inv*density_grad(ipt, ispin)
    2800       688500 :                      vxc_tau(ispin)%array(i, j, k) = dvol_inv*kin_grad(ipt, ispin)
    2801       688500 :                      grad_pw(1)%array(i, j, k) = dvol_inv*grad_grad(ipt, 1, ispin)
    2802       688500 :                      grad_pw(2)%array(i, j, k) = dvol_inv*grad_grad(ipt, 2, ispin)
    2803       688500 :                      grad_pw(3)%array(i, j, k) = dvol_inv*grad_grad(ipt, 3, ispin)
    2804              :                   END IF
    2805              :                END DO
    2806              :             END DO
    2807              :          END DO
    2808              : 
    2809         1392 :          DO i = 1, 3
    2810         1392 :             CALL pw_scale(grad_pw(i), -1.0_dp)
    2811              :          END DO
    2812          348 :          CALL xc_pw_divergence(xc_deriv_method_id, grad_pw, tmp_g, vxc_g, vxc_rho(ispin))
    2813              : 
    2814         1692 :          DO i = 1, 3
    2815         1392 :             CALL pw_pool%give_back_pw(grad_pw(i))
    2816              :          END DO
    2817              :       END DO
    2818              : 
    2819          300 :       IF (ASSOCIATED(vxc_g%pw_grid)) CALL pw_pool%give_back_pw(vxc_g)
    2820          300 :       IF (ASSOCIATED(tmp_g%pw_grid)) CALL pw_pool%give_back_pw(tmp_g)
    2821              : 
    2822          300 :    END SUBROUTINE build_vxc_from_feature_grads
    2823              : 
    2824              : ! **************************************************************************************************
    2825              : !> \brief Print optional diagnostics for the CP2K-native SKALA GPW feature block.
    2826              : !> \param features ...
    2827              : !> \param print_active ...
    2828              : ! **************************************************************************************************
    2829           24 :    SUBROUTINE print_native_grid_diagnostics(features, print_active)
    2830              :       TYPE(skala_gpw_feature_type), INTENT(IN)           :: features
    2831              :       LOGICAL, INTENT(IN)                                :: print_active
    2832              : 
    2833              :       INTEGER                                            :: atom_rows_max, atom_rows_min, &
    2834              :                                                             chunk_rows_max, chunk_rows_min, iw
    2835              :       REAL(KIND=dp)                                      :: chunk_imbalance
    2836              : 
    2837           24 :       IF (.NOT. print_active) RETURN
    2838              : 
    2839           12 :       iw = cp_logger_get_default_io_unit()
    2840           12 :       IF (iw <= 0) RETURN
    2841              :       WRITE (UNIT=iw, FMT="(/,T2,A,1X,ES19.11)") &
    2842           12 :          "SKALA_GPW| Native grid feature electrons", features%electron_count
    2843              :       WRITE (UNIT=iw, FMT="(T2,A,1X,ES19.11)") &
    2844           12 :          "SKALA_GPW| Native grid feature spin moment", features%spin_moment
    2845              :       WRITE (UNIT=iw, FMT="(T2,A,1X,ES19.11)") &
    2846           12 :          "SKALA_GPW| Native grid feature kinetic integral", features%kinetic_integral
    2847              :       WRITE (UNIT=iw, FMT="(T2,A,1X,ES19.11)") &
    2848           12 :          "SKALA_GPW| Native grid feature weight sum", features%grid_weight_sum
    2849           12 :       IF (ALLOCATED(features%atomic_grid_sizes)) THEN
    2850           49 :          atom_rows_min = INT(MINVAL(features%atomic_grid_sizes))
    2851           49 :          atom_rows_max = INT(MAXVAL(features%atomic_grid_sizes))
    2852              :          WRITE (UNIT=iw, FMT="(T2,A,1X,I0,1X,A,1X,I0,1X,A,1X,I0)") &
    2853           12 :             "SKALA_GPW| Native grid atom row range", atom_rows_min, "to", &
    2854           61 :             atom_rows_max, "sum", INT(SUM(features%atomic_grid_sizes))
    2855              :       END IF
    2856           12 :       IF (features%uses_atom_chunks) THEN
    2857              :          WRITE (UNIT=iw, FMT="(T2,A,1X,I0,1X,A,1X,I0)") &
    2858           12 :             "SKALA_GPW| Native grid atom chunk rows", features%chunk_feature_count, &
    2859           24 :             "of", features%nflat
    2860           12 :          IF (ALLOCATED(features%chunk_grad_counts)) THEN
    2861           36 :             chunk_rows_min = MINVAL(features%chunk_grad_counts)/ngrad_per_point
    2862           36 :             chunk_rows_max = MAXVAL(features%chunk_grad_counts)/ngrad_per_point
    2863           12 :             chunk_imbalance = REAL(chunk_rows_max, KIND=dp)/REAL(MAX(1, chunk_rows_min), KIND=dp)
    2864              :             WRITE (UNIT=iw, FMT="(T2,A,1X,I0,1X,A,1X,I0,1X,A,1X,ES12.5)") &
    2865           12 :                "SKALA_GPW| Native grid atom chunk row range", chunk_rows_min, &
    2866           24 :                "to", chunk_rows_max, "imbalance", chunk_imbalance
    2867              :          END IF
    2868              :       END IF
    2869              : 
    2870              :    END SUBROUTINE print_native_grid_diagnostics
    2871              : 
    2872              : ! **************************************************************************************************
    2873              : !> \brief Configure CUDA device selection for the native SKALA GPW Torch path.
    2874              : !> \param use_cuda ...
    2875              : !> \param requested_device ...
    2876              : !> \param group ...
    2877              : !> \return selected CUDA device, or -1 for CPU fallback/no visible CUDA device
    2878              : ! **************************************************************************************************
    2879          304 :    FUNCTION configure_native_grid_cuda(use_cuda, requested_device, group) RESULT(selected_device)
    2880              :       LOGICAL, INTENT(IN)                                :: use_cuda
    2881              :       INTEGER, INTENT(IN)                                :: requested_device
    2882              : 
    2883              :       CLASS(mp_comm_type), INTENT(IN)                    :: group
    2884              : 
    2885              :       INTEGER                                            :: cuda_device_count, iw, pe, selected_device
    2886          304 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: selected_devices
    2887              : 
    2888          304 :       selected_device = -1
    2889              : 
    2890          304 :       IF (.NOT. use_cuda) RETURN
    2891              : 
    2892            0 :       IF (.NOT. torch_cuda_is_available()) THEN
    2893            0 :          cuda_device_count = 0
    2894              :       ELSE
    2895            0 :          cuda_device_count = torch_cuda_device_count()
    2896              :       END IF
    2897            0 :       IF (cuda_device_count > 0) THEN
    2898            0 :          IF (requested_device < 0) THEN
    2899            0 :             selected_device = MOD(group%mepos, cuda_device_count)
    2900              :          ELSE
    2901            0 :             selected_device = requested_device
    2902              :          END IF
    2903              :       END IF
    2904            0 :       IF (selected_device >= cuda_device_count) THEN
    2905              :          CALL cp_abort(__LOCATION__, &
    2906              :                        "GAUXC%NATIVE_GRID_CUDA_DEVICE selects a CUDA device outside the visible "// &
    2907            0 :                        "Torch CUDA device range.")
    2908              :       END IF
    2909            0 :       IF (selected_device >= 0) CALL offload_set_chosen_device(selected_device)
    2910              : 
    2911            0 :       ALLOCATE (selected_devices(group%num_pe))
    2912            0 :       CALL group%allgather(selected_device, selected_devices)
    2913              : 
    2914            0 :       IF (group%mepos /= 0) THEN
    2915            0 :          DEALLOCATE (selected_devices)
    2916            0 :          RETURN
    2917              :       END IF
    2918              :       IF (selected_device == logged_cuda_device .AND. &
    2919              :           cuda_device_count == logged_cuda_device_count .AND. &
    2920            0 :           group%num_pe == logged_cuda_nproc .AND. &
    2921              :           requested_device == logged_cuda_request) THEN
    2922            0 :          DEALLOCATE (selected_devices)
    2923            0 :          RETURN
    2924              :       END IF
    2925              : 
    2926            0 :       iw = cp_logger_get_default_io_unit()
    2927            0 :       IF (iw <= 0) THEN
    2928            0 :          DEALLOCATE (selected_devices)
    2929            0 :          RETURN
    2930              :       END IF
    2931            0 :       IF (selected_device >= 0) THEN
    2932              :          WRITE (UNIT=iw, FMT="(/,T2,A,1X,I0,1X,A,1X,I0,1X,A,1X,I0)") &
    2933            0 :             "SKALA_GPW| Native grid Torch CUDA device", selected_device, &
    2934            0 :             "of", cuda_device_count, "requested", requested_device
    2935              :       ELSE
    2936              :          WRITE (UNIT=iw, FMT="(/,T2,A)") &
    2937            0 :             "SKALA_GPW| Native grid Torch CUDA requested, but no Torch CUDA device is visible"
    2938              :       END IF
    2939              :       WRITE (UNIT=iw, FMT="(T2,A)", ADVANCE="NO") &
    2940            0 :          "SKALA_GPW| Native grid Torch CUDA rank devices"
    2941            0 :       DO pe = 1, group%num_pe
    2942            0 :          WRITE (UNIT=iw, FMT="(1X,I0,A,I0)", ADVANCE="NO") pe - 1, ":", selected_devices(pe)
    2943              :       END DO
    2944            0 :       WRITE (UNIT=iw, FMT=*)
    2945              : 
    2946            0 :       logged_cuda_device = selected_device
    2947            0 :       logged_cuda_device_count = cuda_device_count
    2948            0 :       logged_cuda_nproc = group%num_pe
    2949            0 :       logged_cuda_request = requested_device
    2950            0 :       DEALLOCATE (selected_devices)
    2951              : 
    2952          304 :    END FUNCTION configure_native_grid_cuda
    2953              : 
    2954              : ! **************************************************************************************************
    2955              : !> \brief Load and cache the TorchScript SKALA model.
    2956              : !> \param model_path ...
    2957              : !> \param cuda_device ...
    2958              : !> \param xc_section XC settings, including the native model precision.
    2959              : ! **************************************************************************************************
    2960          564 :    SUBROUTINE ensure_model_loaded(model_path, cuda_device, xc_section)
    2961              :       CHARACTER(len=*), INTENT(IN)                       :: model_path
    2962              :       INTEGER, INTENT(IN)                                :: cuda_device
    2963              :       TYPE(section_vals_type), POINTER                   :: xc_section
    2964              : 
    2965              :       LOGICAL                                            :: fp64
    2966              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
    2967              : 
    2968          564 :       gauxc_section => get_gauxc_section(xc_section)
    2969          282 :       CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_MODEL_FP64", l_val=fp64)
    2970              : 
    2971          282 :       IF (cached_model_loaded) THEN
    2972              :          IF (TRIM(cached_model_path) == TRIM(model_path) .AND. &
    2973          265 :              cached_model_cuda_device == cuda_device .AND. &
    2974          265 :              (cached_model_fp64 .EQV. fp64)) RETURN
    2975            0 :          CALL skala_torch_model_release(cached_model)
    2976            0 :          cached_model_loaded = .FALSE.
    2977              :       END IF
    2978              : 
    2979           17 :       CALL skala_torch_model_load(cached_model, TRIM(model_path), fp64=fp64)
    2980           17 :       cached_model_path = model_path
    2981           17 :       cached_model_cuda_device = cuda_device
    2982           17 :       cached_model_fp64 = fp64
    2983           17 :       cached_model_loaded = .TRUE.
    2984              : 
    2985          282 :    END SUBROUTINE ensure_model_loaded
    2986              : 
    2987              : ! **************************************************************************************************
    2988              : !> \brief Resolve the SKALA TorchScript model path from the GAUXC subsection.
    2989              : !> \param xc_section ...
    2990              : !> \param model_path ...
    2991              : ! **************************************************************************************************
    2992          304 :    SUBROUTINE get_skala_model_path(xc_section, model_path)
    2993              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
    2994              :       CHARACTER(len=default_path_length), INTENT(OUT)    :: model_path
    2995              : 
    2996              :       CHARACTER(len=default_path_length)                 :: model_key
    2997              :       INTEGER                                            :: env_status
    2998              :       LOGICAL                                            :: native_grid_use_cuda
    2999              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
    3000              : 
    3001          304 :       gauxc_section => get_gauxc_section(xc_section)
    3002          304 :       IF (.NOT. ASSOCIATED(gauxc_section)) THEN
    3003            0 :          CPABORT("Native SKALA GPW requires an XC_FUNCTIONAL%GAUXC section")
    3004              :       END IF
    3005              : 
    3006          304 :       CALL section_vals_val_get(gauxc_section, "MODEL", c_val=model_path)
    3007          304 :       model_key = ADJUSTL(model_path)
    3008          304 :       CALL uppercase(model_key)
    3009          304 :       IF (TRIM(model_key) == "NONE" .OR. TRIM(model_key) == "") THEN
    3010            0 :          CPABORT("Native SKALA GPW requires GAUXC%MODEL SKALA or a TorchScript model path")
    3011          304 :       ELSE IF (TRIM(model_key) == "SKALA") THEN
    3012          304 :          CALL section_vals_val_get(gauxc_section, "NATIVE_GRID_USE_CUDA", l_val=native_grid_use_cuda)
    3013          304 :          IF (native_grid_use_cuda) THEN
    3014            0 :             CALL GET_ENVIRONMENT_VARIABLE("GAUXC_SKALA_CUDA_MODEL", model_path, STATUS=env_status)
    3015            0 :             IF (env_status == 0 .AND. LEN_TRIM(model_path) > 0) RETURN
    3016              :          END IF
    3017          304 :          CALL GET_ENVIRONMENT_VARIABLE("SKALA_MODEL", model_path, STATUS=env_status)
    3018          304 :          IF (env_status /= 0 .OR. LEN_TRIM(model_path) == 0) THEN
    3019            0 :             IF (native_grid_use_cuda) THEN
    3020              :                CALL cp_abort(__LOCATION__, &
    3021            0 :                              "MODEL SKALA CUDA path requires GAUXC_SKALA_CUDA_MODEL or SKALA_MODEL")
    3022              :             ELSE
    3023              :                CALL cp_abort(__LOCATION__, &
    3024            0 :                              "MODEL SKALA requires the SKALA_MODEL environment variable")
    3025              :             END IF
    3026              :          END IF
    3027              :       END IF
    3028              : 
    3029              :    END SUBROUTINE get_skala_model_path
    3030              : 
    3031              : ! **************************************************************************************************
    3032              : !> \brief Return the first GAUXC functional subsection, if present.
    3033              : !> \param xc_section ...
    3034              : !> \return ...
    3035              : ! **************************************************************************************************
    3036       735989 :    FUNCTION get_gauxc_section(xc_section) RESULT(gauxc_section)
    3037              :       TYPE(section_vals_type), INTENT(IN), POINTER       :: xc_section
    3038              :       TYPE(section_vals_type), POINTER                   :: gauxc_section
    3039              : 
    3040              :       INTEGER                                            :: ifun
    3041              :       TYPE(section_vals_type), POINTER                   :: functionals, xc_fun
    3042              : 
    3043       735989 :       NULLIFY (gauxc_section)
    3044       735989 :       IF (.NOT. ASSOCIATED(xc_section)) RETURN
    3045              : 
    3046       735989 :       functionals => section_vals_get_subs_vals(xc_section, "XC_FUNCTIONAL")
    3047       735989 :       IF (.NOT. ASSOCIATED(functionals)) RETURN
    3048              : 
    3049       735989 :       ifun = 0
    3050              :       DO
    3051      1424852 :          ifun = ifun + 1
    3052      1424852 :          xc_fun => section_vals_get_subs_vals2(functionals, i_section=ifun)
    3053      1424852 :          IF (.NOT. ASSOCIATED(xc_fun)) EXIT
    3054      1424852 :          IF (xc_fun%section%name == "GAUXC") THEN
    3055              :             gauxc_section => xc_fun
    3056              :             EXIT
    3057              :          END IF
    3058              :       END DO
    3059              : 
    3060              :    END FUNCTION get_gauxc_section
    3061              : 
    3062            0 : END MODULE skala_gpw_functional
        

Generated by: LCOV version 2.0-1