stdlib_io_npy_save.fypp Source File


Source Code

! SPDX-Identifer: MIT

#:include "common.fypp"
#:set RANKS = range(1, MAXRANK + 1)
#:set KINDS_TYPES = REAL_KINDS_TYPES + INT_KINDS_TYPES + CMPLX_KINDS_TYPES

!> Implementation of saving multidimensional arrays to npy files
submodule (stdlib_io_npy) stdlib_io_npy_save
    use stdlib_error, only : error_stop
    use stdlib_strings, only : to_string
    implicit none

contains


    !> Generate magic header string for npy format
    pure function magic_header(major, minor) result(str)
        !> Major version of npy format
        integer, intent(in) :: major
        !> Minor version of npy format
        integer, intent(in) :: minor
        !> Magic string for npy format
        character(len=8) :: str

        str = magic_number // magic_string // achar(major) // achar(minor)
    end function magic_header


    !> Generate header for npy format
    pure function npy_header(vtype, vshape) result(str)
        !> Type of variable
        character(len=*), intent(in) :: vtype
        !> Shape of variable
        integer, intent(in) :: vshape(:)
        !> Header string for npy format
        character(len=:), allocatable :: str

        integer, parameter :: len_v10 = 8 + 2, len_v20 = 8 + 4, block_size = 64

        str = &
            "{'descr': '"//vtype//&
            "', 'fortran_order': True, 'shape': "//&
            shape_str(vshape)//", }"

        if (len(str) + len_v10 >= 65535) then
            str = str // &
                & repeat(" ", block_size - mod(len(str) + len_v20 + 1, block_size)) // nl
            str = magic_header(2, 0) // to_bytes_i4(int(len(str))) // str
        else
            str = str // &
                & repeat(" ", block_size - mod(len(str) + len_v10 + 1, block_size)) // nl
            str = magic_header(1, 0) // to_bytes_i2(int(len(str))) // str
        end if
    end function npy_header

    !> Write integer as byte string in little endian encoding
    pure function to_bytes_i4(val) result(str)
        !> Integer value to convert to bytes
        integer, intent(in) :: val
        !> String of bytes
        character(len=4) :: str

        str = achar(mod(val, 256**1)) // &
            & achar(mod(val, 256**2) / 256**1) // &
            & achar(mod(val, 256**3) / 256**2) // &
            & achar(val / 256**3)
    end function to_bytes_i4


    !> Write integer as byte string in little endian encoding, 2-byte truncated version
    pure function to_bytes_i2(val) result(str)
        !> Integer value to convert to bytes
        integer, intent(in) :: val
        !> String of bytes
        character(len=2) :: str

        str = achar(mod(val, 2**8)) // &
            & achar(mod(val, 2**16) / 2**8)
    end function to_bytes_i2


    !> Print array shape as tuple of int
    pure function shape_str(vshape) result(str)
        !> Shape of variable
        integer, intent(in) :: vshape(:)
        !> Shape string for npy format
        character(len=:), allocatable :: str

        integer :: i

        str = "("
        do i = 1, size(vshape)
            str = str//to_string(vshape(i))//", "
        enddo
        str = str//")"
    end function shape_str


#:for k1, t1 in KINDS_TYPES
  #:for rank in RANKS
    !> Save ${rank}$-dimensional array in npy format
    module subroutine save_npy_${t1[0]}$${k1}$_${rank}$(filename, array, iostat, iomsg)
        !> Name of the npy file to load from
        character(len=*), intent(in) :: filename
        !> Array to be loaded from the npy file
        ${t1}$, intent(in) :: array${ranksuffix(rank)}$
        !> Error status of loading, zero on success
        integer, intent(out), optional :: iostat
        !> Associated error message in case of non-zero status code
        character(len=:), allocatable, intent(out), optional :: iomsg

        character(len=*), parameter :: vtype = type_${t1[0]}$${k1}$
        integer :: io, stat

        open(newunit=io, file=filename, form="unformatted", access="stream", iostat=stat)
        if (stat == 0) then
            write(io, iostat=stat) npy_header(vtype, shape(array))
        end if
        if (stat == 0) then
            write(io, iostat=stat) array
        end if
        close(io, iostat=stat)

        if (present(iostat)) then
            iostat = stat
        else if (stat /= 0) then
            call error_stop("Failed to write array to file '"//filename//"'")
        end if

        if (present(iomsg)) then
            if (stat /= 0) then
                iomsg = "Failed to write array to file '"//filename//"'"
            end if
        end if
    end subroutine save_npy_${t1[0]}$${k1}$_${rank}$
  #:endfor
#:endfor

end submodule stdlib_io_npy_save