LCOV - code coverage report
Current view: top level - src/xc - skala_gpw_functional.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:123285f) Lines: 79.2 % 1394 1104
Test Date: 2026-11-04 00:58:17 Functions: 92.3 % 39 36

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

Generated by: LCOV version 2.0-1