/* * This file is part of the micropython-ulab project, * * https://github.com/v923z/micropython-ulab * * The MIT License (MIT) * * Copyright (c) 2022 Zoltán Vörös */ #include #include "py/builtin.h" #include "py/obj.h" #include "py/runtime.h" #include "py/stream.h" #include "../../ndarray.h" #include "io.h" #define ULAB_IO_BUFFER_SIZE 128 static mp_obj_t io_save(mp_obj_t fname, mp_obj_t ndarray_) { ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(ndarray_); int error; char *buffer = m_new(char, ULAB_IO_BUFFER_SIZE); uint8_t offset = 0; // test for endianness uint16_t x = 1; int8_t endian = (x >> 8) == 1 ? '>' : '<'; mp_obj_t open_args[2] = { fname, MP_OBJ_NEW_QSTR(MP_QSTR_wb) }; mp_obj_t npy = mp_builtin_open(2, open_args, (mp_map_t *)&mp_const_empty_map); const mp_stream_p_t *fout = mp_get_stream(npy); // write header; // magic string + header length, which is always 128 - 10 = 118, represented as a little endian uint16 (0x76, 0x00) // + beginning of the dictionary describing the array memcpy(buffer, "\x93NUMPY\x01\x00\x76\x00{'descr': '", 21); offset += 21; buffer[offset] = endian; if((ndarray->dtype == NDARRAY_UINT8) || (ndarray->dtype == NDARRAY_INT8)) { // for single-byte data, the endianness doesn't matter buffer[offset] = '|'; } offset++; switch(ndarray->dtype) { case NDARRAY_UINT8: memcpy(buffer+offset, "u1", 2); break; case NDARRAY_INT8: memcpy(buffer+offset, "i1", 2); break; case NDARRAY_UINT16: memcpy(buffer+offset, "u2", 2); break; case NDARRAY_INT16: memcpy(buffer+offset, "i2", 2); break; case NDARRAY_FLOAT: #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT memcpy(buffer+offset, "f4", 2); #else memcpy(buffer+offset, "f8", 2); #endif break; #if ULAB_SUPPORTS_COMPLEX case NDARRAY_COMPLEX: #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT memcpy(buffer+offset, "c8", 2); #else memcpy(buffer+offset, "c16", 2); #endif offset++; break; #endif } offset += 2; memcpy(buffer+offset, "', 'fortran_order': False, 'shape': (", 37); offset += 37; if(ndarray->ndim == 1) { offset += sprintf(buffer+offset, "%ld,", ndarray->shape[ULAB_MAX_DIMS - 1]); } else { for(uint8_t i = 0; i < ndarray->ndim - 1; i++) { offset += sprintf(buffer+offset, "%ld, ", ndarray->shape[ULAB_MAX_DIMS - i - 1]); } offset += sprintf(buffer+offset, "%ld", ndarray->shape[ULAB_MAX_DIMS - 1]); } memcpy(buffer+offset, "), }", 4); offset += 4; // pad with space till the very end memset(buffer+offset, 32, ULAB_IO_BUFFER_SIZE - offset - 1); buffer[ULAB_IO_BUFFER_SIZE - 1] = '\n'; fout->write(npy, buffer, ULAB_IO_BUFFER_SIZE, &error); // write the array data uint8_t sz = ndarray->itemsize; offset = 0; uint8_t *array = (uint8_t *)ndarray->array; // TODO: if flatiter is available, we can save the loop expansion #if ULAB_MAX_DIMS > 3 size_t i = 0; do { #endif #if ULAB_MAX_DIMS > 2 size_t j = 0; do { #endif #if ULAB_MAX_DIMS > 1 size_t k = 0; do { #endif size_t l = 0; do { memcpy(buffer+offset, array, sz); offset += sz; if(offset == ULAB_IO_BUFFER_SIZE) { fout->write(npy, buffer, offset, &error); offset = 0; } array += ndarray->strides[ULAB_MAX_DIMS - 1]; l++; } while(l < ndarray->shape[ULAB_MAX_DIMS - 1]); #if ULAB_MAX_DIMS > 1 array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; array += ndarray->strides[ULAB_MAX_DIMS - 2]; k++; } while(k < ndarray->shape[ULAB_MAX_DIMS - 2]); #endif #if ULAB_MAX_DIMS > 2 array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2]; array += ndarray->strides[ULAB_MAX_DIMS - 3]; j++; } while(j < ndarray->shape[ULAB_MAX_DIMS - 3]); #endif #if ULAB_MAX_DIMS > 3 array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3]; array += ndarray->strides[ULAB_MAX_DIMS - 4]; i++; } while(i < ndarray->shape[ULAB_MAX_DIMS - 4]); #endif if(offset != 0) { fout->write(npy, buffer, offset, &error); } m_del(char, buffer, ULAB_IO_BUFFER_SIZE); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_2(io_save_obj, io_save);