|
| 1 | +! SPDX-Identifer: MIT |
| 2 | + |
| 3 | +#:include "common.fypp" |
| 4 | +#:set RANKS = range(1, MAXRANK + 1) |
| 5 | +#:set KINDS_TYPES = REAL_KINDS_TYPES + INT_KINDS_TYPES + CMPLX_KINDS_TYPES |
| 6 | + |
| 7 | +!> Description of the npy format taken from |
| 8 | +!> https://numpy.org/doc/stable/reference/generated/numpy.lib.format.html |
| 9 | +!> |
| 10 | +!>## Format Version 1.0 |
| 11 | +!> |
| 12 | +!> The first 6 bytes are a magic string: exactly \x93NUMPY. |
| 13 | +!> |
| 14 | +!> The next 1 byte is an unsigned byte: |
| 15 | +!> the major version number of the file format, e.g. \x01. |
| 16 | +!> |
| 17 | +!> The next 1 byte is an unsigned byte: |
| 18 | +!> the minor version number of the file format, e.g. \x00. |
| 19 | +!> Note: the version of the file format is not tied to the version of the numpy package. |
| 20 | +!> |
| 21 | +!> The next 2 bytes form a little-endian unsigned short int: |
| 22 | +!> the length of the header data HEADER_LEN. |
| 23 | +!> |
| 24 | +!> The next HEADER_LEN bytes form the header data describing the array’s format. |
| 25 | +!> It is an ASCII string which contains a Python literal expression of a dictionary. |
| 26 | +!> It is terminated by a newline (\n) and padded with spaces (\x20) to make the total |
| 27 | +!> of len(magic string) + 2 + len(length) + HEADER_LEN be evenly divisible by 64 for |
| 28 | +!> alignment purposes. |
| 29 | +!> |
| 30 | +!> The dictionary contains three keys: |
| 31 | +!> |
| 32 | +!> - “descr”: dtype.descr |
| 33 | +!> An object that can be passed as an argument to the numpy.dtype constructor |
| 34 | +!> to create the array’s dtype. |
| 35 | +!> |
| 36 | +!> - “fortran_order”: bool |
| 37 | +!> Whether the array data is Fortran-contiguous or not. Since Fortran-contiguous |
| 38 | +!> arrays are a common form of non-C-contiguity, we allow them to be written directly |
| 39 | +!> to disk for efficiency. |
| 40 | +!> |
| 41 | +!> - “shape”: tuple of int |
| 42 | +!> The shape of the array. |
| 43 | +!> |
| 44 | +!> For repeatability and readability, the dictionary keys are sorted in alphabetic order. |
| 45 | +!> This is for convenience only. A writer SHOULD implement this if possible. A reader MUST |
| 46 | +!> NOT depend on this. |
| 47 | +!> |
| 48 | +!> Following the header comes the array data. If the dtype contains Python objects |
| 49 | +!> (i.e. dtype.hasobject is True), then the data is a Python pickle of the array. |
| 50 | +!> Otherwise the data is the contiguous (either C- or Fortran-, depending on fortran_order) |
| 51 | +!> bytes of the array. Consumers can figure out the number of bytes by multiplying the |
| 52 | +!> number of elements given by the shape (noting that shape=() means there is 1 element) |
| 53 | +!> by dtype.itemsize. |
| 54 | +!> |
| 55 | +!>## Format Version 2.0 |
| 56 | +!> |
| 57 | +!> The version 1.0 format only allowed the array header to have a total size of 65535 bytes. |
| 58 | +!> This can be exceeded by structured arrays with a large number of columns. |
| 59 | +!> The version 2.0 format extends the header size to 4 GiB. numpy.save will automatically |
| 60 | +!> save in 2.0 format if the data requires it, else it will always use the more compatible |
| 61 | +!> 1.0 format. |
| 62 | +!> |
| 63 | +!> The description of the fourth element of the header therefore has become: |
| 64 | +!> “The next 4 bytes form a little-endian unsigned int: the length of the header data |
| 65 | +!> HEADER_LEN.” |
| 66 | +!> |
| 67 | +!>## Format Version 3.0 |
| 68 | +!> |
| 69 | +!> This version replaces the ASCII string (which in practice was latin1) with a |
| 70 | +!> utf8-encoded string, so supports structured types with any unicode field names. |
| 71 | +module stdlib_io_npy |
| 72 | + use stdlib_kinds, only : int8, int16, int32, int64, sp, dp, xdp, qp |
| 73 | + implicit none |
| 74 | + private |
| 75 | + |
| 76 | + public :: save_npy, load_npy |
| 77 | + |
| 78 | + |
| 79 | + !> Version: experimental |
| 80 | + !> |
| 81 | + !> Save multidimensional array in npy format |
| 82 | + !> ([Specification](../page/specs/stdlib_io.html#save_npy)) |
| 83 | + interface save_npy |
| 84 | + #:for k1, t1 in KINDS_TYPES |
| 85 | + #:for rank in RANKS |
| 86 | + module subroutine save_npy_${t1[0]}$${k1}$_${rank}$(filename, array, iostat, iomsg) |
| 87 | + character(len=*), intent(in) :: filename |
| 88 | + ${t1}$, intent(in) :: array${ranksuffix(rank)}$ |
| 89 | + integer, intent(out), optional :: iostat |
| 90 | + character(len=:), allocatable, intent(out), optional :: iomsg |
| 91 | + end subroutine save_npy_${t1[0]}$${k1}$_${rank}$ |
| 92 | + #:endfor |
| 93 | + #:endfor |
| 94 | + end interface save_npy |
| 95 | + |
| 96 | + !> Version: experimental |
| 97 | + !> |
| 98 | + !> Load multidimensional array in npy format |
| 99 | + !> ([Specification](../page/specs/stdlib_io.html#load_npy)) |
| 100 | + interface load_npy |
| 101 | + #:for k1, t1 in KINDS_TYPES |
| 102 | + #:for rank in RANKS |
| 103 | + module subroutine load_npy_${t1[0]}$${k1}$_${rank}$(filename, array, iostat, iomsg) |
| 104 | + character(len=*), intent(in) :: filename |
| 105 | + ${t1}$, allocatable, intent(out) :: array${ranksuffix(rank)}$ |
| 106 | + integer, intent(out), optional :: iostat |
| 107 | + character(len=:), allocatable, intent(out), optional :: iomsg |
| 108 | + end subroutine load_npy_${t1[0]}$${k1}$_${rank}$ |
| 109 | + #:endfor |
| 110 | + #:endfor |
| 111 | + end interface load_npy |
| 112 | + |
| 113 | + |
| 114 | + character(len=*), parameter :: nl = achar(10) |
| 115 | + |
| 116 | + character(len=*), parameter :: & |
| 117 | + type_iint8 = "<i1", type_iint16 = "<i2", type_iint32 = "<i4", type_iint64 = "<i8", & |
| 118 | + type_rsp = "<f4", type_rdp = "<f8", type_rxdp = "<f10", type_rqp = "<f16", & |
| 119 | + type_csp = "<c8", type_cdp = "<c16", type_cxdp = "<c20", type_cqp = "<c32" |
| 120 | + |
| 121 | + character(len=*), parameter :: & |
| 122 | + & magic_number = char(int(z"93")), & |
| 123 | + & magic_string = "NUMPY" |
| 124 | + |
| 125 | + |
| 126 | +end module stdlib_io_npy |
0 commit comments