LCOV - code coverage report
Current view: top level - src - cc4s_export.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:123285f) Lines: 92.3 % 570 526
Test Date: 2026-11-04 00:58:17 Functions: 100.0 % 17 17

            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 Molecular and Gamma-point periodic CC4S export from restricted RI-HF.
      10              : !>        RI_FLAVOR MO stores a weighted AO-pair triangle. The full vertex is
      11              : !>        C^T (T + T^T) C, dressed with the symmetric Coulomb inverse square root.
      12              : !>        The SCF tensors and orbitals are read only. MO transformations are distributed
      13              : !>        and bounded output pieces are gathered to the writing rank.
      14              : ! **************************************************************************************************
      15              : MODULE cc4s_export
      16              :    USE ISO_FORTRAN_ENV,                 ONLY: file_storage_size
      17              :    USE cell_types,                      ONLY: cell_type,&
      18              :                                               plane_distance,&
      19              :                                               use_perd_none,&
      20              :                                               use_perd_xyz
      21              :    USE cp_control_types,                ONLY: dft_control_type
      22              :    USE cp_dbcsr_api,                    ONLY: dbcsr_p_type,&
      23              :                                               dbcsr_release,&
      24              :                                               dbcsr_type
      25              :    USE cp_dbcsr_operations,             ONLY: copy_dbcsr_to_fm,&
      26              :                                               cp_fm_to_dbcsr_row_template
      27              :    USE cp_fm_diag,                      ONLY: cp_fm_geeig_canon
      28              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_create,&
      29              :                                               cp_fm_struct_release,&
      30              :                                               cp_fm_struct_type
      31              :    USE cp_fm_types,                     ONLY: cp_fm_create,&
      32              :                                               cp_fm_release,&
      33              :                                               cp_fm_to_fm,&
      34              :                                               cp_fm_type
      35              :    USE cp_log_handling,                 ONLY: cp_get_default_logger,&
      36              :                                               cp_logger_get_default_io_unit,&
      37              :                                               cp_logger_type
      38              :    USE cp_output_handling,              ONLY: cp_p_file,&
      39              :                                               cp_print_key_finished_output,&
      40              :                                               cp_print_key_generate_filename,&
      41              :                                               cp_print_key_should_output,&
      42              :                                               cp_print_key_unit_nr,&
      43              :                                               cp_printkey_is_on
      44              :    USE dbt_api,                         ONLY: &
      45              :         dbt_contract, dbt_copy, dbt_copy_matrix_to_tensor, dbt_create, dbt_destroy, dbt_get_block, &
      46              :         dbt_get_info, dbt_iterator_blocks_left, dbt_iterator_next_block, dbt_iterator_start, &
      47              :         dbt_iterator_stop, dbt_iterator_type, dbt_nblks_total, dbt_pgrid_create, &
      48              :         dbt_pgrid_destroy, dbt_pgrid_type, dbt_type
      49              :    USE hfx_types,                       ONLY: hfx_type
      50              :    USE ieee_arithmetic,                 ONLY: ieee_is_finite
      51              :    USE input_constants,                 ONLY: do_potential_coulomb,&
      52              :                                               do_potential_truncated,&
      53              :                                               sic_none,&
      54              :                                               xc_none
      55              :    USE input_cp2k_hfx,                  ONLY: ri_mo
      56              :    USE input_section_types,             ONLY: section_vals_get,&
      57              :                                               section_vals_get_subs_vals,&
      58              :                                               section_vals_type,&
      59              :                                               section_vals_val_get
      60              :    USE kinds,                           ONLY: default_path_length,&
      61              :                                               dp,&
      62              :                                               dp_size,&
      63              :                                               int_8
      64              :    USE machine,                         ONLY: m_walltime
      65              :    USE message_passing,                 ONLY: mp_para_env_type
      66              :    USE qs_environment_types,            ONLY: get_qs_env,&
      67              :                                               qs_environment_type
      68              :    USE qs_mo_types,                     ONLY: get_mo_set,&
      69              :                                               has_uniform_occupation,&
      70              :                                               mo_set_type
      71              :    USE qs_tensors_types,                ONLY: create_2c_tensor,&
      72              :                                               create_3c_tensor
      73              :    USE scf_control_types,               ONLY: scf_control_type
      74              : #include "./base/base_uses.f90"
      75              : 
      76              :    IMPLICIT NONE
      77              :    PRIVATE
      78              :    PUBLIC :: write_cc4s
      79              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'cc4s_export'
      80              :    ! MPI statistics count bytes in default integers. DBT/DBM also use default-integer
      81              :    ! counts and grow buffers by up to 30%; leave a factor of two for data storage.
      82              :    INTEGER(int_8), PARAMETER :: output_count_limit = INT(HUGE(0), int_8)/dp_size
      83              :    INTEGER(int_8), PARAMETER :: backend_data_limit = output_count_limit/2
      84              :    ! Up to four 64-bit indices per block, with allocation/communication headroom.
      85              :    INTEGER(int_8), PARAMETER :: backend_block_limit = INT(HUGE(0), int_8)/64
      86              :    REAL(dp), PARAMETER :: elements_per_mib = 1024.0_dp**2/REAL(dp_size, dp)
      87              : 
      88              : CONTAINS
      89              : 
      90              : ! **************************************************************************************************
      91              : !> \brief Check the requested export, canonicalize private Fock/overlap copies and write tensors.
      92              : !> \param qs_env QS environment after SCF
      93              : !> \param converged Whether the SCF actually converged (including the outer SCF)
      94              : ! **************************************************************************************************
      95        24709 :    SUBROUTINE write_cc4s(qs_env, converged)
      96              :       TYPE(qs_environment_type), POINTER                 :: qs_env
      97              :       LOGICAL, INTENT(IN)                                :: converged
      98              : 
      99              :       CHARACTER(len=*), PARAMETER                        :: routineN = 'write_cc4s'
     100              :       REAL(dp), PARAMETER :: hf_fraction_tolerance = 1.0e-14_dp
     101              : 
     102              :       CHARACTER(len=8)                                   :: method
     103              :       CHARACTER(len=default_path_length)                 :: filename
     104              :       INTEGER :: frozen, functional, handle, homo, max_iter, method_id, nao, nmo, nri, nstored, &
     105              :          output_unit, poisson_periodic, stage_handle
     106              :       INTEGER(KIND=int_8)                                :: buffer_elements, total_elements
     107              :       INTEGER, DIMENSION(2)                              :: dims2
     108              :       INTEGER, DIMENSION(3)                              :: dims3
     109              :       LOGICAL                                            :: admm_section_present, do_kpoints, &
     110              :                                                             enabled, periodic
     111              :       REAL(dp) :: amp_conv, cutoff_radius, energy_conv, export_start, fermi_energy, max_cutoff, &
     112              :          max_memory_mb, memory_mb, output_buffer_mib, vertex_norm, write_start
     113        24709 :       REAL(dp), ALLOCATABLE                              :: eigenvalues(:)
     114              :       REAL(dp), DIMENSION(5)                             :: elapsed
     115              :       TYPE(cell_type), POINTER                           :: cell
     116              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct
     117              :       TYPE(cp_fm_type)                                   :: fm_active, fm_coeff, fm_fock, &
     118              :                                                             fm_overlap, fm_work
     119              :       TYPE(cp_fm_type), POINTER                          :: mo_coeff
     120              :       TYPE(cp_logger_type), POINTER                      :: logger
     121        24709 :       TYPE(dbcsr_p_type), DIMENSION(:), POINTER          :: matrix_ks, matrix_s
     122              :       TYPE(dbcsr_type)                                   :: coefficient_matrix
     123       172963 :       TYPE(dbt_type)                                     :: coefficients
     124              :       TYPE(dft_control_type), POINTER                    :: dft_control
     125        24709 :       TYPE(hfx_type), DIMENSION(:, :), POINTER           :: x_data
     126        24709 :       TYPE(mo_set_type), DIMENSION(:), POINTER           :: mos
     127              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     128              :       TYPE(scf_control_type), POINTER                    :: scf_control
     129              :       TYPE(section_vals_type), POINTER                   :: admm_section, input, print_section
     130              : 
     131        49418 :       logger => cp_get_default_logger()
     132        24709 :       CALL get_qs_env(qs_env, input=input)
     133        24709 :       print_section => section_vals_get_subs_vals(input, "DFT%PRINT%CC4S")
     134        24709 :       enabled = cp_printkey_is_on(logger%iter_info, print_section)
     135        24709 :       IF (.NOT. enabled) RETURN
     136            6 :       IF (.NOT. qs_env%single_point_run) THEN
     137            0 :          CALL cp_abort(__LOCATION__, "CC4S: export requires a single-point RUN_TYPE ENERGY or ENERGY_FORCE")
     138              :       END IF
     139            6 :       IF (.NOT. BTEST(cp_print_key_should_output(logger%iter_info, print_section), cp_p_file)) RETURN
     140            6 :       CALL section_vals_val_get(print_section, "FILENAME", c_val=filename)
     141            6 :       IF (filename(1:1) == '=' .OR. filename == '__STD_OUT__') THEN
     142              :          CALL cp_abort(__LOCATION__, &
     143            0 :                        "CC4S: FILENAME must be a file prefix, not an exact name (=name) or __STD_OUT__")
     144              :       END IF
     145            6 :       CALL timeset(routineN, handle)
     146            6 :       export_start = m_walltime()
     147            6 :       elapsed = 0.0_dp
     148            6 :       output_unit = cp_logger_get_default_io_unit(logger)
     149              : 
     150            6 :       CALL section_vals_val_get(print_section, "METHOD", i_val=method_id)
     151            6 :       CALL section_vals_val_get(print_section, "N_FROZEN_CORE", i_val=frozen)
     152            6 :       CALL section_vals_val_get(print_section, "MAX_ITER", i_val=max_iter)
     153            6 :       CALL section_vals_val_get(print_section, "ENERGY_CONVERGENCE", r_val=energy_conv)
     154            6 :       CALL section_vals_val_get(print_section, "AMPLITUDES_CONVERGENCE", r_val=amp_conv)
     155            6 :       CALL section_vals_val_get(print_section, "MAX_MEMORY", r_val=max_memory_mb)
     156            6 :       CALL section_vals_val_get(print_section, "OUTPUT_BUFFER_SIZE", r_val=output_buffer_mib)
     157            6 :       IF (.NOT. ieee_is_finite(output_buffer_mib)) THEN
     158            0 :          CPABORT("CC4S: OUTPUT_BUFFER_SIZE must be finite")
     159              :       END IF
     160            6 :       IF (output_buffer_mib < 1.0_dp/elements_per_mib .OR. &
     161              :           output_buffer_mib > REAL(HUGE(1), dp)/elements_per_mib) THEN
     162              :          CALL cp_abort(__LOCATION__, &
     163            0 :                        "CC4S: OUTPUT_BUFFER_SIZE must hold at least one Real64 value and fit the MPI integer count")
     164              :       END IF
     165            6 :       buffer_elements = FLOOR(output_buffer_mib*elements_per_mib, KIND=int_8)
     166            0 :       SELECT CASE (method_id)
     167              :       CASE (1)
     168            0 :          method = 'MP2'
     169              :       CASE (2)
     170            0 :          method = 'CCSD'
     171              :       CASE (3)
     172            6 :          method = 'CCSD_T'
     173              :       CASE DEFAULT
     174            6 :          CPABORT("CC4S: unsupported method")
     175              :       END SELECT
     176            6 :       IF (.NOT. ieee_is_finite(max_memory_mb)) CPABORT("CC4S: MAX_MEMORY must be finite")
     177            6 :       IF (max_iter < 1 .OR. energy_conv <= 0.0_dp .OR. amp_conv <= 0.0_dp .OR. max_memory_mb <= 0.0_dp) THEN
     178              :          CALL cp_abort(__LOCATION__, &
     179            0 :                        "CC4S: iteration, convergence and memory limits must be positive")
     180              :       END IF
     181              :       CALL get_qs_env(qs_env, x_data=x_data, mos=mos, para_env=para_env, dft_control=dft_control, &
     182              :                       cell=cell, do_kpoints=do_kpoints, scf_control=scf_control, &
     183            6 :                       matrix_ks=matrix_ks, matrix_s=matrix_s)
     184            6 :       IF (dft_control%qs_control%cdft) THEN
     185            0 :          CPABORT("CC4S: constrained DFT is unsupported")
     186              :       END IF
     187            6 :       IF (.NOT. converged) THEN
     188            0 :          CPABORT("CC4S: export requires a converged SCF")
     189              :       END IF
     190            6 :       CALL section_vals_val_get(input, "DFT%POISSON%PERIODIC", i_val=poisson_periodic)
     191            6 :       IF (do_kpoints) THEN
     192            0 :          CPABORT("CC4S: k-point sampling is unsupported")
     193              :       END IF
     194           16 :       periodic = ALL(cell%perd == 1) .AND. poisson_periodic == use_perd_xyz
     195           18 :       IF (.NOT. periodic .AND. &
     196              :           (ANY(cell%perd /= 0) .OR. poisson_periodic /= use_perd_none)) THEN
     197              :          CALL cp_abort(__LOCATION__, &
     198            0 :                        "CC4S: CELL and POISSON must both use PERIODIC NONE or both PERIODIC XYZ")
     199              :       END IF
     200            6 :       IF (periodic .AND. dft_control%charge /= 0) THEN
     201            0 :          CPABORT("CC4S: periodic cells must be neutral")
     202              :       END IF
     203            6 :       IF (dft_control%nspins /= 1 .OR. dft_control%uks .OR. dft_control%roks .OR. dft_control%smear) THEN
     204              :          CALL cp_abort(__LOCATION__, &
     205            0 :                        "CC4S: a restricted closed-shell reference without smearing is required")
     206              :       END IF
     207            6 :       admm_section => section_vals_get_subs_vals(input, "DFT%AUXILIARY_DENSITY_MATRIX_METHOD")
     208            6 :       CALL section_vals_get(admm_section, explicit=admm_section_present)
     209            6 :       IF (admm_section_present) THEN
     210            0 :          CPABORT("CC4S: AUXILIARY_DENSITY_MATRIX_METHOD is unsupported")
     211              :       END IF
     212            6 :       IF (dft_control%dft_plus_u .OR. dft_control%sic_method_id /= sic_none) THEN
     213            0 :          CPABORT("CC4S: DFT+U and self-interaction corrections are unsupported")
     214              :       END IF
     215            6 :       CALL section_vals_val_get(input, "DFT%XC%XC_FUNCTIONAL%_SECTION_PARAMETERS_", i_val=functional)
     216            6 :       IF (functional /= xc_none) THEN
     217              :          CALL cp_abort(__LOCATION__, &
     218            0 :                        "CC4S: canonical HF orbitals required; use XC_FUNCTIONAL NONE")
     219              :       END IF
     220            6 :       IF (.NOT. ASSOCIATED(x_data)) THEN
     221            0 :          CPABORT("CC4S: enable HF with RI_FLAVOR MO")
     222              :       END IF
     223            6 :       IF (SIZE(x_data, 1) /= 1) THEN
     224              :          CALL cp_abort(__LOCATION__, &
     225            0 :                        "CC4S: exactly one HF section is allowed")
     226              :       END IF
     227            6 :       IF (ABS(x_data(1, 1)%general_parameter%fraction - 1.0_dp) > hf_fraction_tolerance) THEN
     228            0 :          CPABORT("CC4S: HF FRACTION must be 1")
     229              :       END IF
     230            6 :       IF (.NOT. x_data(1, 1)%do_hfx_ri) THEN
     231            0 :          CPABORT("CC4S: enable HF/RI with RI_FLAVOR MO")
     232              :       END IF
     233            6 :       IF (.NOT. ASSOCIATED(x_data(1, 1)%ri_data)) THEN
     234            0 :          CPABORT("CC4S: RI-HFX data are unavailable")
     235              :       END IF
     236            6 :       IF (x_data(1, 1)%ri_data%flavor /= ri_mo) THEN
     237            0 :          CPABORT("CC4S: HF/RI requires RI_FLAVOR MO")
     238              :       END IF
     239            6 :       cutoff_radius = 0.0_dp
     240            6 :       IF (periodic) THEN
     241            2 :          IF (.NOT. x_data(1, 1)%ri_data%same_op .OR. &
     242              :              x_data(1, 1)%ri_data%hfx_pot%potential_type /= do_potential_truncated) THEN
     243              :             CALL cp_abort(__LOCATION__, &
     244            0 :                           "CC4S: periodic export requires TRUNCATED HF interaction and the same RI metric; use RI_METRIC HFX")
     245              :          END IF
     246            2 :          cutoff_radius = x_data(1, 1)%ri_data%hfx_pot%cutoff_radius
     247              :          max_cutoff = 0.5_dp*MIN(plane_distance(1, 0, 0, cell), &
     248            2 :                                  plane_distance(0, 1, 0, cell), plane_distance(0, 0, 1, cell))
     249            2 :          IF (cutoff_radius <= 0.0_dp .OR. cutoff_radius >= max_cutoff) THEN
     250              :             CALL cp_abort(__LOCATION__, &
     251            0 :                           "CC4S: positive HF CUTOFF_RADIUS must be smaller than half the minimal cell plane spacing")
     252              :          END IF
     253              :       ELSE
     254            4 :          IF (.NOT. x_data(1, 1)%ri_data%same_op .OR. &
     255              :              x_data(1, 1)%ri_data%hfx_pot%potential_type /= do_potential_coulomb) THEN
     256              :             CALL cp_abort(__LOCATION__, &
     257            0 :                           "CC4S: molecular HF interaction and RI metric must both be COULOMB")
     258              :          END IF
     259              :       END IF
     260              : 
     261            6 :       CALL get_mo_set(mos(1), mo_coeff=mo_coeff, homo=homo, nmo=nstored, nao=nao)
     262            6 :       IF (homo < 1 .OR. homo > nstored) THEN
     263            0 :          CPABORT("CC4S: no occupied orbitals")
     264              :       END IF
     265            6 :       IF (.NOT. has_uniform_occupation(mos(1), first_mo=1, last_mo=homo, occupation=2.0_dp) .OR. &
     266              :           .NOT. has_uniform_occupation(mos(1), first_mo=homo + 1, last_mo=nstored, occupation=0.0_dp)) THEN
     267              :          CALL cp_abort(__LOCATION__, &
     268            0 :                        "CC4S: occupations must be 2 below the HOMO and 0 above")
     269              :       END IF
     270            6 :       IF (frozen < 0 .OR. frozen >= homo) THEN
     271              :          CALL cp_abort(__LOCATION__, &
     272            0 :                        "CC4S: N_FROZEN_CORE must leave at least one occupied orbital")
     273              :       END IF
     274            6 :       CALL dbt_get_info(x_data(1, 1)%ri_data%t_3c_int_ctr_1(1, 1), nfull_total=dims3)
     275            6 :       nri = dims3(2)
     276            6 :       CPASSERT(dims3(1) == nao .AND. dims3(3) == nao .AND. nri > 0)
     277              :       ! Check quadratic storage before canonicalization; the full AO vertex is never gathered.
     278           18 :       IF (limited_product([INT(nao, int_8), INT(nao, int_8)], backend_data_limit) > backend_data_limit) THEN
     279            0 :          CPABORT("CC4S: canonical matrices exceed backend count limits")
     280              :       END IF
     281           18 :       IF (limited_product([INT(nri, int_8), INT(nri, int_8)], backend_data_limit) > backend_data_limit) THEN
     282            0 :          CPABORT("CC4S: RI metric exceeds backend count limits")
     283              :       END IF
     284              :       ! Canonicalization precedes batch selection; reject an insufficient budget before allocation.
     285            6 :       memory_mb = REAL(dp_size, dp)*6.0_dp*REAL(nao, dp)**2/1.0e6_dp
     286            6 :       IF (memory_mb > max_memory_mb) THEN
     287            0 :          CPABORT("CC4S: canonicalization exceeds MAX_MEMORY [MB]")
     288              :       END IF
     289            6 :       elapsed = 0.0_dp
     290              : 
     291              :       ! Always diagonalize private copies, including for OT or a complete stored MO set.
     292              :       ! Drop overlap null modes using the same cutoff as SCF; do not export placeholder energies.
     293            6 :       NULLIFY (fm_struct)
     294              :       CALL cp_fm_struct_create(fm_struct, nrow_global=nao, ncol_global=nao, &
     295            6 :                                template_fmstruct=mo_coeff%matrix_struct)
     296            6 :       CALL cp_fm_create(fm_fock, fm_struct)
     297            6 :       CALL cp_fm_create(fm_overlap, fm_struct)
     298            6 :       CALL cp_fm_create(fm_coeff, fm_struct)
     299            6 :       CALL cp_fm_create(fm_work, fm_struct)
     300            6 :       CALL cp_fm_struct_release(fm_struct)
     301            6 :       CALL copy_dbcsr_to_fm(matrix_ks(1)%matrix, fm_fock)
     302            6 :       CALL copy_dbcsr_to_fm(matrix_s(1)%matrix, fm_overlap)
     303           18 :       ALLOCATE (eigenvalues(nao))
     304              :       CALL cp_fm_geeig_canon(fm_fock, fm_overlap, fm_coeff, eigenvalues, fm_work, &
     305            6 :                              scf_control%eps_eigval, nmo_retained=nmo)
     306            6 :       IF (nmo <= homo) THEN
     307              :          CALL cp_abort(__LOCATION__, &
     308            0 :                        "CC4S: no virtual orbitals after removing overlap null modes")
     309              :       END IF
     310            6 :       IF (eigenvalues(homo + 1) <= eigenvalues(homo)) THEN
     311              :          CALL cp_abort(__LOCATION__, &
     312            0 :                        "CC4S: a positive HOMO-LUMO gap is required")
     313              :       END IF
     314            6 :       fermi_energy = 0.5_dp*(eigenvalues(homo) + eigenvalues(homo + 1))
     315            6 :       nmo = nmo - frozen
     316            6 :       total_elements = vertex_size(nri, nmo)
     317            6 :       buffer_elements = MIN(buffer_elements, total_elements, output_count_limit)
     318              :       ! The initial coefficient copy can have a different block partition. Bound its
     319              :       ! metadata by the number of elements before entering DBCSR/DBT conversion.
     320           18 :       IF (limited_product([INT(nao, int_8), INT(nmo, int_8)], backend_block_limit) > backend_block_limit) THEN
     321            0 :          CPABORT("CC4S: coefficients exceed backend count limits")
     322              :       END IF
     323            6 :       IF (output_unit > 0) THEN
     324            3 :          WRITE (output_unit, '(/,T3,A)') "CC4S| Reference export"
     325            3 :          WRITE (output_unit, '(T3,A,T60,A21)') "CC4S| Reference:", "Restricted RI-HF"
     326            3 :          IF (periodic) THEN
     327            1 :             WRITE (output_unit, '(T3,A,T60,A21)') "CC4S| Boundary conditions:", "Periodic Gamma"
     328            1 :             WRITE (output_unit, '(T3,A,T60,A21)') "CC4S| Coulomb interaction:", "TRUNCATED"
     329            1 :             WRITE (output_unit, '(T3,A,T69,F12.6)') "CC4S| Coulomb cutoff radius [bohr]:", cutoff_radius
     330              :          ELSE
     331            2 :             WRITE (output_unit, '(T3,A,T60,A21)') "CC4S| Boundary conditions:", "Isolated"
     332            2 :             WRITE (output_unit, '(T3,A,T60,A21)') "CC4S| Coulomb interaction:", "COULOMB"
     333              :          END IF
     334            3 :          WRITE (output_unit, '(T3,A)') "CC4S|"
     335            3 :          WRITE (output_unit, '(T3,A,T69,I12)') "CC4S| Number of auxiliary basis functions:", nri
     336            3 :          WRITE (output_unit, '(T3,A,T69,I12)') "CC4S| Number of exported orbitals:", nmo
     337            3 :          WRITE (output_unit, '(T3,A,T69,I12)') "CC4S| Number of exported occupied orbitals:", homo - frozen
     338            3 :          WRITE (output_unit, '(T3,A,T69,I12)') "CC4S| Number of exported virtual orbitals:", nmo - homo + frozen
     339            3 :          WRITE (output_unit, '(T3,A,T69,I12)') "CC4S| Number of frozen occupied orbitals:", frozen
     340              :          WRITE (output_unit, '(T3,A,T63,F18.6)') &
     341            3 :             "CC4S| Expected Coulomb vertex size [MiB]:", REAL(total_elements, dp)/elements_per_mib
     342            3 :          WRITE (output_unit, '(T3,A)') "CC4S|"
     343              :          WRITE (output_unit, '(T3,A,T69,F12.6)') &
     344            3 :             "CC4S| Actual output buffer size per MPI rank [MiB]:", REAL(buffer_elements, dp)/elements_per_mib
     345              :       END IF
     346            6 :       CALL cp_fm_struct_create(fm_struct, nrow_global=nao, ncol_global=nmo, template_fmstruct=mo_coeff%matrix_struct)
     347            6 :       CALL cp_fm_create(fm_active, fm_struct, name="CC4S active orbitals")
     348            6 :       CALL cp_fm_struct_release(fm_struct)
     349            6 :       CALL cp_fm_to_fm(fm_coeff, fm_active, ncol=nmo, source_start=frozen + 1)
     350            6 :       CALL cp_fm_to_dbcsr_row_template(coefficient_matrix, fm_active, matrix_s(1)%matrix)
     351            6 :       CALL dbt_create(coefficient_matrix, coefficients, name="CC4S coefficients")
     352            6 :       CALL dbt_copy_matrix_to_tensor(coefficient_matrix, coefficients)
     353            6 :       CALL dbcsr_release(coefficient_matrix)
     354            6 :       CALL cp_fm_release(fm_active)
     355            6 :       CALL cp_fm_release(fm_fock)
     356            6 :       CALL cp_fm_release(fm_overlap)
     357            6 :       CALL cp_fm_release(fm_coeff)
     358            6 :       CALL cp_fm_release(fm_work)
     359              : 
     360            6 :       CALL dbt_get_info(x_data(1, 1)%ri_data%t_2c_int(1, 1), nfull_total=dims2)
     361           18 :       CPASSERT(ALL(dims2 == nri))
     362              :       ! Transform and export one orbital range at a time; never retain the complete vertex.
     363              :       CALL export_vertex_batches(x_data(1, 1)%ri_data%t_3c_int_ctr_1(1, 1), &
     364              :                                  x_data(1, 1)%ri_data%t_2c_int(1, 1), coefficients, para_env, &
     365              :                                  logger, print_section, nmo, max_memory_mb, buffer_elements, &
     366              :                                  periodic, cutoff_radius, cell%hmat, output_unit, vertex_norm, elapsed, &
     367            6 :                                  homo - frozen, eigenvalues(frozen + 1:frozen + nmo), fermi_energy)
     368            6 :       CALL dbt_destroy(coefficients)
     369            6 :       CALL timeset("cc4s_write_files", stage_handle)
     370            6 :       write_start = m_walltime()
     371            6 :       IF (para_env%is_source()) THEN
     372            3 :          CALL write_cc4s_in(logger, print_section, method, homo - frozen, max_iter, energy_conv, amp_conv)
     373              :       END IF
     374            6 :       elapsed(5) = elapsed(5) + m_walltime() - write_start
     375            6 :       CALL timestop(stage_handle)
     376            6 :       IF (para_env%is_source()) THEN
     377            3 :          IF (output_unit > 0) THEN
     378            3 :             WRITE (output_unit, '(/,T3,A)') "CC4S| Export completed"
     379            3 :             WRITE (output_unit, '(T3,A,T56,ES25.16)') "CC4S| Vertex norm squared [Ha]:", vertex_norm
     380              :             WRITE (output_unit, '(T3,A,T56,ES25.16)') &
     381           56 :                "CC4S| Sum of exported orbital energies [Ha]:", SUM(eigenvalues(frozen + 1:frozen + nmo))
     382            3 :             WRITE (output_unit, '(T3,A)') "CC4S|"
     383            3 :             filename = cp_print_key_generate_filename(logger, print_section, 'EigenEnergies', '.yaml', .FALSE.)
     384            3 :             WRITE (output_unit, '(T3,A,T41,A)') "CC4S| Orbital energies metadata:", &
     385            6 :                ADJUSTR(filename(:MAX(40, LEN_TRIM(filename))))
     386            3 :             filename = cp_print_key_generate_filename(logger, print_section, 'EigenEnergies', '.elements', .FALSE.)
     387            3 :             WRITE (output_unit, '(T3,A,T41,A)') "CC4S| Orbital energies data:", &
     388            6 :                ADJUSTR(filename(:MAX(40, LEN_TRIM(filename))))
     389            3 :             filename = cp_print_key_generate_filename(logger, print_section, 'CoulombVertex', '.yaml', .FALSE.)
     390            3 :             WRITE (output_unit, '(T3,A,T41,A)') "CC4S| Coulomb vertex metadata:", &
     391            6 :                ADJUSTR(filename(:MAX(40, LEN_TRIM(filename))))
     392            3 :             filename = cp_print_key_generate_filename(logger, print_section, 'CoulombVertex', '.elements', .FALSE.)
     393            3 :             WRITE (output_unit, '(T3,A,T41,A)') "CC4S| Coulomb vertex file:", &
     394            6 :                ADJUSTR(filename(:MAX(40, LEN_TRIM(filename))))
     395            3 :             CALL print_vertex_file_size(output_unit, filename)
     396              :          END IF
     397              :       END IF
     398            6 :       DEALLOCATE (eigenvalues)
     399            6 :       CALL para_env%sync()
     400            6 :       elapsed(1) = m_walltime() - export_start
     401              :       ! Report the longest rank-local elapsed times; only the source rank writes files.
     402            6 :       CALL para_env%max(elapsed)
     403            6 :       IF (output_unit > 0) THEN
     404            3 :          WRITE (output_unit, '(T3,A,T69,F12.3)') "CC4S| Total export time [s]:", elapsed(1)
     405            3 :          WRITE (output_unit, '(T3,A,T69,F12.3)') "CC4S| Orbital transformation time [s]:", elapsed(2)
     406            3 :          WRITE (output_unit, '(T3,A,T69,F12.3)') "CC4S| Metric contraction time [s]:", elapsed(3)
     407            3 :          WRITE (output_unit, '(T3,A,T69,F12.3)') "CC4S| Output gathering time [s]:", elapsed(4)
     408            3 :          WRITE (output_unit, '(T3,A,T69,F12.3)') "CC4S| File writing time [s]:", elapsed(5)
     409              :       END IF
     410            6 :       IF (para_env%is_source()) THEN
     411            3 :          IF (output_unit > 0) THEN
     412            3 :             WRITE (output_unit, '(/,T3,A)') "CC4S| Generated CC4S input"
     413            3 :             filename = cp_print_key_generate_filename(logger, print_section, extension='.in', my_local=.FALSE.)
     414            3 :             WRITE (output_unit, '(T3,A,T41,A)') "CC4S| Generated input file:", &
     415            6 :                ADJUSTR(filename(:MAX(40, LEN_TRIM(filename))))
     416            3 :             IF (method == 'CCSD_T') THEN
     417            3 :                WRITE (output_unit, '(T3,A,T73,A8)') "CC4S| Requested method:", 'CCSD(T)'
     418              :             ELSE
     419            0 :                WRITE (output_unit, '(T3,A,T73,A8)') "CC4S| Requested method:", TRIM(method)
     420              :             END IF
     421            3 :             IF (method /= 'MP2') THEN
     422            3 :                WRITE (output_unit, '(T3,A,T69,I12)') "CC4S| Maximum CCSD iterations:", max_iter
     423            3 :                WRITE (output_unit, '(T3,A,T73,ES8.1)') "CC4S| CCSD energy convergence [Ha]:", energy_conv
     424            3 :                WRITE (output_unit, '(T3,A,T73,ES8.1)') "CC4S| CCSD amplitudes convergence:", amp_conv
     425              :             END IF
     426              :          END IF
     427              :       END IF
     428            6 :       CALL timestop(handle)
     429        74187 :    END SUBROUTINE write_cc4s
     430              : 
     431              : ! **************************************************************************************************
     432              : !> \brief Report the actual size of the closed binary vertex file, in MiB, on the writing rank.
     433              : !> \param output_unit Log output unit
     434              : !> \param filename Export filename generated by the print key
     435              : ! **************************************************************************************************
     436            3 :    SUBROUTINE print_vertex_file_size(output_unit, filename)
     437              :       INTEGER, INTENT(IN)                                :: output_unit
     438              :       CHARACTER(len=*), INTENT(IN)                       :: filename
     439              : 
     440              :       INTEGER                                            :: io_status
     441              :       INTEGER(int_8)                                     :: file_size
     442              :       REAL(dp)                                           :: size_mib
     443              : 
     444            3 :       file_size = -1_int_8
     445            3 :       size_mib = -1.0_dp
     446            3 :       INQUIRE (FILE=TRIM(filename), SIZE=file_size, IOSTAT=io_status)
     447            3 :       IF (io_status == 0) THEN
     448            3 :          IF (file_size >= 0_int_8) THEN
     449            3 :             size_mib = REAL(file_size, dp)*REAL(file_storage_size, dp)/(8.0_dp*1024.0_dp**2)
     450              :          END IF
     451              :       END IF
     452            3 :       IF (size_mib >= 0.0_dp) THEN
     453            3 :          WRITE (output_unit, '(T3,A,T63,F18.6)') "CC4S| Written Coulomb vertex size [MiB]:", size_mib
     454              :       ELSE
     455            0 :          WRITE (output_unit, '(T3,A,T63,A18)') "CC4S| Written Coulomb vertex size [MiB]:", "unavailable"
     456              :       END IF
     457            3 :    END SUBROUTINE print_vertex_file_size
     458              : 
     459              : ! **************************************************************************************************
     460              : !> \brief Transform and stream orbital batches, using private auxiliary slices of the SCF triangle.
     461              : !> \param integrals Live SCF tensor, ordered (AO, RI, AO)
     462              : !> \param metric Live SCF inverse square root of the RI metric
     463              : !> \param coefficients Distributed active canonical orbitals
     464              : !> \param para_env MPI parallel environment
     465              : !> \param logger Logger for output selection and naming
     466              : !> \param print_section CC4S print key
     467              : !> \param nmo Number of active orbitals
     468              : !> \param max_memory_mb Estimated additional workspace limit per rank, in decimal MB
     469              : !> \param buffer_elements Maximum number of Real64 values in each output piece
     470              : !> \param periodic Whether the cell is three-dimensionally periodic at Gamma
     471              : !> \param cutoff_radius Truncated Coulomb cutoff in bohr
     472              : !> \param hmat Cell vectors in bohr
     473              : !> \param output_unit Log output unit
     474              : !> \param vertex_norm Squared vertex norm on the writing rank
     475              : !> \param elapsed Accumulated wall times: total, transformation, metric, gathering, writing
     476              : !> \param homo Number of active occupied orbitals
     477              : !> \param eigenvalues Active canonical orbital energies
     478              : !> \param fermi_energy Energy inside the HOMO-LUMO gap
     479              : ! **************************************************************************************************
     480            6 :    SUBROUTINE export_vertex_batches(integrals, metric, coefficients, para_env, logger, print_section, &
     481              :                                     nmo, max_memory_mb, buffer_elements, periodic, cutoff_radius, hmat, &
     482            6 :                                     output_unit, vertex_norm, elapsed, homo, eigenvalues, fermi_energy)
     483              :       TYPE(dbt_type), INTENT(INOUT)                      :: integrals, metric, coefficients
     484              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     485              :       TYPE(cp_logger_type), POINTER                      :: logger
     486              :       TYPE(section_vals_type), POINTER                   :: print_section
     487              :       INTEGER, INTENT(IN)                                :: nmo
     488              :       REAL(dp), INTENT(IN)                               :: max_memory_mb
     489              :       INTEGER(KIND=int_8), INTENT(IN)                    :: buffer_elements
     490              :       LOGICAL, INTENT(IN)                                :: periodic
     491              :       REAL(dp), INTENT(IN)                               :: cutoff_radius, hmat(3, 3)
     492              :       INTEGER, INTENT(IN)                                :: output_unit
     493              :       REAL(dp), INTENT(OUT)                              :: vertex_norm
     494              :       REAL(dp), INTENT(INOUT)                            :: elapsed(5)
     495              :       INTEGER, INTENT(IN)                                :: homo
     496              :       REAL(dp), INTENT(IN)                               :: eigenvalues(nmo), fermi_energy
     497              : 
     498              :       INTEGER :: block_width, bounds2(2, 2), bounds3(2, 3), handle, im, ir, iunit, mb, &
     499              :          metric_blocks(2), mfirst, mlast, nao, nri, pdims(3), pdims2(2), rb, rfirst, rlast
     500            6 :       INTEGER, ALLOCATABLE                               :: ao(:), d1(:), d2(:), d3(:), mo(:), ri(:)
     501              :       REAL(dp)                                           :: memory_mb, pressure, start, trial_m, &
     502              :                                                             trial_r
     503            6 :       REAL(dp), ALLOCATABLE                              :: values(:)
     504           30 :       TYPE(dbt_pgrid_type)                               :: grid, grid2
     505          150 :       TYPE(dbt_type)                                     :: ao_triangle, coeff, coeff_batch, gamma, &
     506          114 :                                                             half, metric_copy, triangle
     507              : 
     508           30 :       ALLOCATE (ao(dbt_nblks_total(integrals, 1)), ri(dbt_nblks_total(integrals, 2)))
     509            6 :       CALL dbt_get_info(integrals, blk_size_1=ao, blk_size_2=ri)
     510           28 :       nao = SUM(ao)
     511           96 :       nri = SUM(ri)
     512              :       ! Smaller orbital blocks may be necessary for one output stripe to fit the backend.
     513            6 :       block_width = 16
     514              :       DO WHILE (limited_product([INT(nri, int_8), INT(nmo, int_8), INT(block_width, int_8)], &
     515           24 :                                 backend_data_limit) > backend_data_limit .AND. block_width > 1)
     516            0 :          block_width = block_width/2
     517              :       END DO
     518           18 :       ALLOCATE (mo(1 + (nmo - 1)/block_width))
     519           16 :       mo = block_width
     520            6 :       mo(SIZE(mo)) = nmo - (SIZE(mo) - 1)*block_width
     521            6 :       CALL dbt_get_info(metric, nblks_total=metric_blocks)
     522              :       ! Reblocking can refine both original and requested auxiliary partitions.
     523           24 :       IF (limited_product([INT(para_env%num_pe, int_8), INT(metric_blocks(1), int_8) + SIZE(ri, KIND=int_8), &
     524              :                            INT(metric_blocks(2), int_8) + SIZE(ri, KIND=int_8)], &
     525            6 :                           backend_block_limit) > backend_block_limit) THEN
     526            0 :          CPABORT("CC4S: RI metric metadata exceeds backend limits")
     527              :       END IF
     528            6 :       mb = SIZE(mo)
     529            6 :       rb = SIZE(ri)
     530              :       ! Bound the busiest rank without assuming balanced distribution. Include private operands,
     531              :       ! simultaneously live results, and conservative redistribution/communication copies.
     532            6 :       pressure = batch_pressure(ao, ri, mo, mb, rb, para_env%num_pe, buffer_elements, max_memory_mb)
     533           12 :       DO WHILE (pressure > 1.0_dp .AND. (mb > 1 .OR. rb > 1))
     534            6 :          trial_m = HUGE(0.0_dp)
     535            6 :          trial_r = HUGE(0.0_dp)
     536            6 :          IF (mb > 1) trial_m = batch_pressure(ao, ri, mo, mb/2 + MODULO(mb, 2), rb, para_env%num_pe, &
     537            4 :                                               buffer_elements, max_memory_mb)
     538            6 :          IF (rb > 1) trial_r = batch_pressure(ao, ri, mo, mb, rb/2 + MODULO(rb, 2), para_env%num_pe, &
     539            6 :                                               buffer_elements, max_memory_mb)
     540            6 :          IF (trial_m <= trial_r) THEN
     541            2 :             mb = mb/2 + MODULO(mb, 2)
     542              :          ELSE
     543            4 :             rb = rb/2 + MODULO(rb, 2)
     544              :          END IF
     545           12 :          pressure = MIN(trial_m, trial_r)
     546              :       END DO
     547            6 :       memory_mb = batch_workspace(nao, nri, nmo, mo, ri, mb, rb, buffer_elements)
     548            6 :       IF (.NOT. batch_counts_fit(ao, ri, mo, mb, rb, para_env%num_pe)) THEN
     549            0 :          CPABORT("CC4S: minimum batch exceeds backend count limits")
     550              :       END IF
     551            6 :       IF (output_unit > 0) THEN
     552              :          WRITE (output_unit, '(T3,A,T69,F12.3)') &
     553            3 :             "CC4S| Estimated export workspace per MPI rank [MB]:", memory_mb
     554              :          WRITE (output_unit, '(T3,A,T69,F12.3)') &
     555            3 :             "CC4S| Export workspace budget per MPI rank [MB]:", max_memory_mb
     556              :       END IF
     557            6 :       IF (memory_mb > max_memory_mb) THEN
     558              :          CALL cp_abort(__LOCATION__, &
     559            0 :                        "CC4S: minimum transformation batch exceeds MAX_MEMORY; increase the limit or reduce OUTPUT_BUFFER_SIZE")
     560              :       END IF
     561            6 :       IF (output_unit > 0) THEN
     562            3 :          WRITE (output_unit, '(T3,A,T69,I12)') "CC4S| Maximum orbitals per transformation batch:", batch_width(mo, mb)
     563            3 :          WRITE (output_unit, '(T3,A,T69,I12)') "CC4S| Maximum auxiliary functions per batch:", batch_width(ri, rb)
     564            3 :          WRITE (output_unit, '(T3,A,T69,I12)') "CC4S| Number of orbital batches:", 1 + (SIZE(mo) - 1)/mb
     565            3 :          WRITE (output_unit, '(T3,A,T69,I12)') "CC4S| Number of auxiliary batches:", 1 + (SIZE(ri) - 1)/rb
     566              :       END IF
     567            6 :       pdims = 0
     568            6 :       pdims2 = 0
     569           24 :       CALL dbt_pgrid_create(para_env, pdims, grid, tensor_dims=[SIZE(mo), SIZE(ri), SIZE(ao)])
     570           18 :       CALL dbt_pgrid_create(para_env, pdims2, grid2, tensor_dims=[SIZE(ao), SIZE(mo)])
     571            6 :       CALL create_2c_tensor(coeff, d1, d2, grid2, ao, mo, name="CC4S active coefficients")
     572            6 :       DEALLOCATE (d1, d2)
     573            6 :       CALL dbt_copy(coefficients, coeff)
     574              :       ! The metric and integrals can have different auxiliary block partitions.
     575            6 :       CALL create_2c_tensor(metric_copy, d1, d2, grid2, ri, ri, name="CC4S inverse metric")
     576            6 :       DEALLOCATE (d1, d2)
     577            6 :       CALL dbt_copy(metric, metric_copy)
     578           18 :       ALLOCATE (values(buffer_elements))
     579            6 :       vertex_norm = 0.0_dp
     580            6 :       iunit = -1
     581            6 :       start = m_walltime()
     582            6 :       IF (para_env%is_source()) THEN
     583            3 :          CALL write_eigen_energies_files(logger, print_section, nmo, homo, eigenvalues, fermi_energy)
     584            3 :          CALL open_coulomb_vertex_file(logger, print_section, nri, nmo, periodic, cutoff_radius, hmat, iunit)
     585              :       END IF
     586            6 :       elapsed(5) = elapsed(5) + m_walltime() - start
     587           14 :       DO im = 1, SIZE(mo), mb
     588            8 :          CALL dbt_create(coeff, coeff_batch, name="CC4S coefficient batch")
     589              :          CALL create_3c_tensor(gamma, d1, d2, d3, grid, ri, mo, mo, [1, 2], [3], &
     590            8 :                                name="CC4S Coulomb vertex batch")
     591            8 :          DEALLOCATE (d1, d2, d3)
     592           10 :          mfirst = SUM(mo(:im - 1)) + 1
     593           20 :          mlast = SUM(mo(:im + MIN(mb - 1, SIZE(mo) - im)))
     594           24 :          bounds2(:, 1) = [1, nao]
     595           24 :          bounds2(:, 2) = [mfirst, mlast]
     596            8 :          CALL dbt_copy(coeff, coeff_batch, bounds=bounds2)
     597            8 :          DO ir = 1, SIZE(ri), rb
     598          164 :             rfirst = SUM(ri(:ir - 1)) + 1
     599          296 :             rlast = SUM(ri(:ir + MIN(rb - 1, SIZE(ri) - ir)))
     600           20 :             start = m_walltime()
     601           20 :             CALL timeset("cc4s_transform", handle)
     602           60 :             bounds3(:, 1) = [1, nao]
     603           60 :             bounds3(:, 2) = [rfirst, rlast]
     604           60 :             bounds3(:, 3) = [1, nao]
     605           20 :             CALL dbt_create(integrals, ao_triangle)
     606              :             CALL create_3c_tensor(half, d1, d2, d3, grid, mo, ri, ao, [1], [2, 3], &
     607           20 :                                   name="CC4S half transform")
     608           20 :             DEALLOCATE (d1, d2, d3)
     609              :             ! Copy only this auxiliary slice. Contractions never receive live SCF tensors.
     610           20 :             CALL dbt_copy(integrals, ao_triangle, bounds=bounds3)
     611              :             CALL dbt_contract(1.0_dp, coeff_batch, ao_triangle, 0.0_dp, half, &
     612              :                               contract_1=[1], notcontract_1=[2], contract_2=[1], notcontract_2=[2, 3], &
     613           20 :                               map_1=[1], map_2=[2, 3], filter_eps=0.0_dp)
     614              :             ! Restore the weighted AO triangle before transforming the second orbital index.
     615              :             ! Sum, not average: diagonal atom blocks carry weight 1/2.
     616              :             CALL dbt_contract(1.0_dp, coeff_batch, ao_triangle, 1.0_dp, half, &
     617              :                               contract_1=[1], notcontract_1=[2], contract_2=[3], notcontract_2=[2, 1], &
     618           20 :                               map_1=[1], map_2=[2, 3], filter_eps=0.0_dp)
     619           20 :             CALL dbt_destroy(ao_triangle)
     620              :             CALL create_3c_tensor(triangle, d1, d2, d3, grid, ri, mo, mo, [1, 2], [3], &
     621           20 :                                   name="CC4S MO batch")
     622           20 :             DEALLOCATE (d1, d2, d3)
     623              :             CALL dbt_contract(1.0_dp, half, coeff, 0.0_dp, triangle, &
     624              :                               contract_1=[3], notcontract_1=[1, 2], contract_2=[1], notcontract_2=[2], &
     625           20 :                               map_1=[3, 1], map_2=[2], filter_eps=0.0_dp)
     626           20 :             CALL dbt_destroy(half)
     627           20 :             CALL timestop(handle)
     628           20 :             elapsed(2) = elapsed(2) + m_walltime() - start
     629           20 :             start = m_walltime()
     630           20 :             CALL timeset("cc4s_metric", handle)
     631              :             CALL dbt_contract(1.0_dp, metric_copy, triangle, 1.0_dp, gamma, &
     632              :                               contract_1=[1], notcontract_1=[2], contract_2=[1], notcontract_2=[2, 3], &
     633           20 :                               map_1=[1], map_2=[2, 3], filter_eps=0.0_dp)
     634           20 :             CALL dbt_destroy(triangle)
     635           20 :             CALL timestop(handle)
     636           60 :             elapsed(3) = elapsed(3) + m_walltime() - start
     637              :          END DO
     638            8 :          CALL write_coulomb_vertex_batch(para_env, nri, nmo, gamma, iunit, mfirst, mlast, values, vertex_norm, elapsed)
     639            8 :          CALL dbt_destroy(gamma)
     640           14 :          CALL dbt_destroy(coeff_batch)
     641              :       END DO
     642            6 :       start = m_walltime()
     643            6 :       IF (para_env%is_source()) CALL cp_print_key_finished_output(iunit, logger, print_section)
     644            6 :       elapsed(5) = elapsed(5) + m_walltime() - start
     645            6 :       DEALLOCATE (values)
     646            6 :       CALL dbt_destroy(metric_copy)
     647            6 :       CALL dbt_destroy(coeff)
     648            6 :       CALL dbt_pgrid_destroy(grid)
     649            6 :       CALL dbt_pgrid_destroy(grid2)
     650            6 :       DEALLOCATE (ao, ri, mo)
     651            6 :    END SUBROUTINE export_vertex_batches
     652              : 
     653              : ! **************************************************************************************************
     654              : !> \brief Positive product, saturated at limit+1, without overflowing intermediate products.
     655              : !> \param factors Nonnegative factors
     656              : !> \param limit Maximum supported value, strictly below HUGE(int_8)
     657              : !> \return Product or limit+1 if the limit would be exceeded
     658              : ! **************************************************************************************************
     659          234 :    PURE FUNCTION limited_product(factors, limit) RESULT(value)
     660              :       INTEGER(int_8), INTENT(IN)                         :: factors(:), limit
     661              :       INTEGER(int_8)                                     :: value
     662              : 
     663              :       INTEGER                                            :: i
     664              : 
     665          234 :       value = 1_int_8
     666         1116 :       DO i = 1, SIZE(factors)
     667         1116 :          IF (factors(i) == 0_int_8) THEN
     668          234 :             value = 0_int_8
     669              :             RETURN
     670              :          END IF
     671              :       END DO
     672         1116 :       DO i = 1, SIZE(factors)
     673          882 :          IF (value > limit/factors(i)) THEN
     674            0 :             value = limit + 1_int_8
     675              :             RETURN
     676              :          END IF
     677         1116 :          value = value*factors(i)
     678              :       END DO
     679              :    END FUNCTION limited_product
     680              : 
     681              : ! **************************************************************************************************
     682              : !> \brief Validate the complete stream extent before constructing offsets or opening files.
     683              : !> \param nri Number of auxiliary functions
     684              : !> \param nmo Number of active orbitals
     685              : !> \return Number of exported Real64 elements
     686              : ! **************************************************************************************************
     687            6 :    FUNCTION vertex_size(nri, nmo) RESULT(elements)
     688              :       INTEGER, INTENT(IN)                                :: nri, nmo
     689              :       INTEGER(int_8)                                     :: elements
     690              : 
     691              :       INTEGER(int_8), PARAMETER :: file_limit = HUGE(0_int_8)/dp_size
     692              : 
     693            6 :       CPASSERT(nri > 0 .AND. nmo > 0)
     694           24 :       elements = limited_product([INT(nri, int_8), INT(nmo, int_8), INT(nmo, int_8)], file_limit)
     695            6 :       IF (elements > file_limit) CPABORT("CC4S: vertex exceeds stream position range")
     696            6 :    END FUNCTION vertex_size
     697              : 
     698              : ! **************************************************************************************************
     699              : !> \brief Intersect a block segment with an output piece before narrowing to local indices.
     700              : !> \param start Zero-based global offset of the block segment
     701              : !> \param length Number of values in the block segment
     702              : !> \param offset Zero-based global offset of the output piece
     703              : !> \param count Number of values in the output piece
     704              : !> \param lo First overlapping block index, or 1 for an empty intersection
     705              : !> \param hi Last overlapping block index, or 0 for an empty intersection
     706              : ! **************************************************************************************************
     707     15432183 :    PURE SUBROUTINE segment_overlap(start, length, offset, count, lo, hi)
     708              :       INTEGER(int_8), INTENT(IN)                         :: start
     709              :       INTEGER, INTENT(IN)                                :: length
     710              :       INTEGER(int_8), INTENT(IN)                         :: offset, count
     711              :       INTEGER, INTENT(OUT)                               :: lo, hi
     712              : 
     713              :       INTEGER(int_8)                                     :: first, last
     714              : 
     715     15432183 :       first = MAX(start, offset)
     716     15432183 :       last = MIN(start + INT(length, int_8), offset + count)
     717     15432183 :       lo = 1
     718     15432183 :       hi = 0
     719     15432183 :       IF (first >= last) RETURN
     720        14863 :       lo = INT(first - start) + 1
     721        14863 :       hi = INT(last - start)
     722              :    END SUBROUTINE segment_overlap
     723              : 
     724              : ! **************************************************************************************************
     725              : !> \brief Bound backend data and metadata independently of the user memory budget.
     726              : !> \param ao AO block sizes
     727              : !> \param ri Auxiliary block sizes
     728              : !> \param mo MO block sizes
     729              : !> \param mb MO blocks in a batch
     730              : !> \param rb Auxiliary blocks in a batch
     731              : !> \param nranks Maximum replication multiplicity
     732              : !> \param pressure Maximum ratio of a count bound to its supported limit
     733              : !> \return Whether every intermediate is safely representable, even on one rank
     734              : ! **************************************************************************************************
     735           22 :    FUNCTION batch_counts_fit(ao, ri, mo, mb, rb, nranks, pressure) RESULT(fits)
     736              :       INTEGER, INTENT(IN)                                :: ao(:), ri(:), mo(:), mb, rb, nranks
     737              :       REAL(dp), INTENT(OUT), OPTIONAL                    :: pressure
     738              :       LOGICAL                                            :: fits
     739              : 
     740              :       INTEGER(int_8), PARAMETER :: product_limit = HUGE(0_int_8)/dp_size
     741              : 
     742              :       INTEGER(int_8)                                     :: a, b, block_count, data_count, m, na, &
     743              :                                                             nb, nm, nq, nr, q, r
     744              : 
     745          116 :       a = SUM(INT(ao, int_8))
     746          412 :       r = SUM(INT(ri, int_8))
     747           62 :       m = SUM(INT(mo, int_8))
     748           22 :       b = INT(batch_width(mo, mb), int_8)
     749           22 :       q = INT(batch_width(ri, rb), int_8)
     750           22 :       na = SIZE(ao, KIND=int_8)
     751           22 :       nr = SIZE(ri, KIND=int_8)
     752           22 :       nm = SIZE(mo, KIND=int_8)
     753           22 :       nb = INT(mb, int_8)
     754           22 :       nq = INT(rb, int_8)
     755              :       data_count = MAX(limited_product([a, a, q], product_limit), &
     756              :                        limited_product([b, q, a], product_limit), &
     757              :                        limited_product([q, m, b], product_limit), &
     758          286 :                        limited_product([r, m, b], product_limit))
     759              :       ! Allow refinement in three dimensions and replicated partial-result metadata
     760              :       ! received from every rank. Payload buffers in TAS replication are per peer.
     761              :       block_count = MAX(limited_product([8_int_8, INT(nranks, int_8), na, nm], product_limit), &
     762              :                         limited_product([8_int_8, INT(nranks, int_8), na, na, nq], product_limit), &
     763              :                         limited_product([8_int_8, INT(nranks, int_8), nb, nq, na], product_limit), &
     764              :                         limited_product([8_int_8, INT(nranks, int_8), nq, nm, nb], product_limit), &
     765          550 :                         limited_product([8_int_8, INT(nranks, int_8), nr, nm, nb], product_limit))
     766           22 :       fits = data_count <= backend_data_limit .AND. block_count <= backend_block_limit
     767           22 :       IF (PRESENT(pressure)) pressure = MAX(REAL(data_count, dp)/REAL(backend_data_limit, dp), &
     768           16 :                                             REAL(block_count, dp)/REAL(backend_block_limit, dp))
     769           22 :    END FUNCTION batch_counts_fit
     770              : 
     771              : ! **************************************************************************************************
     772              : !> \brief Score a candidate batch against both workspace and integer-count limits.
     773              : !> \param ao AO block sizes
     774              : !> \param ri Auxiliary block sizes
     775              : !> \param mo MO block sizes
     776              : !> \param mb MO blocks in a batch
     777              : !> \param rb Auxiliary blocks in a batch
     778              : !> \param nranks Maximum replication multiplicity
     779              : !> \param buffer_elements Output buffer length
     780              : !> \param max_memory_mb Additional workspace budget
     781              : !> \return Score no larger than one for an admissible batch
     782              : ! **************************************************************************************************
     783           16 :    FUNCTION batch_pressure(ao, ri, mo, mb, rb, nranks, buffer_elements, max_memory_mb) RESULT(pressure)
     784              :       INTEGER, INTENT(IN)                                :: ao(:), ri(:), mo(:), mb, rb, nranks
     785              :       INTEGER(int_8), INTENT(IN)                         :: buffer_elements
     786              :       REAL(dp), INTENT(IN)                               :: max_memory_mb
     787              :       REAL(dp)                                           :: pressure
     788              : 
     789              :       REAL(dp)                                           :: count_pressure
     790              : 
     791          418 :       pressure = batch_workspace(SUM(ao), SUM(ri), SUM(mo), mo, ri, mb, rb, buffer_elements)/max_memory_mb
     792           16 :       IF (.NOT. batch_counts_fit(ao, ri, mo, mb, rb, nranks, count_pressure)) pressure = MAX(pressure, count_pressure)
     793           16 :    END FUNCTION batch_pressure
     794              : 
     795              : ! **************************************************************************************************
     796              : !> \brief Largest number of functions in a group of consecutive tensor blocks.
     797              : !> \param sizes Block sizes
     798              : !> \param blocks Number of blocks per batch
     799              : !> \return Maximum batch width
     800              : ! **************************************************************************************************
     801           94 :    FUNCTION batch_width(sizes, blocks) RESULT(width)
     802              :       INTEGER, INTENT(IN)                                :: sizes(:), blocks
     803              :       INTEGER                                            :: width
     804              : 
     805              :       INTEGER                                            :: i
     806              : 
     807           94 :       width = 0
     808           94 :       DO i = 1, SIZE(sizes), blocks
     809         1068 :          width = MAX(width, SUM(sizes(i:i + MIN(blocks - 1, SIZE(sizes) - i))))
     810              :       END DO
     811           94 :    END FUNCTION batch_width
     812              : 
     813              : ! **************************************************************************************************
     814              : !> \brief Conservative busiest-rank workspace estimate, without a balanced-distribution assumption.
     815              : !>        Four copies allow for private operands, contraction results and redistribution buffers.
     816              : !>        Excludes existing SCF storage, allocator overhead and CC4S. This is not an RSS limit.
     817              : !> \param nao Number of AO functions
     818              : !> \param nri Number of auxiliary functions
     819              : !> \param nmo Number of active orbitals
     820              : !> \param mo Orbital block sizes
     821              : !> \param ri Auxiliary block sizes
     822              : !> \param mb Orbital blocks per batch
     823              : !> \param rb Auxiliary blocks per batch
     824              : !> \param buffer_elements Reusable output buffer length
     825              : !> \return Estimated additional memory per rank in decimal MB
     826              : ! **************************************************************************************************
     827           22 :    FUNCTION batch_workspace(nao, nri, nmo, mo, ri, mb, rb, buffer_elements) RESULT(memory_mb)
     828              :       INTEGER, INTENT(IN)                                :: nao, nri, nmo, mo(:), ri(:), mb, rb
     829              :       INTEGER(KIND=int_8), INTENT(IN)                    :: buffer_elements
     830              :       REAL(dp)                                           :: memory_mb
     831              : 
     832              :       REAL(dp)                                           :: a, b, fixed, m, q, r, work
     833              : 
     834           22 :       a = REAL(nao, dp)
     835           22 :       r = REAL(nri, dp)
     836           22 :       m = REAL(nmo, dp)
     837           22 :       b = REAL(batch_width(mo, mb), dp)
     838           22 :       q = REAL(batch_width(ri, rb), dp)
     839           22 :       fixed = 4.0_dp*(r*r + a*m) + 2.0_dp*REAL(buffer_elements, dp)
     840           22 :       work = 4.0_dp*(a*a*q + b*q*a + q*m*b + r*m*b + a*b)
     841           22 :       memory_mb = REAL(dp_size, dp)*MAX(6.0_dp*a*a, fixed + work)/1.0e6_dp
     842           22 :    END FUNCTION batch_workspace
     843              : 
     844              : ! **************************************************************************************************
     845              : !> \brief  Write EigenEnergies.yaml and EigenEnergies.elements (CC4S format, Hartree units).
     846              : !> \param logger Logger for output selection and naming
     847              : !> \param print_section CC4S print key
     848              : !> \param nMO Number of active canonical orbitals
     849              : !> \param homo Number of active occupied orbitals
     850              : !> \param eigenvalues Active orbital energies in Hartree
     851              : !> \param fermi_energy Energy inside the HOMO-LUMO gap
     852              : ! **************************************************************************************************
     853            3 :    SUBROUTINE write_eigen_energies_files(logger, print_section, nMO, homo, eigenvalues, fermi_energy)
     854              :       TYPE(cp_logger_type), POINTER                      :: logger
     855              :       TYPE(section_vals_type), POINTER                   :: print_section
     856              :       INTEGER, INTENT(IN)                                :: nMO, homo
     857              :       REAL(KIND=dp), INTENT(IN)                          :: eigenvalues(nMO), fermi_energy
     858              : 
     859              :       INTEGER                                            :: i, iunit
     860              : 
     861              :       iunit = cp_print_key_unit_nr(logger, print_section, extension='.yaml', middle_name='EigenEnergies', &
     862            3 :                                    file_status='REPLACE', file_position='REWIND')
     863            3 :       WRITE (iunit, '(A)') 'version: 100'
     864            3 :       WRITE (iunit, '(A)') 'type: Tensor'
     865            3 :       WRITE (iunit, '(A)') 'scalarType: Real64'
     866            3 :       WRITE (iunit, '(A)') 'dimensions:'
     867            3 :       WRITE (iunit, '(A,I0)') '- length: ', nMO
     868            3 :       WRITE (iunit, '(A)') '  type: State'
     869            3 :       WRITE (iunit, '(A)') 'elements:'
     870            3 :       WRITE (iunit, '(A)') '  type: TextFile'
     871            3 :       WRITE (iunit, '(A)') 'unit: 1.0'
     872            3 :       WRITE (iunit, '(A)') 'metaData:'
     873            3 :       WRITE (iunit, '(A,ES25.16)') '  fermiEnergy: ', fermi_energy
     874            3 :       WRITE (iunit, '(A,I0)') '  homoIndex: ', homo
     875            3 :       WRITE (iunit, '(A)') '  energies:'
     876           56 :       DO i = 1, nMO
     877           56 :          WRITE (iunit, '(A,ES25.16)') '  - ', eigenvalues(i)
     878              :       END DO
     879            3 :       CALL cp_print_key_finished_output(iunit, logger, print_section)
     880              : 
     881              :       iunit = cp_print_key_unit_nr(logger, print_section, extension='.elements', middle_name='EigenEnergies', &
     882            3 :                                    file_status='REPLACE', file_position='REWIND')
     883           56 :       DO i = 1, nMO
     884           56 :          WRITE (iunit, '(ES25.16)') eigenvalues(i)
     885              :       END DO
     886            3 :       CALL cp_print_key_finished_output(iunit, logger, print_section)
     887              : 
     888            3 :    END SUBROUTINE write_eigen_energies_files
     889              : 
     890              : ! **************************************************************************************************
     891              : !> \brief Gather and write one orbital range in bounded pieces, preserving Fortran element order.
     892              : !> \param para_env MPI parallel environment
     893              : !> \param nRI Number of auxiliary functions
     894              : !> \param nMO Number of active canonical orbitals
     895              : !> \param gamma Distributed vertex, with data only in the selected third-index range
     896              : !> \param iunit Open stream unit on the writing rank
     897              : !> \param first_mo First orbital in this batch
     898              : !> \param last_mo Last orbital in this batch
     899              : !> \param values Reusable output buffer
     900              : !> \param vertex_norm Accumulated squared norm on the writing rank
     901              : !> \param elapsed Accumulated wall times
     902              : ! **************************************************************************************************
     903            8 :    SUBROUTINE write_coulomb_vertex_batch(para_env, nRI, nMO, gamma, iunit, first_mo, last_mo, &
     904            8 :                                          values, vertex_norm, elapsed)
     905              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     906              :       INTEGER, INTENT(IN)                                :: nRI, nMO
     907              :       TYPE(dbt_type), INTENT(INOUT)                      :: gamma
     908              :       INTEGER, INTENT(IN)                                :: iunit, first_mo, last_mo
     909              :       REAL(dp), INTENT(INOUT)                            :: values(:), vertex_norm, elapsed(5)
     910              : 
     911              :       INTEGER                                            :: column, handle, hi, ind(3), j, k, lo, &
     912              :                                                             sizes(3)
     913              :       INTEGER(KIND=int_8)                                :: count, first, last, mirror, nvalues, &
     914              :                                                             offset, row, segment, start, used, &
     915              :                                                             within
     916            8 :       INTEGER, ALLOCATABLE                               :: off1(:), off2(:), off3(:)
     917              :       LOGICAL                                            :: found
     918              :       REAL(dp)                                           :: start_time
     919            8 :       REAL(dp), ALLOCATABLE                              :: block(:, :, :)
     920              :       TYPE(dbt_iterator_type)                            :: iter
     921              : 
     922           16 :       start_time = m_walltime()
     923            8 :       CALL timeset("cc4s_gather", handle)
     924            8 :       CPASSERT(SIZE(values, KIND=int_8) <= output_count_limit)
     925            0 :       ALLOCATE (off1(dbt_nblks_total(gamma, 1)), off2(dbt_nblks_total(gamma, 2)), &
     926           56 :                 off3(dbt_nblks_total(gamma, 3)))
     927            8 :       CALL dbt_get_info(gamma, blk_offset_1=off1, blk_offset_2=off2, blk_offset_3=off3)
     928          114 :       DO column = first_mo, last_mo
     929          106 :          offset = INT(nRI, int_8)*INT(nMO, int_8)*INT(column - 1, int_8)
     930          106 :          nvalues = offset + INT(nRI, int_8)*INT(column, int_8)
     931              :          ! Gather one triangle only. Write its transpose from identical values for exact symmetry.
     932         5548 :          DO WHILE (offset < nvalues)
     933         5434 :             count = MIN(SIZE(values, KIND=int_8), nvalues - offset)
     934      3287490 :             values = 0.0_dp
     935              :             ! Blocks own disjoint elements; threads fill disjoint parts of the bounded buffer.
     936              : !$OMP PARALLEL DEFAULT(NONE) &
     937              : !$OMP SHARED(gamma, off1, off2, off3, nRI, nMO, offset, count, values) &
     938         5434 : !$OMP PRIVATE(iter, ind, sizes, block, found, j, k, first, last, start, lo, hi)
     939              :             CALL dbt_iterator_start(iter, gamma)
     940              :             DO WHILE (dbt_iterator_blocks_left(iter))
     941              :                CALL dbt_iterator_next_block(iter, ind, blk_size=sizes)
     942              :                first = INT(off1(ind(1)) - 1, int_8) + INT(nRI, int_8)* &
     943              :                        (INT(off2(ind(2)) - 1, int_8) + INT(nMO, int_8)*INT(off3(ind(3)) - 1, int_8))
     944              :                last = first + INT(sizes(1), int_8) + INT(nRI, int_8)* &
     945              :                       (INT(sizes(2) - 1, int_8) + INT(nMO, int_8)*INT(sizes(3) - 1, int_8))
     946              :                IF (first >= offset + count .OR. last <= offset) CYCLE
     947              :                CALL dbt_get_block(gamma, ind, block, found)
     948              :                CPASSERT(found)
     949              :                DO k = 1, sizes(3)
     950              :                   DO j = 1, sizes(2)
     951              :                      start = first + INT(nRI, int_8)*(INT(j - 1, int_8) + INT(nMO, int_8)*INT(k - 1, int_8))
     952              :                      CALL segment_overlap(start, sizes(1), offset, count, lo, hi)
     953              :                      IF (lo > hi) CYCLE
     954              :                      values(start + lo - offset:start + hi - offset) = block(lo:hi, j, k)
     955              :                   END DO
     956              :                END DO
     957              :                DEALLOCATE (block)
     958              :             END DO
     959              :             CALL dbt_iterator_stop(iter)
     960              : !$OMP END PARALLEL
     961       285034 :             CALL para_env%sum(values(:count), para_env%source)
     962         5434 :             CALL timestop(handle)
     963         5434 :             elapsed(4) = elapsed(4) + m_walltime() - start_time
     964         5434 :             start_time = m_walltime()
     965         5434 :             CALL timeset("cc4s_write_files", handle)
     966         5434 :             IF (para_env%is_source()) THEN
     967        72617 :                CALL write_coulomb_vertex_block(iunit, offset, values(:count))
     968        72617 :                vertex_norm = vertex_norm + SUM(values(:count)**2)
     969         2717 :                used = 0_int_8
     970         5985 :                DO WHILE (used < count)
     971         3268 :                   within = MODULO(offset + used, INT(nRI, int_8)*INT(nMO, int_8))
     972         3268 :                   row = within/INT(nRI, int_8) + 1_int_8
     973         3268 :                   within = MODULO(within, INT(nRI, int_8))
     974         3268 :                   segment = MIN(count - used, INT(nRI, int_8) - within)
     975         3268 :                   IF (row < column) THEN
     976         3002 :                      mirror = within + INT(nRI, int_8)*(INT(column - 1, int_8) + INT(nMO, int_8)*(row - 1_int_8))
     977        67234 :                      CALL write_coulomb_vertex_block(iunit, mirror, values(used + 1:used + segment))
     978        67234 :                      vertex_norm = vertex_norm + SUM(values(used + 1:used + segment)**2)
     979              :                   END IF
     980         3268 :                   used = used + segment
     981              :                END DO
     982              :             END IF
     983         5434 :             CALL timestop(handle)
     984         5434 :             elapsed(5) = elapsed(5) + m_walltime() - start_time
     985         5434 :             start_time = m_walltime()
     986         5434 :             CALL timeset("cc4s_gather", handle)
     987         5434 :             offset = offset + count
     988              :          END DO
     989              :       END DO
     990            8 :       DEALLOCATE (off1, off2, off3)
     991            8 :       CALL timestop(handle)
     992            8 :       elapsed(4) = elapsed(4) + m_walltime() - start_time
     993           16 :    END SUBROUTINE write_coulomb_vertex_batch
     994              : 
     995              : ! **************************************************************************************************
     996              : !> \brief Write the Coulomb vertex header and open its raw binary elements file.
     997              : !> \param logger Logger for output selection and naming
     998              : !> \param print_section CC4S print key
     999              : !> \param nRI Number of auxiliary functions
    1000              : !> \param nMO Number of active canonical orbitals
    1001              : !> \param periodic Whether the cell is three-dimensionally periodic at Gamma
    1002              : !> \param cutoff_radius Truncated Coulomb cutoff in bohr (zero for molecular Coulomb)
    1003              : !> \param hmat Cell vectors as columns, in bohr
    1004              : !> \param iunit Open stream unit for vertex elements
    1005              : ! **************************************************************************************************
    1006            3 :    SUBROUTINE open_coulomb_vertex_file(logger, print_section, nRI, nMO, periodic, cutoff_radius, hmat, iunit)
    1007              :       TYPE(cp_logger_type), POINTER                      :: logger
    1008              :       TYPE(section_vals_type), POINTER                   :: print_section
    1009              :       INTEGER, INTENT(IN)                                :: nRI, nMO
    1010              :       LOGICAL, INTENT(IN)                                :: periodic
    1011              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff_radius, hmat(3, 3)
    1012              :       INTEGER, INTENT(OUT)                               :: iunit
    1013              : 
    1014              :       INTEGER                                            :: i
    1015              : 
    1016              :       iunit = cp_print_key_unit_nr(logger, print_section, extension='.yaml', middle_name='CoulombVertex', &
    1017            3 :                                    file_status='REPLACE', file_position='REWIND')
    1018            3 :       WRITE (iunit, '(A)') 'version: 100'
    1019            3 :       WRITE (iunit, '(A)') 'type: Tensor'
    1020            3 :       WRITE (iunit, '(A)') 'scalarType: Real64'
    1021            3 :       WRITE (iunit, '(A)') 'dimensions:'
    1022            3 :       WRITE (iunit, '(A,I0)') '- length: ', nRI
    1023            3 :       WRITE (iunit, '(A)') '  type: AuxiliaryField'
    1024            3 :       WRITE (iunit, '(A,I0)') '- length: ', nMO
    1025            3 :       WRITE (iunit, '(A)') '  type: State'
    1026            3 :       WRITE (iunit, '(A,I0)') '- length: ', nMO
    1027            3 :       WRITE (iunit, '(A)') '  type: State'
    1028            3 :       WRITE (iunit, '(A)') 'elements:'
    1029            3 :       WRITE (iunit, '(A)') '  type: IeeeBinaryFile'
    1030            3 :       WRITE (iunit, '(A)') 'unit: 1.0'
    1031            3 :       WRITE (iunit, '(A)') 'metaData:'
    1032            3 :       IF (periodic) THEN
    1033            1 :          WRITE (iunit, '(A)') '  boundaryConditions: "periodic-3d-gamma"'
    1034            1 :          WRITE (iunit, '(A)') '  interactionPotential: "TRUNCATED"'
    1035            1 :          WRITE (iunit, '(A,ES25.16)') '  cutoffRadiusBohr: ', cutoff_radius
    1036            1 :          WRITE (iunit, '(A)') '  madelungCorrection: 0.0'
    1037            1 :          WRITE (iunit, '(A)') '  latticeVectorsBohr:'
    1038            4 :          DO i = 1, 3
    1039              :             WRITE (iunit, '(A,ES25.16,A,ES25.16,A,ES25.16,A)') &
    1040            4 :                '  - [', hmat(1, i), ', ', hmat(2, i), ', ', hmat(3, i), ']'
    1041              :          END DO
    1042              :       ELSE
    1043            2 :          WRITE (iunit, '(A)') '  boundaryConditions: "molecular"'
    1044            2 :          WRITE (iunit, '(A)') '  interactionPotential: "COULOMB"'
    1045              :       END IF
    1046            3 :       CALL cp_print_key_finished_output(iunit, logger, print_section)
    1047              : 
    1048              :       iunit = cp_print_key_unit_nr(logger, print_section, extension='.elements', middle_name='CoulombVertex', &
    1049              :                                    file_status='REPLACE', file_position='REWIND', &
    1050            3 :                                    file_form='UNFORMATTED', file_access='STREAM')
    1051            3 :    END SUBROUTINE open_coulomb_vertex_file
    1052              : 
    1053              : ! **************************************************************************************************
    1054              : !> \brief Write a contiguous vertex block at an explicit position in the raw elements file.
    1055              : !> \param iunit Open stream unit for vertex elements
    1056              : !> \param offset Zero-based element offset in auxiliary-orbital-orbital storage order
    1057              : !> \param values Contiguous block of vertex elements
    1058              : ! **************************************************************************************************
    1059         5719 :    SUBROUTINE write_coulomb_vertex_block(iunit, offset, values)
    1060              :       INTEGER, INTENT(IN)                                :: iunit
    1061              :       INTEGER(KIND=int_8), INTENT(IN)                    :: offset
    1062              :       REAL(KIND=dp), CONTIGUOUS, INTENT(IN)              :: values(:)
    1063              : 
    1064              :       INTEGER                                            :: io_status
    1065              :       INTEGER(KIND=int_8)                                :: position
    1066              : 
    1067              :       ! Use CP2K's Real64 byte size, independent of direct-access record-length units.
    1068         5719 :       CPASSERT(offset >= 0_int_8)
    1069         5719 :       CPASSERT(SIZE(values, KIND=int_8) > 0_int_8)
    1070         5719 :       CPASSERT(SIZE(values, KIND=int_8) <= HUGE(position)/INT(dp_size, int_8) - offset)
    1071         5719 :       position = offset*INT(dp_size, int_8) + 1_int_8
    1072         5719 :       WRITE (iunit, POS=position, IOSTAT=io_status) values
    1073         5719 :       IF (io_status /= 0) THEN
    1074            0 :          CPABORT("CC4S: failed writing CoulombVertex.elements")
    1075              :       END IF
    1076         5719 :    END SUBROUTINE write_coulomb_vertex_block
    1077              : 
    1078              : ! **************************************************************************************************
    1079              : !> \brief  Write the CC4S algorithm sequence and solver settings to cc4s.in.
    1080              : !> \param logger Logger for output selection and naming
    1081              : !> \param print_section CC4S print key
    1082              : !> \param method Requested correlation method: MP2, CCSD or CCSD_T
    1083              : !> \param nocc Number of exported occupied spatial orbitals
    1084              : !> \param max_iter Maximum coupled-cluster iterations
    1085              : !> \param energy_conv CC4S energy convergence threshold
    1086              : !> \param amp_conv CC4S amplitude convergence threshold
    1087              : ! **************************************************************************************************
    1088            3 :    SUBROUTINE write_cc4s_in(logger, print_section, method, nocc, max_iter, energy_conv, amp_conv)
    1089              :       TYPE(cp_logger_type), POINTER                      :: logger
    1090              :       TYPE(section_vals_type), POINTER                   :: print_section
    1091              :       CHARACTER(LEN=*), INTENT(IN)                       :: method
    1092              :       INTEGER, INTENT(IN)                                :: nocc, max_iter
    1093              :       REAL(dp), INTENT(IN)                               :: energy_conv, amp_conv
    1094              : 
    1095              :       CHARACTER(len=default_path_length)                 :: filename
    1096              :       INTEGER                                            :: iunit
    1097              : 
    1098              :       iunit = cp_print_key_unit_nr(logger, print_section, extension='.in', &
    1099            3 :                                    file_status='REPLACE', file_position='REWIND')
    1100              : 
    1101              :       ! --- common header: read tensors, slice, build integrals ---
    1102            3 :       filename = cp_print_key_generate_filename(logger, print_section, 'CoulombVertex', '.yaml', .FALSE.)
    1103            3 :       CALL write_tensor_read(iunit, filename, 'CoulombVertex')
    1104            3 :       filename = cp_print_key_generate_filename(logger, print_section, 'EigenEnergies', '.yaml', .FALSE.)
    1105            3 :       CALL write_tensor_read(iunit, filename, 'EigenEnergies')
    1106            3 :       WRITE (iunit, '(A)') '- name: DefineHolesAndParticles'
    1107            3 :       WRITE (iunit, '(A)') '  in:'
    1108            3 :       WRITE (iunit, '(A)') '    eigenEnergies: EigenEnergies'
    1109            3 :       WRITE (iunit, '(A)') '  out:'
    1110            3 :       WRITE (iunit, '(A)') '    slicedEigenEnergies: EigenEnergies'
    1111            3 :       WRITE (iunit, '(A)') '- name: SliceOperator'
    1112            3 :       WRITE (iunit, '(A)') '  in:'
    1113            3 :       WRITE (iunit, '(A)') '    slicedEigenEnergies: EigenEnergies'
    1114            3 :       WRITE (iunit, '(A)') '    operator: CoulombVertex'
    1115            3 :       WRITE (iunit, '(A)') '  out:'
    1116            3 :       WRITE (iunit, '(A)') '    slicedOperator: CoulombVertex'
    1117            3 :       WRITE (iunit, '(A)') '- name: VertexCoulombIntegrals'
    1118            3 :       WRITE (iunit, '(A)') '  in:'
    1119            3 :       WRITE (iunit, '(A)') '    slicedCoulombVertex: CoulombVertex'
    1120            3 :       WRITE (iunit, '(A)') '  out:'
    1121            3 :       WRITE (iunit, '(A)') '    coulombIntegrals: CoulombIntegrals'
    1122              : 
    1123              :       ! --- MP2 (always included) ---
    1124            3 :       WRITE (iunit, '(A)') '- name: SecondOrderPerturbationTheory'
    1125            3 :       WRITE (iunit, '(A)') '  in:'
    1126            3 :       WRITE (iunit, '(A)') '    coulombIntegrals: CoulombIntegrals'
    1127            3 :       WRITE (iunit, '(A)') '    slicedEigenEnergies: EigenEnergies'
    1128            3 :       WRITE (iunit, '(A)') '  out:'
    1129            3 :       WRITE (iunit, '(A)') '    energy: Mp2Energy'
    1130              : 
    1131              :       ! --- CoupledCluster (CCSD, CCSD_T) ---
    1132            6 :       SELECT CASE (TRIM(method))
    1133              :       CASE ('MP2')
    1134              :          ! The MP2 driver is complete above.
    1135              :       CASE ('CCSD', 'CCSD_T')
    1136            3 :          CALL write_coupled_cluster_step(iunit, 'Ccsd', 'Amplitudes', nocc, max_iter, energy_conv, amp_conv)
    1137              :       CASE DEFAULT
    1138            3 :          CPABORT("CC4S: unsupported method")
    1139              :       END SELECT
    1140              : 
    1141              :       ! --- PerturbativeTriples (CCSD_T only) ---
    1142            3 :       IF (TRIM(method) == 'CCSD_T') THEN
    1143            3 :          WRITE (iunit, '(A)') '- name: PerturbativeTriples'
    1144            3 :          WRITE (iunit, '(A)') '  in:'
    1145            3 :          WRITE (iunit, '(A)') '    slicedEigenEnergies: EigenEnergies'
    1146            3 :          WRITE (iunit, '(A)') '    amplitudes: Amplitudes'
    1147            3 :          WRITE (iunit, '(A)') '    coulombIntegrals: CoulombIntegrals'
    1148            3 :          WRITE (iunit, '(A)') '  out: {}'
    1149              :       END IF
    1150              : 
    1151            3 :       CALL cp_print_key_finished_output(iunit, logger, print_section)
    1152              : 
    1153            3 :    END SUBROUTINE write_cc4s_in
    1154              : 
    1155              : ! **************************************************************************************************
    1156              : !> \brief  Write a single CoupledCluster step to the cc4s.in file.
    1157              : !> \param iunit Output file unit
    1158              : !> \param cc_method CC4S coupled-cluster method
    1159              : !> \param amp_key CC4S output key for amplitudes
    1160              : !> \param nocc Number of exported occupied spatial orbitals
    1161              : !> \param max_iter Maximum coupled-cluster iterations
    1162              : !> \param energy_conv CC4S energy convergence threshold
    1163              : !> \param amp_conv CC4S amplitude convergence threshold
    1164              : ! **************************************************************************************************
    1165            3 :    SUBROUTINE write_coupled_cluster_step(iunit, cc_method, amp_key, nocc, max_iter, energy_conv, amp_conv)
    1166              :       INTEGER, INTENT(IN)                                :: iunit
    1167              :       CHARACTER(LEN=*), INTENT(IN)                       :: cc_method, amp_key
    1168              :       INTEGER, INTENT(IN)                                :: nocc, max_iter
    1169              :       REAL(dp), INTENT(IN)                               :: energy_conv, amp_conv
    1170              : 
    1171            3 :       WRITE (iunit, '(A)') '- name: CoupledCluster'
    1172            3 :       WRITE (iunit, '(A)') '  in:'
    1173            3 :       WRITE (iunit, '(A)') '    method: '//TRIM(cc_method)
    1174              :       ! Match CC4S v1.1 defaults using the occupied space after frozen-core removal.
    1175            3 :       WRITE (iunit, '(A,I0)') '    integralsSliceSize: ', nocc
    1176            3 :       WRITE (iunit, '(A)') '    slicedEigenEnergies: EigenEnergies'
    1177            3 :       WRITE (iunit, '(A)') '    coulombIntegrals: CoulombIntegrals'
    1178            3 :       WRITE (iunit, '(A)') '    slicedCoulombVertex: CoulombVertex'
    1179            3 :       WRITE (iunit, '(A,I0)') '    maxIterations: ', max_iter
    1180            3 :       WRITE (iunit, '(A,ES25.16)') '    energyConvergence: ', energy_conv
    1181            3 :       WRITE (iunit, '(A,ES25.16)') '    amplitudesConvergence: ', amp_conv
    1182            3 :       WRITE (iunit, '(A)') '    mixer:'
    1183            3 :       WRITE (iunit, '(A)') '      type: DiisMixer'
    1184            3 :       WRITE (iunit, '(A)') '      maxResidua: 4'
    1185            3 :       WRITE (iunit, '(A)') '  out:'
    1186            3 :       WRITE (iunit, '(A)') '    amplitudes: '//TRIM(amp_key)
    1187            3 :       WRITE (iunit, '(A)') '    energy: CoupledClusterEnergy'
    1188              : 
    1189            3 :    END SUBROUTINE write_coupled_cluster_step
    1190              : 
    1191              : ! **************************************************************************************************
    1192              : !> \brief Write a tensor reference relative to the generated CC4S input's directory.
    1193              : !> \param iunit CC4S input unit
    1194              : !> \param filename Generated tensor header filename
    1195              : !> \param destination CC4S tensor name
    1196              : ! **************************************************************************************************
    1197            6 :    SUBROUTINE write_tensor_read(iunit, filename, destination)
    1198              :       INTEGER, INTENT(IN)                                :: iunit
    1199              :       CHARACTER(len=*), INTENT(IN)                       :: filename, destination
    1200              : 
    1201              :       INTEGER                                            :: i
    1202              : 
    1203            6 :       WRITE (iunit, '(A)') '- name: Read'
    1204            6 :       WRITE (iunit, '(A)') '  in:'
    1205            6 :       WRITE (iunit, '(A)', ADVANCE='NO') "    fileName: '"
    1206              :       ! A quoted YAML scalar preserves spaces and punctuation; apostrophes are doubled.
    1207          224 :       DO i = INDEX(filename, '/', BACK=.TRUE.) + 1, LEN_TRIM(filename)
    1208          218 :          WRITE (iunit, '(A)', ADVANCE='NO') filename(i:i)
    1209          224 :          IF (filename(i:i) == "'") WRITE (iunit, '(A)', ADVANCE='NO') "'"
    1210              :       END DO
    1211            6 :       WRITE (iunit, '(A)') "'"
    1212            6 :       WRITE (iunit, '(A)') '  out:'
    1213            6 :       WRITE (iunit, '(A)') '    destination: '//destination
    1214            6 :    END SUBROUTINE write_tensor_read
    1215              : 
    1216              : END MODULE cc4s_export
        

Generated by: LCOV version 2.0-1