#+TITLE: Examples #+STARTUP: latexpreview #+SETUPFILE: ./theme.setup * Writing nuclear coordinates Here is a demonstration of how to use TREXIO to write the nuclear coordinates of a water molecule to a file. It shows the basic steps involved in opening a file, writing the data, and closing the file, as well as the necessary TREXIO functions to perform these actions. ** C #+begin_src c #include #include int main() { int num = 3; // Number of atoms double coord[][3] = { // xyz coordinates in atomic units 0. , 0. , -0.24962655, 0. , 2.70519714, 1.85136466, 0. , -2.70519714, 1.85136466 }; trexio_exit_code rc; // Open the TREXIO file trexio_t* f = trexio_open("water.trexio", 'w', TREXIO_HDF5, &rc); if (rc != TREXIO_SUCCESS) { fprintf(stderr, "Error: %s\n", trexio_string_of_error(rc)); return -1; } // Write the number of nuclei rc = trexio_write_nucleus_num (f, num); if (rc != TREXIO_SUCCESS) { fprintf(stderr, "Error: %s\n", trexio_string_of_error(rc)); return -1; } // Write the nuclear coordinates rc = trexio_write_nucleus_coord (f, &coord[0][0]); if (rc != TREXIO_SUCCESS) { fprintf(stderr, "Error: %s\n", trexio_string_of_error(rc)); return -1; } // Close the TREXIO file rc = trexio_close(f); if (rc != TREXIO_SUCCESS) { fprintf(stderr, "Error: %s\n", trexio_string_of_error(rc)); return -1; } return 0; } #+end_src ** Python This code uses the TREXIO Python binding to create a new TREXIO file named =water.trexio=, and write the nuclear coordinates of a water molecule. The ~coord~ variable is a list of three lists, each containing the x, y, and z coordinates of the water molecule's nuclei. The ~with~ statement is used to ensure the file is properly closed after the write is complete. The ~trexio.write_nucleus_num~ function is used to write the number of nuclei in the system. The ~trexio.write_nucleus_coord~ function is used to write the nuclear coordinates of the system. #+begin_src python import trexio coord = [ # xyz coordinates in atomic units [0. , 0., -0.24962655], [0. , 2.70519714, 1.85136466], [0. , -2.70519714, 1.85136466] ] # The Python API calls can raise `trexio.Error` # exceptions to be handled via try/except clauses # in the user application with trexio.File("water.trexio", 'w', back_end=trexio.TREXIO_HDF5) as f: trexio.write_nucleus_num(f, len(coord)) trexio.write_nucleus_coord(f, coord) #+end_src ** Fortran #+begin_src f90 program trexio_water use trexio integer, parameter :: num=3 ! Number of nuclei double precision :: coord(3,3) ! Array of atom coordinates integer(trexio_t) :: f ! The TREXIO file handle integer(trexio_exit_code) :: rc ! TREXIO return code character*(128) :: err_msg ! String holding the error message coord(:,:) = reshape( (/ 0.d0 , 0.d0 , -0.24962655d0, & 0.d0 , 2.70519714d0, 1.85136466d0, & 0.d0 , -2.70519714d0, 1.85136466d0 /), & shape(coord) ) ! Open the TREXIO file f = trexio_open ('water.trexio', 'w', TREXIO_HDF5, rc) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error: '//trim(err_msg) call exit(-1) end if ! Write the number of nuclei rc = trexio_write_nucleus_num (f, num) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error: '//trim(err_msg) call exit(-1) end if ! Write the nuclear coordinates rc = trexio_write_nucleus_coord (f, coord) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error: '//trim(err_msg) call exit(-1) end if ! Close the TREXIO file rc = trexio_close(f) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error: '//trim(err_msg) call exit(-1) end if end program #+end_src * Accessing sparse quantities (integrals) ** Fortran :PROPERTIES: :header-args: :tangle print_energy.f90 :END: #+begin_src f90 program print_energy use trexio implicit none character*(128) :: filename ! Name of the input file integer :: rc ! Return code for error checking integer(8) :: f ! TREXIO file handle character*(128) :: err_msg ! Error message #+end_src This program computes the energy as: \[ E = E_{\text{NN}} + \sum_{ij} \gamma_{ij}\, \langle j | h | i \rangle\, +\, \frac{1}{2} \sum_{ijkl} \Gamma_{ijkl}\, \langle k l | i j \rangle\; \textrm{ with } \; 0 < i,j,k,l \le n \] One needs to read from the TREXIO file: - $n$ :: The number of molecular orbitals - $E_{\text{NN}}$ :: The nuclear repulsion energy - $\gamma_{ij}$ :: The one-body reduced density matrix - $\langle j |h| i \rangle$ :: The one-electron Hamiltonian integrals - $\Gamma_{ijkl}$ :: The two-body reduced density matrix - $\langle k l | i j \rangle$ :: The electron repulsion integrals #+begin_src f90 integer :: n double precision :: E, E_nn double precision, allocatable :: D(:,:), h0(:,:) double precision, allocatable :: G(:,:,:,:), W(:,:,:,:) #+end_src *** Declare Temporary variables #+begin_src f90 integer :: i, j, k, l, m integer(8), parameter :: BUFSIZE = 100000_8 integer(8) :: offset, icount, size_max integer :: buffer_index(4,BUFSIZE) double precision :: buffer_values(BUFSIZE) double precision, external :: ddot ! BLAS dot product #+end_src *** Obtain the name of the TREXIO file from the command line, and open it for reading #+begin_src f90 call getarg(1, filename) f = trexio_open (filename, 'r', TREXIO_AUTO, rc) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error opening TREXIO file: '//trim(err_msg) stop end if #+end_src *** Read the nuclear repulsion energy #+begin_src f90 rc = trexio_read_nucleus_repulsion(f, E_nn) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error reading nuclear repulsion: '//trim(err_msg) stop end if #+end_src *** Read the number of molecular orbitals #+begin_src f90 rc = trexio_read_mo_num(f, n) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error reading number of MOs: '//trim(err_msg) stop end if #+end_src *** Allocate memory #+begin_src f90 allocate( D(n,n), h0(n,n) ) allocate( G(n,n,n,n), W(n,n,n,n) ) G(:,:,:,:) = 0.d0 W(:,:,:,:) = 0.d0 #+end_src *** Read one-electron quantities #+begin_src f90 rc = trexio_has_mo_1e_int_core_hamiltonian(f) if (rc /= TREXIO_SUCCESS) then stop 'No core hamiltonian in file' end if rc = trexio_read_mo_1e_int_core_hamiltonian(f, h0) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error reading core Hamiltonian: '//trim(err_msg) stop end if rc = trexio_has_rdm_1e(f) if (rc /= TREXIO_SUCCESS) then stop 'No 1e RDM in file' end if rc = trexio_read_rdm_1e(f, D) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error reading one-body RDM: '//trim(err_msg) stop end if #+end_src *** Read two-electron quantities Reading is done with OpenMP. Each thread reads its own buffer, and the buffers are then processed in parallel. Reading the file requires a lock, so it is done in a critical section. The ~offset~ variable is shared, and it is incremented in the critical section. For each read, the function returns in ~icount~ the number of read integrals, so this variable needs also to be protected in the critical section when modified. **** Electron repulsion integrals #+begin_src f90 rc = trexio_has_mo_2e_int_eri(f) if (rc /= TREXIO_SUCCESS) then stop 'No electron repulsion integrals in file' end if rc = trexio_read_mo_2e_int_eri_size (f, size_max) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error reading number of ERIs: '//trim(err_msg) stop end if offset = 0_8 !$OMP PARALLEL DEFAULT(SHARED) PRIVATE(icount, i, j, k, l, & !$OMP buffer_index, buffer_values, m) icount = BUFSIZE do while (icount == BUFSIZE) !$OMP CRITICAL if (offset < size_max) then rc = trexio_read_mo_2e_int_eri(f, offset, icount, buffer_index, buffer_values) offset = offset + icount else icount = 0 end if !$OMP END CRITICAL do m=1,icount i = buffer_index(1,m) j = buffer_index(2,m) k = buffer_index(3,m) l = buffer_index(4,m) W(i,j,k,l) = buffer_values(m) W(k,j,i,l) = buffer_values(m) W(i,l,k,j) = buffer_values(m) W(k,l,i,j) = buffer_values(m) W(j,i,l,k) = buffer_values(m) W(j,k,l,i) = buffer_values(m) W(l,i,j,k) = buffer_values(m) W(l,k,j,i) = buffer_values(m) end do end do !$OMP END PARALLEL #+end_src **** Reduced density matrix #+begin_src f90 rc = trexio_has_rdm_2e(f) if (rc /= TREXIO_SUCCESS) then stop 'No two-body density matrix in file' end if rc = trexio_read_rdm_2e_size (f, size_max) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error reading number of 2-RDM elements: '//trim(err_msg) stop end if offset = 0_8 !$OMP PARALLEL DEFAULT(SHARED) PRIVATE(icount, i, j, k, l, & !$OMP buffer_index, buffer_values, m) icount = bufsize do while (offset < size_max) !$OMP CRITICAL if (offset < size_max) then rc = trexio_read_rdm_2e(f, offset, icount, buffer_index, buffer_values) offset = offset + icount else icount = 0 end if !$OMP END CRITICAL do m=1,icount i = buffer_index(1,m) j = buffer_index(2,m) k = buffer_index(3,m) l = buffer_index(4,m) G(i,j,k,l) = buffer_values(m) end do end do !$OMP END PARALLEL #+end_src *** Compute the energy When the orbitals are real, we can use \begin{eqnarray*} E &=& E_{\text{NN}} + \sum_{ij} \gamma_{ij}\, \langle j | h | i \rangle\, +\, \frac{1}{2} \sum_{ijkl} \Gamma_{ijkl}\, \langle k l | i j \rangle \\ &=& E_{\text{NN}} + \sum_{ij} \gamma_{ij}\, \langle i | h | j \rangle\, +\, \frac{1}{2} \sum_{ijkl} \Gamma_{ijkl}\, \langle i j | k l \rangle \\ \end{eqnarray*} As $(n,m)$ 2D arrays are stored in memory as $(n \times m)$ 1D arrays, we could pass the matrices to the ~ddot~ BLAS function to perform the summations in a single call for the 1-electron quantities. Instead, we prefer to interleave the 1-electron (negative) and 2-electron (positive) summations to have a better cancellation of numerical errors. Here $n^4$ can be larger than the largest possible 32-bit integer, so it is not safe to pass $n^4$ to the ~ddot~ BLAS function. Hence, we perform $n^2$ loops, using vectors of size $n^2$. #+begin_src f90 E = 0.d0 do l=1,n E = E + ddot( n, D(1,l), 1, h0(1,l), 1 ) do k=1,n E = E + 0.5d0 * ddot( n*n, G(1,1,k,l), 1, W(1,1,k,l), 1 ) end do end do E = E + E_nn print *, 'Energy: ', E #+end_src *** Terminate #+begin_src f90 deallocate( D, h0, G, W ) end program #+end_src ** Python :PROPERTIES: :header-args: :tangle print_energy.py :END: #+begin_src python import sys import trexio import numpy as np BUFSIZE = 100000 #+end_src This program computes the energy as: \[ E = E_{\text{NN}} + \sum_{ij} \gamma_{ij}\, \langle j | h | i \rangle\, +\, \frac{1}{2} \sum_{ijkl} \Gamma_{ijkl}\, \langle k l | i j \rangle\; \textrm{ with } \; 0 < i,j,k,l \le n \] One needs to read from the TREXIO file: - $n$ :: The number of molecular orbitals - $E_{\text{NN}}$ :: The nuclear repulsion energy - $\gamma_{ij}$ :: The one-body reduced density matrix - $\langle j |h| i \rangle$ :: The one-electron Hamiltonian integrals - $\Gamma_{ijkl}$ :: The two-body reduced density matrix - $\langle k l | i j \rangle$ :: The electron repulsion integrals *** Obtain the name of the TREXIO file from the command line, and open it for reading #+begin_src python filename = sys.argv[1] f = trexio.File(filename, 'r', trexio.TREXIO_AUTO) #+end_src *** Read the nuclear repulsion energy #+begin_src python E_nn = trexio.read_nucleus_repulsion(f) #+end_src *** Read the number of molecular orbitals #+begin_src python n = trexio.read_mo_num(f) #+end_src *** Read one-electron quantities #+begin_src python if not trexio.has_mo_1e_int_core_hamiltonian(f): print("No core hamiltonian in file") sys.exit(-1) h0 = trexio.read_mo_1e_int_core_hamiltonian(f) if not trexio.has_rdm_1e(f): print("No 1e RDM in file") sys.exit(-1) D = trexio.read_rdm_1e(f) #+end_src *** Read two-electron quantities **** Electron repulsion integrals #+begin_src python if not trexio.has_mo_2e_int_eri(f): print("No electron repulsion integrals in file") sys.exit(-1) size_max = trexio.read_mo_2e_int_eri_size(f) offset = 0 icount = BUFSIZE feof = False W = np.zeros( (n,n,n,n) ) while not feof: buffer_index, buffer_values, icount, feof = trexio.read_mo_2e_int_eri(f, offset, icount) for m in range(icount): i, j, k, l = buffer_index[m] W[i,j,k,l] = buffer_values[m] W[k,j,i,l] = buffer_values[m] W[i,l,k,j] = buffer_values[m] W[k,l,i,j] = buffer_values[m] W[j,i,l,k] = buffer_values[m] W[j,k,l,i] = buffer_values[m] W[l,i,j,k] = buffer_values[m] W[l,k,j,i] = buffer_values[m] #+end_src **** Reduced density matrix #+begin_src python if not trexio.has_rdm_2e(f): print("No two-body density matrix in file") offset = 0 icount = BUFSIZE feof = False G = np.zeros( (n,n,n,n) ) while not feof: buffer_index, buffer_values, icount, feof = trexio.read_rdm_2e(f, offset, icount) for m in range(icount): i, j, k, l = buffer_index[m] G[i,j,k,l] = buffer_values[m] #+end_src *** Compute the energy When the orbitals are real, we can use \begin{eqnarray*} E &=& E_{\text{NN}} + \sum_{ij} \gamma_{ij}\, \langle j | h | i \rangle\, +\, \frac{1}{2} \sum_{ijkl} \Gamma_{ijkl}\, \langle k l | i j \rangle \\ &=& E_{\text{NN}} + \sum_{ij} \gamma_{ij}\, \langle i | h | j \rangle\, +\, \frac{1}{2} \sum_{ijkl} \Gamma_{ijkl}\, \langle i j | k l \rangle \\ \end{eqnarray*} #+begin_src python G = np.reshape(G, (n*n, n*n) ) W = np.reshape(W, (n*n, n*n) ) E = E_nn E += 0.5*sum( [ np.dot(G[:,l], W[:,l]) for l in range(n*n) ] ) E += sum( [ np.dot(D[:,l], h0[:,l]) for l in range(n) ] ) print (f"Energy: {E}") #+end_src * Reading determinants ** Fortran :PROPERTIES: :header-args: :tangle print_dets.f90 :END: #+begin_src f90 program test use trexio implicit none character*(128) :: filename ! Name of the input file integer(trexio_exit_code) :: rc ! Return code for error checking integer(trexio_t) :: trex_determinant_file ! TREXIO file handle character*(128) :: err_msg ! Error message integer*8, allocatable :: buffer(:,:,:) integer(8) :: offset, icount, BUFSIZE integer :: ndet, int64_num, m integer :: occ_num_up, occ_num_dn integer, allocatable :: orb_list_up(:), orb_list_dn(:) call getarg(1, filename) trex_determinant_file = trexio_open(filename, 'r', TREXIO_AUTO, rc) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error opening TREXIO file: '//trim(err_msg) stop end if rc = trexio_read_determinant_num(trex_determinant_file, ndet) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error reading determinant_num: '//trim(err_msg) stop end if print *, 'ndet', ndet rc = trexio_get_int64_num(trex_determinant_file, int64_num) if (rc /= TREXIO_SUCCESS) then call trexio_string_of_error(rc, err_msg) print *, 'Error reading int64_num: '//trim(err_msg) stop end if print *, 'int64_num', int64_num BUFSIZE = 1000_8 allocate(buffer(int64_num, 2, BUFSIZE)) allocate(orb_list_up(int64_num*64), orb_list_dn(int64_num*64)) offset = 0_8 icount = BUFSIZE do while (icount == BUFSIZE) if (offset < ndet) then rc = trexio_read_determinant_list(trex_determinant_file, offset, icount, buffer) offset = offset + icount else icount = 0 end if print *, '---' do m=1,icount rc = trexio_to_orbital_list_up_dn(int64_num, buffer(1,1,m), & orb_list_up, orb_list_dn, occ_num_up, occ_num_dn) print '(100(I3,X))', (orb_list_up(1:occ_num_up)), (orb_list_dn(1:occ_num_dn)) print *, '' end do end do deallocate(buffer, orb_list_dn, orb_list_up) end #+end_src