-
Dilawar Singh authored1eb469f6
cnpy.hpp 6.18 KiB
/*
* =====================================================================================
*
* Filename: cnpy.h
*
* Description: Write a stl vector to numpy format 2.
*
* This program is part of MOOSE simulator.
*
* Version: 1.0
* Created: 05/04/2016 10:36:19 AM
* Revision: none
* Compiler: gcc
*
* Author: Dilawar Singh (), dilawars@ncbs.res.in
* Organization: NCBS Bangalore
*
* =====================================================================================
*/
#ifndef cnpy_INC
#define cnpy_INC
#include <iostream>
#include <fstream>
#include <vector>
#include <cassert>
#include <complex>
#include <typeinfo>
#ifdef ENABLE_CPP11
#include <memory>
#include <array>
#else /* ----- not ENABLE_CPP11 ----- */
#endif /* ----- not ENABLE_CPP11 ----- */
#include <string>
#include <stdint.h>
#include "global.h"
#include "../utility/print_function.hpp"
using namespace std;
namespace cnpy2 {
// Check the endian-ness of machine at run-time. This is from library
// https://github.com/rogersce/cnpy
char BigEndianTest();
// And another function to convert given std datatype to numpy representation.
char map_type(const std::type_info& t);
void split(vector<string>& strs, string& input, const string& pat);
/**
* @brief Check if a numpy file is sane or not.
*
* Read first 8 bytes and compare with standard header.
*
* @param npy_file Path to file.
*
* @return true if file is sane, else false.
*/
bool is_valid_numpy_file( FILE* fp );
/**
* @brief Parser header from a numpy file. Store it in vector.
*
* @param header
*/
void parse_header( FILE* fp, string& header );
/**
* @brief Change shape in numpy header.
*
* @param
* @param data_len
* @param
*/
void change_shape_in_header( const string& filename
, const size_t data_len, const size_t numcols
);
static const unsigned int __pre__size__ = 8;
static char __pre__[__pre__size__] = {
(char)0x93, 'N', 'U', 'M', 'P', 'Y' /* Magic */
, (char)0x01, (char) 0x00 /* format */
};
template< typename T>
void write_header(
FILE* fp
, const vector<string>& colnames
, vector<unsigned int>shape , char version
)
{
// Heder are always at the begining of file.
fseek( fp, 0, SEEK_SET );
char endianChar = cnpy2::BigEndianTest();
char formatChar = cnpy2::map_type( typeid(T) );
string dict = ""; // This is the header to numpy file
dict += "{'descr': [";
for( vector<string>::const_iterator it = colnames.begin();
it != colnames.end(); it++ )
dict += "('" + *it + "' , '" + endianChar + formatChar + "'),";
dict += "], 'fortran_order': False, 'shape': (";
dict += moose::toString(shape[0]);
for(size_t i = 1; i < shape.size(); i++)
{
dict += ",";
dict += moose::toString(shape[i]);
}
if( shape.size() == 1) dict += ",";
dict += "), }";
// When appending to this file, we need to rewrite header. Size of header
// might change since shape values might change. Minimum shape from (1,)
// could become quite large (13132131321,) etc. For safety region, append a
// chunk of whitespace characters so that overwriting header does not
// overwrite the data. This trick would save us from copying data into
// memory.
dict += string(11, ' '); /* 32 bit number is fit */
// pad with spaces so that preamble+headerlen+dict is modulo 16 bytes.
// preamble is 8 bytes, header len is 4 bytes, total 12.
// dict needs to end with \n
unsigned int remainder = 16 - (12 + dict.size()) % 16;
dict.insert(dict.end(),remainder,' ');
*(dict.end()-1) = '\n';
if( version == '2' )
__pre__[6] = (char) 0x02;
fwrite( __pre__, sizeof( char ), __pre__size__, fp );
// Now write the size of dict. It is 2bytes long in version 1 and 4 bytes
// long in version 2.
if( version == '2' )
{
uint32_t s = dict.size();
fwrite( (char*)&s, sizeof( uint32_t ), 1, fp );
}
else
{
int16_t s = dict.size();
fwrite( (char*)&s, sizeof( uint16_t ), 1, fp );
}
fwrite( dict.c_str(), sizeof(char), dict.size(), fp );
}
// write to version 1 or version 2.
template<typename T>
void save_numpy(
const string& outfile
, const vector<double>& vec
, vector<string> colnames
, const string openmode
, const char version = '1'
)
{
// In our application, we need to write a vector as matrix. We do not
// support the stacking of matrices.
vector<unsigned int> shape;
if( colnames.size() == 0)
return;
shape.push_back( vec.size() / colnames.size());
/* In mode "w", open the file and write a header as well. When file is open
* in mode "a", we assume that file is alreay a valid numpy file.
*/
if( openmode == "w" )
{
FILE* fp = fopen( outfile.c_str(), "wb" );
if( NULL == fp )
{
moose::showWarn( "Could not open file " + outfile );
return;
}
write_header<T>( fp, colnames, shape, version );
fclose( fp );
}
else /* Append mode. */
{
// Do a sanity check if file is really a numpy file.
FILE* fp = fopen( outfile.c_str(), "r" );
if( ! fp )
{
moose::showError( "Can't open " + outfile + " to validate" );
return;
}
else if(! is_valid_numpy_file( fp ) )
{
moose::showWarn( outfile + " is not a valid numpy file"
+ " I am not goind to write to it"
);
return;
}
if( fp )
fclose( fp );
// And change the shape in header.
change_shape_in_header( outfile, vec.size(), colnames.size() );
}
FILE* fp = fopen( outfile.c_str(), "ab" );
if( NULL == fp )
{
moose::showWarn( "Could not open " + outfile + " to write " );
return;
}
fwrite( &vec[0], sizeof(T), vec.size(), fp );
fclose( fp );
}
} /* Namespace cnpy2 ends. */
#endif /* ----- #ifndef cnpy_INC ----- */