Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-09 07:49:28

0001 #ifndef NPU_HH
0002 #define NPU_HH
0003 
0004 /**
0005 NPU.hh : Utilities used from NP.hh
0006 ====================================
0007 
0008 This is developed in https://github.com/simoncblyth/np/
0009 but given the header-only nature is often just incorporated into
0010 other projects together with NP.hh
0011 
0012 **/
0013 
0014 
0015 #include <csignal>
0016 #include <sstream>
0017 #include <iostream>
0018 #include <iomanip>
0019 #include <string>
0020 #include <cstring>
0021 #include <vector>
0022 #include <cassert>
0023 #include <complex>
0024 #include <fstream>
0025 #include <cstdlib>
0026 #include <cstdint>
0027 #include <algorithm>
0028 #include <chrono>
0029 #include <cctype>
0030 #include <locale>
0031 #include <tuple>
0032 
0033 
0034 #include <sys/types.h>
0035 #include <sys/stat.h>
0036 #include <unistd.h>
0037 
0038 
0039 /**
0040 desc : type codes and sizes used by descr_
0041 ---------------------------------------------
0042 **/
0043 
0044 
0045 template<typename T>
0046 struct desc
0047 {
0048     static constexpr char code = '?' ;
0049     static constexpr unsigned size = 0 ;
0050 };
0051 
0052 template<> struct desc<float>  { static constexpr char code = 'f' ; static constexpr unsigned size = sizeof(float)  ; };
0053 template<> struct desc<double> { static constexpr char code = 'f' ; static constexpr unsigned size = sizeof(double) ; };
0054 
0055 template<> struct desc<char> {   static constexpr char code = 'i' ; static constexpr unsigned size = sizeof(char)   ; };
0056 template<> struct desc<short> {  static constexpr char code = 'i' ; static constexpr unsigned size = sizeof(short)  ; };
0057 template<> struct desc<int> {    static constexpr char code = 'i' ; static constexpr unsigned size = sizeof(int)    ; };
0058 template<> struct desc<long> {   static constexpr char code = 'i' ; static constexpr unsigned size = sizeof(long)   ; };
0059 template<> struct desc<long long> {  static constexpr char code = 'i' ; static constexpr unsigned size = sizeof(long long)   ;  };
0060 
0061 template<> struct desc<unsigned char> {   static constexpr char code = 'u' ; static constexpr unsigned size = sizeof(unsigned char) ;  };
0062 template<> struct desc<unsigned short> {  static constexpr char code = 'u' ; static constexpr unsigned size = sizeof(unsigned short) ;   };
0063 template<> struct desc<unsigned int> {    static constexpr char code = 'u' ; static constexpr unsigned size = sizeof(unsigned int) ;  };
0064 template<> struct desc<unsigned long> {   static constexpr char code = 'u' ; static constexpr unsigned size = sizeof(unsigned long) ;   };
0065 template<> struct desc<unsigned long long> {  static constexpr char code = 'u' ; static constexpr unsigned size = sizeof(unsigned long long) ;  };
0066 
0067 template<> struct desc<std::complex<float> > {   static constexpr char code = 'c' ; static constexpr unsigned size = sizeof(std::complex<float>)  ; } ;
0068 template<> struct desc<std::complex<double> > {  static constexpr char code = 'c' ; static constexpr unsigned size = sizeof(std::complex<double>) ; } ;
0069 
0070 struct endian
0071 {
0072     static char constexpr LITTLE = '<' ;
0073     static char constexpr BIG = '>' ;
0074     static char detect() { unsigned one = 1u ; return (*(char *)&one == 1) ? LITTLE : BIG ; } ;
0075 };
0076 
0077 template<typename T>
0078 struct descr_
0079 {
0080     static std::string dtype()  // eg "<f4"
0081     {
0082         std::stringstream ss ;
0083         ss << endian::detect() << desc<T>::code << desc<T>::size ;
0084         return ss.str();
0085     }
0086     static std::string dtype_name() // eg "float32"
0087     {
0088         std::stringstream ss ;
0089         switch(desc<T>::code)
0090         {
0091            case 'f': ss << "float"   ; break ;
0092            case 'i': ss << "int"     ; break ;
0093            case 'u': ss << "uint"    ; break ;
0094            case 'c': ss << "complex" ; break ;
0095         }
0096         ss << desc<T>::size*8 ;
0097         return ss.str();
0098     }
0099 };
0100 
0101 template struct descr_<float> ;
0102 template struct descr_<double> ;
0103 
0104 template struct descr_<char> ;
0105 template struct descr_<short> ;
0106 template struct descr_<int> ;
0107 template struct descr_<long> ;
0108 template struct descr_<long long> ;
0109 
0110 template struct descr_<unsigned char> ;
0111 template struct descr_<unsigned short> ;
0112 template struct descr_<unsigned int> ;
0113 template struct descr_<unsigned long> ;
0114 template struct descr_<unsigned long long> ;
0115 
0116 template struct descr_<std::complex<float> > ;
0117 template struct descr_<std::complex<double> > ;
0118 
0119 
0120 struct dtype_convert
0121 {
0122     static std::string from_name(const char* name)
0123     {
0124         std::stringstream ss ;
0125         ss << endian::detect() ;
0126         if(     strstr(name,"float"))   ss << 'f' ;
0127         else if(strstr(name,"uint"))    ss << 'u' ;
0128         else if(strstr(name,"int"))     ss << 'i' ;
0129         else if(strstr(name,"complex")) ss << 'c' ;
0130 
0131         std::stringstream ii;
0132         for (int i=0 ; i < int(strlen(name)) ; i++) ii << (std::isdigit(name[i]) ? name[i] : ' ' ) ; // replace non-digits with spaces
0133         int nbit(0);
0134         ii >> nbit ;
0135 
0136         bool expect = nbit % 8 == 0 ;
0137         ss << ( expect ? nbit/8 : 0 ) ;
0138 
0139         return ss.str();
0140     }
0141 };
0142 
0143 /**
0144 net_hdr
0145 ---------
0146 
0147 Packing and unpacking of simple network header
0148 composed of a small number of 32bit unsigned ints
0149 expressed in big endian "network order".
0150 
0151 **/
0152 
0153 #include <arpa/inet.h>    // htonl
0154 
0155 
0156 struct net_hdr
0157 {
0158     static constexpr unsigned LENGTH = 4*4 ;
0159 
0160     union uc4_t {
0161         uint32_t          u    ;
0162         char              c[4] ;
0163     };
0164     static std::string pack(const std::vector<unsigned> items);
0165     static void unpack(     const std::string& hdr         , std::vector<unsigned>& items );
0166     static void unpack( char* data, unsigned num_bytes , std::vector<unsigned>& items );
0167 
0168     static unsigned unpack( const std::string& hdr, unsigned index );
0169 };
0170 
0171 
0172 inline std::string net_hdr::pack(const std::vector<unsigned> items) // static
0173 {
0174     unsigned ni = items.size();
0175 
0176     assert( ni == 4 );
0177     assert( sizeof(unsigned) == 4);
0178     assert( ni*sizeof(unsigned) == LENGTH );
0179 
0180     uc4_t uc4 ;
0181     std::string hdr(LENGTH, '\0' );
0182     for(unsigned i=0 ; i < ni ; i++)
0183     {
0184         uc4.u = htonl(items[i]) ;   // to big endian or "network order"
0185         memcpy( (void*)(hdr.data() + i*sizeof(unsigned)), &(uc4.c[0]), 4 );
0186     }
0187     return hdr ;
0188 }
0189 
0190 inline void net_hdr::unpack( const std::string& hdr, std::vector<unsigned>& items ) // static
0191 {
0192     unpack((char*)hdr.data(), hdr.length(), items );
0193 }
0194 
0195 inline unsigned net_hdr::unpack( const std::string& hdr, unsigned index ) // static
0196 {
0197     std::vector<unsigned> items ;
0198     unpack(hdr, items);
0199     return index < items.size() ? items[index] : 0 ;
0200 }
0201 
0202 inline void net_hdr::unpack( char* data, unsigned num_bytes, std::vector<unsigned>& items ) // static
0203 {
0204     assert( 4 == sizeof(unsigned));
0205     unsigned ni = num_bytes/sizeof(unsigned);
0206 
0207     items.clear();
0208     items.resize(ni);
0209 
0210     uc4_t uc4 ;
0211     for(unsigned i=0 ; i < ni ; i++)
0212     {
0213         memcpy( &(uc4.c[0]), data + i*4, 4 );
0214         items[i] = ntohl(uc4.u) ;   // from big endian to endian-ness of host
0215     }
0216 }
0217 
0218 
0219 
0220 struct NPS
0221 {
0222     typedef std::int64_t INT ;
0223     typedef std::uint64_t UINT ;
0224     static constexpr const INT ONE = 1 ;  // -std=c++11 SOMETIMES GIVES LINK ERRORS : but NOT -std=c++17
0225 
0226     NPS(std::vector<INT>& shape_ ) : shape(shape_) {}  ;
0227 
0228     static NPS::INT set_shape(std::vector<INT>& shape_, INT ni, INT nj=-1, INT nk=-1, INT nl=-1, INT nm=-1, INT no=-1 )
0229     {
0230         NPS sh(shape_);
0231         sh.set_shape(ni,nj,nk,nl,nm,no);
0232         return sh.size();
0233     }
0234 
0235     static NPS::INT copy_shape(std::vector<INT>& dst, const std::vector<INT>& src)
0236     {
0237         for(INT i=0 ; i < INT(src.size()) ; i++) dst.push_back(src[i]);
0238         return size(dst);
0239     }
0240 
0241     static size_t copy_shape(std::vector<size_t>& dst, const std::vector<INT>& src)
0242     {
0243         for(size_t i=0 ; i < src.size() ; i++) dst.push_back(src[i]);
0244         return size(dst);
0245     }
0246 
0247 
0248     static NPS::INT copy_shape(std::vector<INT>& dst, INT ni=-1, INT nj=-1, INT nk=-1, INT nl=-1, INT nm=-1, INT no=-1)
0249     {
0250         if(ni >= 0) dst.push_back(ni);   // experimental allow zero items
0251         if(nj > 0) dst.push_back(nj);
0252         if(nk > 0) dst.push_back(nk);
0253         if(nl > 0) dst.push_back(nl);
0254         if(nm > 0) dst.push_back(nm);
0255         if(no > 0) dst.push_back(no);
0256         return size(dst);
0257     }
0258 
0259     void set_shape(INT ni, INT nj=-1, INT nk=-1, INT nl=-1, INT nm=-1, INT no=-1)
0260     {
0261         if(ni >= 0) shape.push_back(ni);   // experimental allow zero items
0262         if(nj > 0) shape.push_back(nj);
0263         if(nk > 0) shape.push_back(nk);
0264         if(nl > 0) shape.push_back(nl);
0265         if(nm > 0) shape.push_back(nm);
0266         if(no > 0) shape.push_back(no);
0267     }
0268     void set_shape(const std::vector<INT>& other)
0269     {
0270         copy_shape(shape, other);
0271     }
0272 
0273     static NPS::INT change_shape(std::vector<INT>& shp, INT ni_, INT nj_=-1, INT nk_=-1, INT nl_=-1, INT nm_=-1, INT no_=-1)
0274     {
0275         INT nv0 = size(shp);
0276         INT nv1 = std::max(ONE,ni_)*std::max(ONE,nj_)*std::max(ONE,nk_)*std::max(ONE,nl_)*std::max(ONE,nm_)*std::max(ONE,no_) ;
0277 
0278         if( nv0 != nv1 )  // try to devine a missing -1 entry
0279         {
0280             if(      ni_ < 0 ) ni_ = nv0/nv1 ;
0281             else if( nj_ < 0 ) nj_ = nv0/nv1 ;
0282             else if( nk_ < 0 ) nk_ = nv0/nv1 ;
0283             else if( nl_ < 0 ) nl_ = nv0/nv1 ;
0284             else if( nm_ < 0 ) nm_ = nv0/nv1 ;
0285             else if( no_ < 0 ) no_ = nv0/nv1 ;
0286 
0287             INT nv2 = std::max(ONE,ni_)*std::max(ONE,nj_)*std::max(ONE,nk_)*std::max(ONE,nl_)*std::max(ONE,nm_)*std::max(ONE,no_) ;
0288             bool expect = nv0 % nv1 == 0 && nv2 == nv0 ;
0289 
0290             if(!expect) std::cout
0291                 << " NPS::change_shape INVALID SHAPE CHANGE : SIZE MUST STAY CONSTANT : ONLY ONE -1 ENTRY CAN BE AUTO-FILLED  "
0292                 << std::endl
0293                 << " nv0 " << nv0
0294                 << " nv1 " << nv1
0295                 << " nv2 " << nv2
0296                 << " ni_ " << ni_
0297                 << " nj_ " << nj_
0298                 << " nk_ " << nk_
0299                 << " nl_ " << nl_
0300                 << " nm_ " << nm_
0301                 << " no_ " << no_
0302                 << std::endl
0303                 ;
0304 
0305             assert(expect);
0306         }
0307 
0308         shp.clear();
0309         return copy_shape(shp, ni_, nj_, nk_, nl_, nm_, no_ );
0310     }
0311 
0312     static NPS::INT product(const std::vector<INT>& src )
0313     {
0314         INT nd = src.size();
0315         INT prod = 1 ;
0316         for(INT i=0 ; i < nd ; i++) prod *= src[i] ;
0317         return prod ;
0318     }
0319     static void reshape(std::vector<INT>& dst, const std::vector<INT>& src )
0320     {
0321         assert( product(dst) == product(src) );
0322         dst = src ;
0323     }
0324 
0325     template<int P>
0326     static void size_2D( INT& width, INT& height, const std::vector<INT>& sh )
0327     {
0328         INT nd = sh.size() ;
0329         assert( nd > 1 && sh[nd-1] == P );
0330         width = sh[nd-2] ;
0331         height = 1 ;
0332         for(INT i=0 ; i < nd-2 ; i++) height *= sh[i] ;
0333     }
0334 
0335     static std::string desc(const std::vector<INT>& shape)
0336     {
0337         std::stringstream ss ;
0338         ss << "("  ;
0339         for(unsigned i=0 ; i < shape.size() ; i++) ss << shape[i] << ", " ;
0340         ss << ")"  ;
0341         return ss.str();
0342     }
0343 
0344     static std::string json(const std::vector<INT>& shape)
0345     {
0346         std::stringstream ss ;
0347         ss << "["  ;
0348         for(unsigned i=0 ; i < shape.size() ; i++)
0349         {
0350             ss << shape[i]  ;
0351             if( i < shape.size() - 1 ) ss << ", " ;
0352         }
0353         ss << "]"  ;
0354         return ss.str();
0355     }
0356 
0357     static NPS::INT size(const std::vector<INT>& shape)
0358     {
0359         INT ndim = INT(shape.size());
0360         INT sz = 1;
0361         for(INT i=0; i<ndim; ++i) sz *= shape[i] ;
0362         return ndim == 0 ? 0 : sz ;
0363     }
0364 
0365     static NPS::UINT usize(const std::vector<INT>& shape)
0366     {
0367         INT ndim = INT(shape.size());
0368         UINT sz = 1;
0369         for(INT i=0; i<ndim; ++i) sz *= shape[i] ;
0370         return ndim == 0 ? 0 : sz ;
0371     }
0372 
0373 
0374 
0375     static size_t size(const std::vector<size_t>& shape)
0376     {
0377         size_t ndim = shape.size();
0378         size_t sz = 1;
0379         for(size_t i=0; i<ndim; ++i) sz *= shape[i] ;
0380         return ndim == 0 ? 0 : sz ;
0381     }
0382 
0383 
0384     static NPS::INT itemsize(const std::vector<INT>& shape)
0385     {
0386         INT sz = 1;
0387         for(unsigned i=1; i<shape.size(); ++i) sz *= shape[i] ;
0388         return sz ;
0389     }
0390 
0391     static NPS::INT itemsize_(const std::vector<INT>& shape, INT i=-1, INT j=-1, INT k=-1, INT l=-1, INT m=-1, INT o=-1 )
0392     {
0393         // assert only one transition from valid indices to skipped indices
0394         if( i == -1 )                                                      assert( j == -1 && k == -1 &&  l == -1 && m == -1 && o == -1 ) ;
0395         if( i > -1 && j == -1 )                                            assert(            k == -1 &&  l == -1 && m == -1 && o == -1 ) ;
0396         if( i > -1 && j > -1 && k == -1 )                                  assert(                        l == -1 && m == -1 && o == -1 ) ;
0397         if( i > -1 && j > -1 && k >  -1 && l == -1 )                       assert(                                   m == -1 && o == -1 ) ;
0398         if( i > -1 && j > -1 && k >  -1 && l >  -1 && m == -1 )            assert(                                              o == -1 ) ;
0399         if( i > -1 && j > -1 && k >  -1 && l >  -1 && m >  -1 && o == -1 ) assert(                                              true    ) ;
0400 
0401         unsigned dim0 = 0 ;
0402         if( i == -1 )                                                      dim0 = 0 ;
0403         if( i > -1 && j == -1 )                                            dim0 = 1 ;
0404         if( i > -1 && j > -1 && k == -1 )                                  dim0 = 2 ;
0405         if( i > -1 && j > -1 && k >  -1 && l == -1 )                       dim0 = 3 ;
0406         if( i > -1 && j > -1 && k >  -1 && l >  -1 && m == -1 )            dim0 = 4 ;
0407         if( i > -1 && j > -1 && k >  -1 && l >  -1 && m >  -1 && o == -1 ) dim0 = 5 ;
0408         if( i > -1 && j > -1 && k >  -1 && l >  -1 && m >  -1 && o >  -1 ) dim0 = 6 ;
0409 
0410         INT sz = 1;
0411         if( dim0 < shape.size() )
0412         {
0413             for(unsigned d=dim0; d<shape.size(); ++d) sz *= shape[d] ;
0414         }
0415 #ifdef DEBUG_NPU
0416         std::cout
0417             << "NPS::itemsize_"
0418             << "(" << std::setw(3) << i
0419             << " " << std::setw(3) << j
0420             << " " << std::setw(3) << k
0421             << " " << std::setw(3) << l
0422             << " " << std::setw(3) << m
0423             << " " << std::setw(3) << o
0424             << ")"
0425             << " " << sz
0426             << std::endl
0427             ;
0428 #endif
0429         return sz ;
0430     }
0431 
0432     std::string desc() const { return desc(shape) ; }
0433     std::string json() const { return json(shape) ; }
0434     NPS::INT size() const { return size(shape) ; }
0435 
0436 
0437     static NPS::INT ni_(const std::vector<INT>& shape) { return shape.size() > 0 ? shape[0] : 1 ;  }
0438     static NPS::INT nj_(const std::vector<INT>& shape) { return shape.size() > 1 ? shape[1] : 1 ;  }
0439     static NPS::INT nk_(const std::vector<INT>& shape) { return shape.size() > 2 ? shape[2] : 1 ;  }
0440     static NPS::INT nl_(const std::vector<INT>& shape) { return shape.size() > 3 ? shape[3] : 1 ;  }
0441     static NPS::INT nm_(const std::vector<INT>& shape) { return shape.size() > 4 ? shape[4] : 1 ;  }
0442     static NPS::INT no_(const std::vector<INT>& shape) { return shape.size() > 5 ? shape[5] : 1 ;  }
0443 
0444     NPS::INT ni_() const { return ni_(shape) ; }
0445     NPS::INT nj_() const { return nj_(shape) ; }
0446     NPS::INT nk_() const { return nk_(shape) ; }
0447     NPS::INT nl_() const { return nl_(shape) ; }
0448     NPS::INT nm_() const { return nm_(shape) ; }
0449     NPS::INT no_() const { return no_(shape) ; }
0450 
0451     NPS::INT idx(INT i, INT j, INT k, INT l, INT m, INT o)
0452     {
0453         [[maybe_unused]] INT ni = ni_() ;
0454         INT nj = nj_() ;
0455         INT nk = nk_() ;
0456         INT nl = nl_() ;
0457         INT nm = nm_() ;
0458         INT no = no_() ;
0459 
0460         return  i*nj*nk*nl*nm*no + j*nk*nl*nm*no + k*nl*nm*no + l*nm*no + m*no + o ;
0461     }
0462 
0463 
0464     std::vector<INT>& shape ;
0465 };
0466 
0467 
0468 
0469 struct U
0470 {
0471     typedef std::vector<std::string> VS ;
0472     typedef std::vector<int64_t> VT ;
0473 
0474 
0475     static constexpr const bool VERBOSE = false ;
0476     static constexpr const bool RAISE = true ;
0477 
0478     enum { ERROR_PATH=-1, DIR_PATH=1 , FILE_PATH=2, OTHER_PATH=3 } ;
0479 
0480     static void sizeof_check();
0481 
0482     static bool EndsWith( const char* s, const char* q) ;
0483     static std::string ChangeExt( const char* s, const char* x1, const char* x2) ;
0484     static std::string DirName( const char* path );
0485 
0486     static std::string BaseName( const char* path );
0487     static const char* BaseName_( const char* path );
0488 
0489     static std::string BaseName_NoSepAsis( const char* path );
0490     static const char* BaseName_NoSepAsis_( const char* path );
0491 
0492 
0493     static std::string FormSiblingPath0( const char* sibname , const char* dirpath );
0494     static std::string FormSiblingPath(  const char* sibname , const char* dirpath );
0495     static std::string FormExecutableSiblingPath( const char* argv0 , const char* dirpath );
0496     static bool        IsExecutableSiblingPath(   const char* argv0,  const char* dirpath );
0497     static int SetEnvDefaultExecutableSiblingPath(const char* ekey, char* argv0, const char* dirpath );
0498 
0499     static int setenvvar( const char* ekey, const char* value, bool overwrite=true, char special_empty_token='\0' );
0500 
0501 
0502     template<typename ... Args>
0503     static std::string Format_( const char* fmt, Args ... args );
0504 
0505     template<typename ... Args>
0506     static const char* Format( const char* fmt, Args ... args );
0507 
0508 
0509 
0510     static std::string FormNameWithPrefix_( char prefix, int idx, int wid=3 );
0511     static const char* FormNameWithPrefix( char prefix, int idx, int wid=3 );
0512 
0513     static std::string FormName_( int idx, int wid=3 );
0514     static const char* FormName( int idx, int wid=3 );
0515 
0516     static std::string FormName_( const char* prefix, int idx, const char* ext, int wid=-1 );
0517     static const char* FormName(  const char* prefix, int idx, const char* ext, int wid=-1 );
0518 
0519     static std::string FormName_( const char* prefix, const char* body, const char* ext );
0520     static const char* FormName( const char* prefix, const char* body, const char* ext );
0521 
0522     static bool IsIntegerString(const char* str);
0523 
0524     static bool isdigit_(char c );
0525     static bool isalnum_(char c );
0526     static bool isupper_(char c );
0527     static bool islower_(char c );
0528 
0529 
0530     static void Summarize( std::vector<std::string>& smry_labels, const std::vector<std::string>* labels, int wid );
0531     static const char* Summarize( const char* label, int wid );
0532     static std::string Summarize_( const char* label, int wid );
0533 
0534 
0535     static void LineVector( std::vector<std::string>& lines, const char* LINES, const char* PREFIX=nullptr );
0536 
0537     static void LiteralTrim( std::string& line );
0538     static void Literal(    std::vector<std::string>& lines, const char* LINES );
0539     static void LiteralAnno( std::vector<std::string>& field, std::vector<std::string>& anno, const char* LINES, const char* delim="#" );
0540 
0541     static std::string Space(int wid);
0542 
0543 
0544 
0545     static std::string form_name(const char* stem, const char* ext);
0546     static std::string form_path(const char* dir, const char* name);
0547     static std::string form_path(const char* dir, const char* reldir, const char* name);
0548 
0549 
0550 
0551     static constexpr const char* DEFAULT_PATH_ARG_0 = "/tmp" ;
0552     template<typename ... Args>
0553     static std::string Path_( Args ... args_  );
0554 
0555     template<typename ... Args>
0556     static const char* Path( Args ... args );
0557 
0558 
0559     template<typename T>
0560     static inline void MakeVec(std::vector<T>& vec, const char* line, char delim=',');
0561 
0562     template<typename T>
0563     static std::vector<T>* MakeVec(const char* line, char delim=',');
0564 
0565     static void Zip(
0566         std::vector<std::string>& kvs,
0567         const std::vector<std::string>& keys,
0568         const std::vector<std::string>& vals,
0569         char delim=':');
0570 
0571 
0572     template<typename T>
0573     static long LoadVec(std::vector<T>& vec, const char* path_);
0574 
0575     template<typename T>
0576     static int Category(const std::vector<T>& cats, const T& val );
0577 
0578     static bool StartsWith( const char* s, const char* q) ;
0579     static bool Contains(   const char* s, const char* q) ;
0580 
0581     template<typename T> static unsigned NumSteps( T x0, T x1, T dx );
0582 
0583     template<typename T> static T To( const char* a );
0584     template<typename T> static bool ConvertsTo( const char* a );
0585 
0586     static char* PWD();
0587 
0588     template<typename T>
0589     static std::vector<T>* GetEnvVec(const char* ekey, const char* fallback, char delim=',');
0590     static int         GetEnvInt(  const char* envkey, int fallback );
0591 
0592     static size_t StringToSize(const std::string& str);
0593     static size_t GetEnvSize(const char* envkey, size_t fallback);
0594 
0595     static const char* GetEnv(    const char* envkey, const char* fallback);
0596     static bool        HasEnv( const char* envkey );
0597 
0598     template<typename T>
0599     static T           GetE(const char* ekey, T fallback);
0600 
0601     static int MakeDirs( const char* dirpath, int mode=0 );
0602     static int MakeDirsForFile( const char* filepath, int mode=0);
0603 
0604     static int PathType( const char* path );  // directory:1 file:2
0605     static int PathType( const char* base, const char* name );  // directory:1 file:2
0606 
0607     static void DirList(std::vector<std::string>& names, const char* _path,
0608                 const char* ext=nullptr, bool exclude=false, bool allow_nonexisting=false );
0609     static void Trim(std::vector<std::string>& names, const char* ext);
0610     static void Split(const char* str, char delim,   std::vector<std::string>& elem);
0611     static bool prefix_suffix( char** pfx, char** sfx, const char* start_sfx, const char* str );
0612 
0613     static int FindIndex(const std::vector<std::string>& names, const char* name);
0614 
0615     static std::string Desc(const std::vector<std::string>& names);
0616 
0617     static const char* Resolve0(const char* spec, const char* relp=nullptr );
0618     // $TOK/remainder/path.npy
0619 
0620     static const char* Resolve( const char* spec, const char* rel1=nullptr, const char* rel2=nullptr );
0621     // $TOK/remainder/$ANOTHER/path.npy
0622 
0623     static void        WriteString( const char* dir, const char* reldir, const char* name, const char* str );
0624     static void        WriteString( const char* dir, const char* name, const char* str );
0625     static void        WriteString( const char* path, const char* str );
0626 
0627     static const char* ReadString( const char* dir, const char* reldir, const char* name);
0628     static const char* ReadString( const char* dir, const char* name);
0629 
0630     static std::string ReadString_( const char* path_ );
0631     static const char* ReadString( const char* path );
0632 
0633     static std::string ReadString2_( const char* path_ );
0634     static const char* ReadString2( const char* path );
0635 
0636     static uint64_t Now();
0637     static bool LooksLikeStampInt(   const char* str);
0638     template<typename T>
0639     static bool LooksLikeTimestamp( T value );
0640 
0641     static bool LooksLikeProfileTriplet(const char* str);
0642 
0643     static std::string Format(uint64_t t=0, const char* fmt="%FT%T.", int _wsubsec=3 );
0644 
0645     static constexpr const char* LOG_FMT = "%Y-%m-%d %H:%M:%S" ;
0646     static std::string FormatLog(const char* msg=nullptr);
0647     static std::string Log(const char* msg=nullptr);
0648 
0649     static std::string FormatInt(int64_t t, int wid );
0650 
0651     static char* LastDigit(const char* str);
0652     static char* FirstDigit(const char* str);
0653     static char* FirstToLastDigit(const char* str);
0654 
0655 
0656     static void GetMetaKVS_(const char* metadata,    VS* keys, VS* vals, VT* stamps, bool only_with_stamp );
0657     static void GetMetaKVS( const std::string& meta, VS* keys, VS* vals, VT* stamps, bool only_with_stamp );
0658 
0659     static void KeyIndices( std::vector<int>& indices, const std::vector<std::string>& keys, const char* key );
0660     static int KeyIndex( const std::vector<std::string>& keys, const char* key );
0661     static int FormattedKeyIndex( std::string& fkey,  const std::vector<std::string>& keys, const char* key, int idx0, int idx1  );
0662 
0663     static void SplitTuple( std::vector<std::string>& keys, std::vector<int64_t>& tt, const std::vector<std::tuple<std::string,  int64_t>>& kt );
0664 };
0665 
0666 
0667 
0668 inline void U::sizeof_check() // static
0669 {
0670     assert( sizeof(float) == 4  );
0671     assert( sizeof(double) == 8  );
0672 
0673     assert( sizeof(char) == 1 );
0674     assert( sizeof(short) == 2 );
0675     assert( sizeof(int)   == 4 );
0676     assert( sizeof(long)  == 8 );
0677     assert( sizeof(long long)  == 8 );
0678 }
0679 
0680 
0681 template<typename T>
0682 inline void U::MakeVec(std::vector<T>& vec, const char* line, char delim)
0683 {
0684     if(line == nullptr) return ;
0685     std::stringstream ss;
0686     ss.str(line);
0687     std::string s;
0688     while (std::getline(ss, s, delim))
0689     {
0690         std::istringstream iss(s);
0691         T t ;
0692         iss >> t ;
0693         vec.push_back(t) ;
0694     }
0695 }
0696 
0697 inline void U::Zip(
0698     std::vector<std::string>& kvs,
0699     const std::vector<std::string>& keys,
0700     const std::vector<std::string>& vals,
0701     char delim )
0702 {
0703     assert( keys.size() == vals.size() );
0704     kvs.clear();
0705 
0706     char s_delim[2] ;
0707     s_delim[0] = delim ;
0708     s_delim[1] = '\0' ;
0709 
0710     for(int i=0 ; i < int(keys.size()) ; i++)
0711     {
0712         std::string kv = U::FormName_( keys[i].c_str(), s_delim, vals[i].c_str() );
0713         kvs.push_back(kv);
0714     }
0715 }
0716 
0717 
0718 
0719 /**
0720 U::MakeVec
0721 ------------
0722 
0723 If no elements are parsed from the line, nullptr is returned.
0724 
0725 **/
0726 
0727 template<typename T>
0728 inline std::vector<T>* U::MakeVec(const char* line, char delim)
0729 {
0730     if(line == nullptr) return nullptr ;
0731     std::vector<T> vec ;
0732     MakeVec(vec, line, delim) ;
0733     return vec.size() == 0 ? nullptr : new std::vector<T>(vec) ;
0734 }
0735 
0736 /**
0737 U::LoadVec
0738 ------------
0739 
0740 Load bytes from binary file into vector that is sized accordingly.
0741 The type is expected to be "char" or "unsigned char"
0742 
0743 HMM: does this belong in NPX.h ?
0744 
0745 **/
0746 template<typename T>
0747 inline long U::LoadVec(std::vector<T>& vec, const char* path_)
0748 {
0749     assert( sizeof(T) == 1 ) ;
0750 
0751     const char* path = U::Resolve(path_);
0752     FILE *fp = fopen(path,"rb");
0753 
0754     fseek(fp, 0L, SEEK_END);
0755     long file_size = ftell(fp);
0756     rewind(fp);
0757 
0758     vec.resize(file_size);
0759 
0760     long bytes_read = fread(vec.data(), sizeof(T), file_size, fp );
0761     fclose(fp);
0762     assert( file_size == bytes_read );
0763 
0764     return bytes_read ;
0765 }
0766 
0767 
0768 
0769 
0770 template<typename T>
0771 inline int U::Category(const std::vector<T>& cats, const T& val )
0772 {
0773     int cat = std::distance( cats.begin(), std::find(cats.begin(), cats.end(), val ) );
0774     if( cat == int(cats.size()) ) cat = -1 ;
0775     return cat ;
0776 }
0777 
0778 
0779 
0780 
0781 inline bool U::StartsWith( const char* s, const char* q) // static
0782 {
0783     return s && q && strlen(q) <= strlen(s) && strncmp(s, q, strlen(q)) == 0 ;
0784 }
0785 inline bool U::Contains( const char* s, const char* q) // static
0786 {
0787     return s && q && strlen(q) <= strlen(s) && strstr(s, q) != nullptr ;
0788 }
0789 
0790 
0791 
0792 template <typename T> unsigned U::NumSteps( T x0, T x1, T dx )
0793 {
0794     assert( x1 > x0 );
0795     assert( dx > T(0.) ) ;
0796 
0797     unsigned ns = 0 ;
0798     for(T x=x0 ; x <= x1 ; x+=dx ) ns+=1 ;
0799     return ns ;
0800 }
0801 
0802 
0803 template <typename T> inline T U::To( const char* a )   // static
0804 {
0805     std::string s(a);
0806     std::istringstream iss(s);
0807     T v ;
0808     iss >> v ;
0809     return v ;
0810 }
0811 
0812 // specialization for std::string as the above truncates at the first blank in the string, see tests/NP_set_meta_get_meta_test.cc
0813 template<> inline std::string U::To(const char* a )
0814 {
0815     std::string s(a);
0816     return s ;
0817 }
0818 
0819 
0820 
0821 
0822 
0823 template <typename T> inline bool U::ConvertsTo( const char* a )   // static
0824 {
0825     if( a == nullptr ) return false ;
0826     if( strlen(a) == 0) return false ;
0827     std::string s(a);
0828     std::istringstream iss(s);
0829     T v ;
0830     iss >> v ;
0831     return iss.fail() == false ;
0832 }
0833 
0834 
0835 inline char* U::PWD() // static
0836 {
0837     return getenv("PWD");
0838 }
0839 
0840 template<typename T>
0841 inline std::vector<T>* U::GetEnvVec(const char* ekey, const char* fallback, char delim)
0842 {
0843     char* line = getenv(ekey);
0844     return MakeVec<T>( line ? line : fallback, delim  ) ;
0845 }
0846 
0847 
0848 inline int U::GetEnvInt(const char* envkey, int fallback)
0849 {
0850     char* val = getenv(envkey);
0851     int ival = val ? std::atoi(val) : fallback ;
0852     return ival ;
0853 }
0854 
0855 
0856 inline size_t U::StringToSize(const std::string& str)
0857 {
0858     try
0859     {
0860         size_t pos;
0861         size_t result = std::stoul(str, &pos);
0862         if (pos != str.length()) throw std::invalid_argument("U::StringToSize - string contains invalid characters");
0863         return result;
0864     } catch (const std::invalid_argument&) {
0865         throw std::invalid_argument("U::StringToSize - Invalid input: not a valid number");
0866     } catch (const std::out_of_range&) {
0867         throw std::out_of_range("Number out of range for size_t");
0868     }
0869 }
0870 
0871 
0872 inline size_t U::GetEnvSize(const char* envkey, size_t fallback)
0873 {
0874     char* val = std::getenv(envkey);
0875     if (!val) return fallback;
0876     try
0877     {
0878         return StringToSize(val);
0879     }
0880     catch (const std::exception&)
0881     {
0882         return fallback; // Return fallback on invalid input
0883     }
0884 }
0885 
0886 
0887 inline const char* U::GetEnv(const char* envkey, const char* fallback)
0888 {
0889     const char* evalue = getenv(envkey);
0890     return evalue ? evalue : fallback ;
0891 }
0892 
0893 inline bool U::HasEnv(const char* envkey)
0894 {
0895     const char* evalue = getenv(envkey);
0896     return evalue ? true : false ;
0897 }
0898 
0899 
0900 template<typename T>
0901 inline T U::GetE(const char* ekey, T fallback)
0902 {
0903     char* v = getenv(ekey);
0904     if(v == nullptr) return fallback ;
0905 
0906     std::string s(v);
0907     std::istringstream iss(s);
0908     T t ;
0909     iss >> t ;
0910     return t ;
0911 }
0912 
0913 template int      U::GetE(const char*, int );
0914 template unsigned U::GetE(const char*, unsigned );
0915 template float    U::GetE(const char*, float );
0916 template double   U::GetE(const char*, double );
0917 template char     U::GetE(const char*, char );
0918 template unsigned char U::GetE(const char*, unsigned char );
0919 
0920 
0921 
0922 
0923 
0924 
0925 
0926 inline bool U::EndsWith( const char* s, const char* q)
0927 {
0928     int pos = strlen(s) - strlen(q) ;
0929     return pos > 0 && strncmp(s + pos, q, strlen(q)) == 0 ;
0930 }
0931 
0932 inline std::string U::ChangeExt( const char* s, const char* x1, const char* x2)
0933 {
0934     assert( EndsWith(s, x1) );
0935 
0936     std::string st = s ;
0937     std::stringstream ss ;
0938 
0939     ss << st.substr(0, strlen(s) - strlen(x1) ) ;
0940     ss << x2 ;
0941     return ss.str() ;
0942 }
0943 
0944 inline std::string U::DirName( const char* path )
0945 {
0946     std::string p = path ;
0947     std::size_t pos = p.find_last_of("/") ;
0948     return pos == std::string::npos ? "" : p.substr(0, pos);
0949 }
0950 
0951 inline std::string U::BaseName( const char* path )
0952 {
0953     std::string p = path ;
0954     std::size_t pos = p.find_last_of("/") ;
0955     return pos == std::string::npos ? "" : p.substr(pos+1);
0956 }
0957 inline const char* U::BaseName_( const char* path )
0958 {
0959     std::string name = BaseName(path);
0960     return strdup(name.c_str());
0961 }
0962 
0963 
0964 /**
0965 U::BaseName_NoSepAsis
0966 -----------------------
0967 
0968 Returns the basename of a path, when there is no separator
0969 returns the path asis. For example::
0970 
0971    U::BaseName_NoSepAsis("/some/directory/path/name.txt") -> "name.txt"
0972    U::BaseName("/some/directory/path/name.txt") -> name.txt
0973 
0974    U::BaseName_NoSepAsis("name.txt") -> "name.txt"
0975    U::BaseName("name.txt") -> ""
0976 
0977 **/
0978 
0979 inline std::string U::BaseName_NoSepAsis( const char* path )
0980 {
0981     std::string p = path ;
0982     std::size_t pos = p.find_last_of("/") ;
0983     return pos == std::string::npos ? p : p.substr(pos+1);
0984 }
0985 inline const char* U::BaseName_NoSepAsis_( const char* path )
0986 {
0987     std::string name = BaseName_NoSepAsis(path);
0988     return strdup(name.c_str());
0989 }
0990 
0991 
0992 
0993 /**
0994 U::FormSiblingPath0
0995 ---------------------
0996 
0997 For example::
0998 
0999    sibname : sreport
1000    dirpath : /data/blyth/opticks/GEOM/J23_1_0_rc3_ok0/CSGOptiXSMTest/ALL0
1001    returns : /data/blyth/opticks/GEOM/J23_1_0_rc3_ok0/CSGOptiXSMTest/sreport
1002 
1003 **/
1004 
1005 inline std::string U::FormSiblingPath0( const char* sibname , const char* dirpath )
1006 {
1007     std::stringstream ss ;
1008     std::string container = DirName(dirpath) ;
1009     ss << container << "/" << sibname ;
1010     std::string str = ss.str();
1011     return str ;
1012 }
1013 
1014 
1015 /**
1016 U::FormSiblingPath
1017 ---------------------
1018 
1019 For example::
1020 
1021    sibname : sreport
1022    dirpath : /data/blyth/opticks/GEOM/J23_1_0_rc3_ok0/CSGOptiXSMTest/ALL0
1023    returns : /data/blyth/opticks/GEOM/J23_1_0_rc3_ok0/CSGOptiXSMTest/ALL0_sreport
1024 
1025 **/
1026 
1027 inline std::string U::FormSiblingPath( const char* sibname , const char* dirpath )
1028 {
1029     std::stringstream ss ;
1030     ss << dirpath << "_" << sibname ;
1031     std::string str = ss.str();
1032     return str ;
1033 }
1034 
1035 
1036 inline std::string U::FormExecutableSiblingPath( const char* argv0 , const char* dirpath )
1037 {
1038     const char* exename = BaseName_NoSepAsis_(argv0) ;
1039     std::string sibpath = FormSiblingPath( exename, dirpath );
1040 
1041     if(VERBOSE) std::cout
1042         << "[U::FormExecutableSiblingPath"
1043         << std::endl
1044         << " argv0 " << ( argv0 ? argv0 : "-" )
1045         << std::endl
1046         << " dirpath " << ( dirpath ? dirpath : "-" )
1047         << std::endl
1048         << " exename " << ( exename ? exename : "-" )
1049         << std::endl
1050         << " sibpath " << sibpath
1051         << std::endl
1052         << "]U::FormExecutableSiblingPath"
1053         << std::endl
1054         ;
1055 
1056     return sibpath ;
1057 }
1058 
1059 /**
1060 U::IsExecutableSiblingPath
1061 ----------------------------
1062 
1063 For argv0 of sreport OR /some/path/to/sreport dirpath the
1064 exepected results are:
1065 
1066 * /some/director/tree/leading/to/ALL0_sreport   YES
1067 * /some/director/tree/leading/to/sreport        NO
1068 
1069 * ALL0_sreport   YES
1070 * sreport        NO
1071 
1072 **/
1073 
1074 inline bool U::IsExecutableSiblingPath(const char* argv0,  const char* dirpath )  // static
1075 {
1076     const char* exename = BaseName_NoSepAsis_(argv0) ;
1077     const char* basename = BaseName_NoSepAsis_(dirpath) ;
1078     bool is_executable_sibling_path = basename && exename && EndsWith(basename, exename) && strlen(basename) > strlen(exename) ;
1079 
1080     if(VERBOSE) std::cout
1081         << "[U::IsExecutableSiblingPath"
1082         << std::endl
1083         << " argv0 " << ( argv0 ? argv0 : "-" )
1084         << std::endl
1085         << " dirpath " << ( dirpath ? dirpath : "-" )
1086         << std::endl
1087         << " exename " << ( exename ? exename : "-" )
1088         << std::endl
1089         << " basename " << ( basename ? basename : "-" )
1090         << std::endl
1091         << " is_executable_sibling_path " << ( is_executable_sibling_path ? "YES" : "NO " )
1092         << "]U::IsExecutableSiblingPath"
1093         << std::endl
1094         ;
1095 
1096     return is_executable_sibling_path ;
1097 }
1098 
1099 inline int U::SetEnvDefaultExecutableSiblingPath(const char* ekey, char* argv0, const char* dirpath )
1100 {
1101     std::string _sibfold = FormExecutableSiblingPath(argv0, dirpath);
1102     const char* sibfold = _sibfold.empty() ? nullptr : _sibfold.c_str() ;
1103     bool overwrite = false ;
1104     int rc = setenvvar( ekey, sibfold, overwrite );
1105 
1106     if(VERBOSE) std::cout
1107         << "[U::SetEnvDefaultExecutableSiblingPath"
1108         << std::endl
1109         << " ekey " << ( ekey ? ekey : "-" )
1110         << std::endl
1111         << " argv0 " << ( argv0 ? argv0 : "-" )
1112         << std::endl
1113         << " ditpath " << ( dirpath ? dirpath : "-" )
1114         << std::endl
1115         << " sibfold " << ( sibfold ? sibfold : "-" )
1116         << std::endl
1117         << " rc " << rc
1118         << std::endl
1119         << "]U::SetEnvDefaultExecutableSiblingPath"
1120         << std::endl
1121         ;
1122 
1123     return rc ;
1124 }
1125 
1126 
1127 
1128 
1129 /**
1130 U::setenvvar (similar to opticks/sysrap/ssys.h ssys::setenvvar)
1131 -----------------------------------------------------------------
1132 
1133 overwrite:false
1134     preexisting envvar is not overridden.
1135 
1136 As shell handling of empty strings is inconvenient the special_empty_token char
1137 allows a single char to represent the empty string, eg '-'
1138 
1139 **/
1140 
1141 inline int U::setenvvar( const char* ekey, const char* value, bool overwrite, char special_empty_token)
1142 {
1143     std::stringstream ss ;
1144     ss << ekey << "=" ;
1145 
1146     if(value)
1147     {
1148         if(special_empty_token != '\0' && strlen(value) == 1 && value[0] == special_empty_token)
1149         {
1150             ss << "" ;
1151         }
1152         else
1153         {
1154             ss << value ;
1155         }
1156     }
1157 
1158     std::string ekv = ss.str();
1159     const char* prior = getenv(ekey) ;
1160 
1161     char* ekv_ = const_cast<char*>(strdup(ekv.c_str()));
1162 
1163     int rc = ( overwrite || !prior ) ? putenv(ekv_) : 0  ;
1164 
1165     const char* after = getenv(ekey) ;
1166 
1167     if(VERBOSE) std::cerr
1168         << "U::setenvvar"
1169         << " ekey " << ekey
1170         << " ekv " << ekv
1171         << " overwrite " << overwrite
1172         << " prior " << ( prior ? prior : "NULL" )
1173         << " value " << ( value ? value : "NULL" )
1174         << " after " << ( after ? after : "NULL" )
1175         << " rc " << rc
1176         << std::endl
1177         ;
1178 
1179     return rc ;
1180 }
1181 
1182 
1183 
1184 
1185 
1186 
1187 
1188 
1189 template<typename ... Args>
1190 inline std::string U::Format_( const char* fmt, Args ... args )
1191 {
1192     int sz = std::snprintf( nullptr, 0, fmt, args ... ) + 1 ; // +1 for null termination
1193     assert( sz > 0 );
1194     std::vector<char> buf(sz) ;
1195     std::snprintf( buf.data(), sz, fmt, args ... );
1196     return std::string( buf.begin(), buf.begin() + sz - 1 );  // exclude null termination
1197 }
1198 
1199 template std::string U::Format_( const char*, const char*, int, int );
1200 template std::string U::Format_( const char*, int );
1201 template std::string U::Format_( const char*, unsigned long long );
1202 
1203 
1204 template<typename ... Args>
1205 inline const char* U::Format( const char* fmt, Args ... args )
1206 {
1207     std::string str = Format_(fmt, std::forward<Args>(args)... );
1208     return strdup(str.c_str());
1209 }
1210 
1211 template const char* U::Format( const char*, const char*, int, int );
1212 template const char* U::Format( const char*, int  );
1213 template const char* U::Format( const char*, unsigned long long  );
1214 
1215 
1216 
1217 
1218 
1219 
1220 inline std::string U::FormNameWithPrefix_( char prefix, int idx, int wid )
1221 {
1222     std::stringstream ss ;
1223     ss << prefix << std::setfill('0') << std::setw(wid) << idx ;
1224     std::string s = ss.str();
1225     return s ;
1226 }
1227 
1228 inline const char* U::FormNameWithPrefix( char prefix, int idx, int wid )
1229 {
1230     std::string str = FormNameWithPrefix_(prefix, idx, wid) ;
1231     return strdup(str.c_str());
1232 }
1233 
1234 inline std::string U::FormName_( int idx, int wid )
1235 {
1236     std::stringstream ss ;
1237     ss << std::setfill('0') << std::setw(wid) << idx ;
1238     std::string str = ss.str();
1239     return str ;
1240 }
1241 inline const char* U::FormName( int idx, int wid )
1242 {
1243     std::string str = FormName_(idx, wid) ;
1244     return strdup(str.c_str());
1245 }
1246 
1247 
1248 inline std::string U::FormName_( const char* prefix, int idx, const char* ext, int wid )
1249 {
1250     std::stringstream ss ;
1251     if(prefix) ss << prefix ;
1252 
1253     if( wid > 0 )
1254     {
1255         ss << std::setfill('0') << std::setw(wid) << idx ;
1256     }
1257     else
1258     {
1259         ss << idx ;
1260     }
1261 
1262     if(ext) ss << ext ;
1263     std::string s = ss.str();
1264     return s ;
1265 }
1266 inline const char* U::FormName( const char* prefix, int idx, const char* ext, int wid )
1267 {
1268     std::string name = FormName_(prefix, idx, ext, wid );
1269     return strdup(name.c_str());
1270 }
1271 
1272 
1273 
1274 
1275 
1276 inline std::string U::FormName_( const char* prefix, const char* body, const char* ext )
1277 {
1278     std::stringstream ss ;
1279     if(prefix) ss << prefix ;
1280     if(body) ss << body ;
1281     if(ext) ss << ext ;
1282     std::string s = ss.str();
1283     return s ;
1284 }
1285 inline const char* U::FormName( const char* prefix, const char* body, const char* ext )
1286 {
1287     std::string name = FormName_(prefix, body, ext );
1288     return strdup(name.c_str());
1289 }
1290 
1291 
1292 inline bool U::IsIntegerString(const char* str)
1293 {
1294     if(!str) return false ;
1295     if(strlen(str)==0) return false ;
1296 
1297     std::string s(str);
1298     return s.find_first_not_of("0123456789") == std::string::npos ;
1299 }
1300 
1301 
1302 
1303 // cctype
1304 
1305 inline bool U::isdigit_(char c ) { return std::isdigit(static_cast<unsigned char>(c)) ; }
1306 inline bool U::isalnum_(char c ) { return std::isalnum(static_cast<unsigned char>(c)) ; }
1307 inline bool U::isupper_(char c ) { return std::isupper(static_cast<unsigned char>(c)) ; }
1308 inline bool U::islower_(char c ) { return std::islower(static_cast<unsigned char>(c)) ; }
1309 
1310 
1311 inline void U::Summarize( std::vector<std::string>& smry_labels, const std::vector<std::string>* labels, int wid )
1312 {
1313     int num_labels = labels ? labels->size() : 0 ;
1314     for(int i=0 ; i < num_labels ; i++)
1315     {
1316         const char* label = (*labels)[i].c_str() ;
1317         smry_labels.push_back( U::Summarize(label, wid) ) ;
1318     }
1319 }
1320 
1321 /**
1322 U::Summarize
1323 ---------------
1324 
1325 Shorten stamp labels via heuristics of distinctive chars
1326 
1327 C++ version of npmeta.py NPMeta::Summarize
1328 
1329 
1330 * always take first char
1331 * alnum after _
1332 * upper char following lower
1333 * accept r or o after P to distinguish Pre and Post
1334 
1335 **/
1336 
1337 inline const char* U::Summarize( const char* label, int wid )  // static
1338 {
1339     if(label == nullptr) return nullptr ;
1340     std::string smry = U::Summarize_(label, wid);
1341     char* _smry = const_cast<char*>(smry.c_str()) ;
1342     int len = strlen(_smry) ;
1343     if(len > wid) _smry[wid+1] = '\0' ;
1344     return strdup(_smry) ;
1345 }
1346 inline std::string U::Summarize_( const char* label, int wid )  // static
1347 {
1348     int len = strlen(label) ;
1349     std::string str ;
1350     if( len <= wid )
1351     {
1352         str = label ;
1353     }
1354     else
1355     {
1356         std::stringstream ss ;
1357         char p = '\0' ;
1358         for(int i=0 ; i < len ; i++)
1359         {
1360            char c = label[i] ;
1361            bool take =  ( p == '\0' )
1362                      || ( isalnum_(c) && p == '_' )
1363                      || ( isalnum_(c) && p == '_' )
1364                      || ( isupper_(c) && islower_(p) )
1365                      || ( p == 'P' && ( c == 'r' || c == 'o' ) )
1366                      ;
1367            p = c ;
1368            if(take) ss << c ;
1369         }
1370         str = ss.str();
1371     }
1372     return str ;
1373 }
1374 
1375 
1376 /**
1377 U::LineVector
1378 --------------
1379 
1380 
1381 **/
1382 
1383 inline void U::LineVector( std::vector<std::string>& lines, const char* LINES, const char* PREFIX )
1384 {
1385     std::stringstream fss(LINES);
1386     std::string _line ;
1387     while(getline(fss, _line))
1388     {
1389         const char* line = _line.c_str();
1390         size_t len = strlen(line) ;
1391         if(len==0) continue ;
1392         bool match = PREFIX == nullptr ? true : len >= strlen(PREFIX) && strncmp(line, PREFIX, strlen(PREFIX)) == 0 ;
1393         if(!match) continue ;
1394         lines.push_back(line);
1395     }
1396 }
1397 
1398 
1399 
1400 
1401 
1402 inline void U::LiteralTrim( std::string& line )
1403 {
1404     const char* trim = " " ;
1405     if(strlen(line.c_str())==0) return ;
1406     line.erase(0, line.find_first_not_of(trim));  // left trim
1407     line.erase(line.find_last_not_of(trim) + 1);   // right trim
1408 }
1409 
1410 inline void U::Literal( std::vector<std::string>& lines, const char* LINES )
1411 {
1412     std::stringstream fss(LINES);
1413     std::string line ;
1414     while(getline(fss, line))
1415     {
1416         LiteralTrim(line);
1417         if(line.size() == 0) continue ;
1418         lines.push_back(line);
1419     }
1420 }
1421 
1422 inline void U::LiteralAnno( std::vector<std::string>& field, std::vector<std::string>& anno, const char* LINES, const char* delim  )
1423 {
1424     std::vector<std::string> lines ;
1425     U::Literal(lines, LINES );
1426     int num_lines = lines.size();
1427     bool dump = false ;
1428 
1429     if(dump) std::cout << "U::LiteralAnno num_lines " << num_lines << std::endl ;
1430 
1431     for(int i=0 ; i < num_lines ; i++)
1432     {
1433         const std::string& line = lines[i] ;
1434         std::size_t pfirst = line.find_first_of(delim) ; // first char matching any of delim char
1435         std::size_t plast =  line.find_last_of(delim) ;  // last char matching any of delim char
1436 
1437         if(dump) std::cout
1438             << " line [" << line << "]"
1439             << " pfirst " << pfirst
1440             << " plast " << plast
1441             << std::endl
1442             ;
1443 
1444         if( pfirst != std::string::npos && plast != std::string::npos && plast - pfirst == 1 )
1445         {
1446             std::string _field = line.substr(0, pfirst) ;
1447             std::string _anno  = line.substr(plast+1) ;
1448             LiteralTrim(_field);
1449             LiteralTrim(_anno);
1450             field.push_back( _field );
1451             anno.push_back( _anno );
1452         }
1453         else
1454         {
1455             field.push_back(line);
1456             anno.push_back("");
1457         }
1458     }
1459 }
1460 
1461 
1462 
1463 
1464 
1465 inline std::string U::Space(int wid)
1466 {
1467     std::stringstream ss ;
1468     for(int i=0 ; i < wid ; i++) ss << " " ;
1469     std::string str = ss.str();
1470     return str ;
1471 }
1472 
1473 
1474 
1475 inline std::string U::form_name(const char* stem, const char* ext)
1476 {
1477     std::stringstream ss ;
1478     ss << stem ;
1479     ss << ext ;
1480     return ss.str();
1481 }
1482 inline std::string U::form_path(const char* dir, const char* name)
1483 {
1484     std::stringstream ss ;
1485     ss << dir ;
1486     if(name) ss << "/" << name ;
1487     return ss.str();
1488 }
1489 
1490 inline std::string U::form_path(const char* dir, const char* reldir, const char* name)
1491 {
1492     std::stringstream ss ;
1493     ss << dir ;
1494     if(reldir) ss << "/" << reldir ;
1495     if(name) ss << "/" << name ;
1496     return ss.str();
1497 }
1498 
1499 
1500 
1501 
1502 
1503 
1504 
1505 template<typename ... Args>
1506 std::string U::Path_( Args ... args_  )
1507 {
1508     std::vector<const char*> args = {args_...} ;
1509 
1510     std::vector<std::string> elem ;
1511     for(unsigned i=0 ; i < args.size() ; i++)
1512     {
1513         const char* arg = args[i] ;
1514         if( i == 0 && arg == nullptr ) arg = DEFAULT_PATH_ARG_0 ;
1515         if(arg) elem.push_back(arg);
1516     }
1517 
1518     unsigned num_elem = elem.size() ;
1519     std::stringstream ss ;
1520     for(unsigned i=0 ; i < num_elem ; i++)
1521     {
1522         const std::string& ele = elem[i] ;
1523         ss << ele << ( i < num_elem - 1 ? "/" : "" ) ;
1524     }
1525     std::string s = ss.str();
1526 
1527     return s ;
1528 }
1529 
1530 template std::string U::Path_( const char*, const char* );
1531 template std::string U::Path_( const char*, const char*, const char* );
1532 template std::string U::Path_( const char*, const char*, const char*, const char* );
1533 
1534 template<typename ... Args>
1535 const char* U::Path( Args ... args )
1536 {
1537     std::string s = Path_(args...)  ;
1538     return strdup(s.c_str()) ;
1539 }
1540 
1541 template const char* U::Path( const char*, const char* );
1542 template const char* U::Path( const char*, const char*, const char* );
1543 template const char* U::Path( const char*, const char*, const char*, const char* );
1544 
1545 
1546 
1547 
1548 
1549 
1550 
1551 #include <cstring>
1552 #include <cstdlib>
1553 #include <cstdio>
1554 #include <sys/stat.h>
1555 #include <errno.h>
1556 #include "dirent.h"
1557 
1558 inline int U::MakeDirs( const char* dirpath_, int mode_ )
1559 {
1560     mode_t default_mode = S_IRWXU | S_IRGRP |  S_IXGRP | S_IROTH | S_IXOTH ;
1561     mode_t mode = mode_ == 0 ? default_mode : mode_ ;
1562 
1563     char* dirpath = strdup(dirpath_);
1564     char* p = dirpath ;
1565     int rc = 0 ;
1566 
1567     while (*p != '\0' && rc == 0)
1568     {
1569         p++;                                 // advance past leading character, probably slash, and subsequent slashes the next line gets to
1570         while(*p != '\0' && *p != '/') p++;  // advance p until subsequent slash
1571         char v = *p;                         // store the slash
1572         *p = '\0' ;                          // replace slash with string terminator
1573         //printf("%s\n", path );
1574         rc = mkdir(dirpath, mode) == -1 && errno != EEXIST ? 1 : 0 ;  // set rc non-zero for mkdir errors other than exists already
1575         *p = v;                              // put back the slash
1576     }
1577     free(dirpath);
1578     return rc ;
1579 }
1580 
1581 inline int U::MakeDirsForFile( const char* filepath, int mode_ )
1582 {
1583     if(filepath == nullptr) return 1 ;
1584     std::string dirpath = U::DirName(filepath);
1585     return MakeDirs(dirpath.c_str(), mode_ );
1586 }
1587 
1588 inline int U::PathType( const char* base, const char* name )
1589 {
1590     const char* path = Path(base, name);
1591     return PathType(path) ;
1592 }
1593 inline int U::PathType( const char* path )
1594 {
1595     int rc = ERROR_PATH ;
1596     struct stat st ;
1597     if(0 == stat(path, &st))
1598     {
1599         if(     S_ISDIR(st.st_mode)) rc = DIR_PATH ;
1600         else if(S_ISREG(st.st_mode)) rc = FILE_PATH ;
1601         else                         rc = OTHER_PATH ;
1602     }
1603     return rc ;
1604 }
1605 
1606 /**
1607 U::DirList
1608 -----------
1609 
1610 ext:nullptr
1611     (default) matches all extensions
1612 
1613 
1614 
1615 
1616 **/
1617 
1618 inline void U::DirList(
1619     std::vector<std::string>& names,
1620     const char* _path,
1621     const char* ext,
1622     bool exclude,
1623     bool allow_nonexisting
1624    )
1625 {
1626     const char* path = Resolve(_path);
1627 
1628     DIR* dir = opendir(path) ;
1629     if(!dir && allow_nonexisting) return ;
1630     if(!dir) std::cout << "U::DirList FAILED TO OPEN DIR " << ( path ? path : "-" ) << std::endl ;
1631     if(!dir && RAISE) std::raise(SIGINT) ;
1632     if(!dir) return ;
1633     struct dirent* entry ;
1634     while ((entry = readdir(dir)) != nullptr)
1635     {
1636         const char* name = entry->d_name ;
1637         bool dot_name = strcmp(name,".") == 0 || strcmp(name,"..") == 0 ;
1638         if(dot_name) continue ;
1639 
1640         bool ext_match = ext == nullptr ? true : ( strlen(name) > strlen(ext) && strcmp(name + strlen(name) - strlen(ext), ext)==0)  ;
1641         if(ext_match == true  && exclude == false) names.push_back(name);
1642         if(ext_match == false && exclude == true)  names.push_back(name);
1643     }
1644     closedir (dir);
1645     std::sort( names.begin(), names.end() );
1646 
1647     if(names.size() == 0 ) std::cout
1648         << "U::DirList"
1649         << " path " << ( path ? path : "-" )
1650         << " ext " << ( ext ? ext : "-" )
1651         << " NO ENTRIES FOUND "
1652         << " exclude " << exclude
1653         << std::endl
1654         ;
1655 }
1656 
1657 inline void U::Trim(std::vector<std::string>& names, const char* ext)
1658 {
1659     for(int i=0 ; i < int(names.size()) ; i++)
1660     {
1661         std::string& name = names[i];
1662         const char* n = name.c_str();
1663         bool ends_with_ext =  strlen(n) > strlen(ext)  && strncmp(n + strlen(n) - strlen(ext), ext, strlen(ext) ) == 0 ;
1664         if(!ends_with_ext) std::cerr << "U::Trim NOT ends_with_ext " << std::endl ;
1665         assert( ends_with_ext );
1666         name = name.substr(0, strlen(n) - strlen(ext));
1667     }
1668 }
1669 
1670 inline void U::Split(const char* str, char delim,   std::vector<std::string>& elem)
1671 {
1672     std::stringstream ss;
1673     ss.str(str)  ;
1674     std::string s;
1675     while (std::getline(ss, s, delim)) elem.push_back(s) ;
1676 }
1677 
1678 
1679 
1680 
1681 
1682 /**
1683 U::prefix_suffix (after sstr::prefix_suffix)
1684 ---------------------------------------------
1685 
1686 Splits *str* into *pfx* and *sfx* where *sfx* begins with the *start_sfx* argument
1687 returning true when the suffix is found. For example with *start_sfx* "["::
1688 
1689     str: /tmp/w54.npy[0:1]
1690     pfx: /tmp/w54.npy
1691     sfx: [0:1]
1692 
1693 A string starting with *start_sfx* is not regarded as a suffix, causing false
1694 to be returned.
1695 
1696 **/
1697 
1698 inline bool U::prefix_suffix( char** pfx, char** sfx, const char* start_sfx, const char* str )
1699 {
1700     if(!str || !start_sfx) return false ;
1701 
1702     *pfx = strdup(str);
1703     char* p = strstr(*pfx, start_sfx);
1704 
1705     bool has_suffix = p && (p > *pfx) ;
1706     if(has_suffix)
1707     {
1708         *sfx = strdup(p);
1709         p[0] = '\0' ; // terminate pfx at position of start_sfx
1710     }
1711    return has_suffix ;
1712 }
1713 
1714 
1715 
1716 inline int U::FindIndex(const std::vector<std::string>& names, const char* name) // static
1717 {
1718     size_t idx = std::distance( names.begin(), std::find( names.begin(), names.end(), name ));
1719     return idx >= names.size() ? -1  : int(idx) ;
1720 }
1721 
1722 
1723 
1724 
1725 
1726 inline std::string U::Desc(const std::vector<std::string>& names)
1727 {
1728     std::stringstream ss ;
1729     for(unsigned i=0 ; i < names.size() ; i++) ss << "[" << names[i] << "]" << std::endl ;
1730     std::string s = ss.str();
1731     return s ;
1732 }
1733 
1734 
1735 /**
1736 U::Resolve0 : Old impl that only replaces tokens in first path element
1737 -----------------------------------------------------------------------
1738 
1739 ::
1740 
1741     $TOKEN/remainder/path/name.npy   (tok_plus)
1742     $TOKEN
1743 
1744 If the TOKEN envvar is not set then nullptr is returned.
1745 
1746 **/
1747 
1748 inline const char* U::Resolve0(const char* spec_, const char* relp_)
1749 {
1750     if(spec_ == nullptr) return nullptr ;
1751 
1752     std::string spec_relp = form_path(spec_, relp_);
1753     char* spec = strdup(spec_relp.c_str()) ;
1754 
1755     std::stringstream ss ;
1756     if( spec[0] == '$' )
1757     {
1758         char* sep = strchr(spec, '/');       // point to first slash
1759         char* end = strchr(spec, '\0' );
1760         bool tok_plus =  sep && end && sep != end ;
1761         if(tok_plus) *sep = '\0' ;           // replace slash with null termination
1762         char* pfx = getenv(spec+1) ;
1763         if(pfx == nullptr) return nullptr ;
1764         if(tok_plus) *sep = '/' ;            // put back the slash
1765         ss << pfx  << ( sep ? sep : "" ) ;
1766     }
1767     else
1768     {
1769         ss << spec ;
1770     }
1771 
1772     std::string str = ss.str();
1773     const char* path = str.c_str();
1774     return strdup(path) ;
1775 }
1776 
1777 /**
1778 U::Resolve
1779 ------------
1780 
1781 NB similar to sysrap/spath.h spath::_ResolvePath
1782 
1783 This resolves spec with multiple tokens, eg::
1784 
1785     $HOME/.opticks/GEOM/$GEOM/CSGFoundry/SSim/jpmt/PMTSimParamData/MPT
1786 
1787 Any unresolved token causes nullptr to be returned.
1788 
1789 TODO support ".." to go up a level eg::
1790 
1791     $FOLD/../other/a.npy
1792 
1793 
1794 **/
1795 
1796 inline const char* U::Resolve(const char* spec_, const char* rel1_, const char* rel2_ )
1797 {
1798     if(spec_ == nullptr) return nullptr ;
1799     std::string spec_relp = form_path(spec_, rel1_, rel2_ );
1800     char* spec = strdup(spec_relp.c_str()) ;
1801 
1802     std::stringstream ss ;
1803     int speclen = int(strlen(spec)) ;
1804     char* end = strchr(spec, '\0' );
1805     int i = 0 ;
1806 
1807     if(VERBOSE) std::cout << " spec " << spec << " speclen " << speclen << std::endl ;
1808 
1809     while( i < speclen )
1810     {
1811         if(VERBOSE) std::cout << " i " << i << " spec[i] " << spec[i] << std::endl ;
1812         if( spec[i] == '$' )
1813         {
1814             char* p = spec + i ;
1815             char* sep = strchr( p, '/' ) ; // first slash after token
1816             bool tok_plus =  sep && end && sep != end ;
1817             if(tok_plus) *sep = '\0' ;           // replace slash with null termination
1818             char* val = getenv(p+1) ;  // skip '$'
1819             int toklen = int(strlen(p)) ;  // strlen("TOKEN")  no need for +1 as already at '$'
1820             if(VERBOSE) std::cout << " toklen " << toklen << std::endl ;
1821             if(val == nullptr)
1822             {
1823                 std::cerr
1824                     << "U::Resolve token ["
1825                     << p+1
1826                     << "] does not resolve "
1827                     << std::endl
1828                     ;
1829                 return nullptr ;    // all tokens must resolve
1830             }
1831             if(tok_plus) *sep = '/' ;            // put back the slash
1832             ss << val  ;
1833 
1834             i += toklen ;   // skip over the token
1835         }
1836         else
1837         {
1838            ss << spec[i] ;
1839            i += 1 ;
1840         }
1841     }
1842     std::string str = ss.str();
1843     const char* path = str.c_str();
1844     return strdup(path) ;
1845 }
1846 
1847 
1848 
1849 
1850 
1851 
1852 
1853 
1854 
1855 inline void U::WriteString( const char* dir, const char* reldir, const char* name, const char* str )  // static
1856 {
1857     std::string path = form_path(dir, reldir, name);
1858     WriteString(path.c_str(), str);
1859 }
1860 
1861 inline void U::WriteString( const char* dir, const char* name, const char* str )  // static
1862 {
1863     std::string path = form_path(dir, name);
1864     WriteString(path.c_str(), str);
1865 }
1866 
1867 inline void U::WriteString( const char* path, const char* str )  // static
1868 {
1869     if(str == nullptr) return ;
1870 
1871     MakeDirsForFile(path);
1872 
1873     std::ofstream fp(path, std::ios::out);
1874     fp << str ;
1875     fp.close();
1876 }
1877 
1878 
1879 inline const char* U::ReadString( const char* dir, const char* reldir, const char* name) // static
1880 {
1881     std::string path = form_path(dir, reldir, name);
1882     return ReadString(path.c_str());
1883 }
1884 
1885 inline const char* U::ReadString( const char* dir, const char* name) // static
1886 {
1887     std::string path = form_path(dir, name);
1888     return ReadString(path.c_str());
1889 }
1890 
1891 
1892 inline std::string U::ReadString_( const char* path_ )  // static
1893 {
1894     const char* path = Resolve(path_);
1895     std::vector<std::string> lines ;
1896     std::string line ;
1897     std::ifstream ifs(path);
1898     while(std::getline(ifs, line)) lines.push_back(line);
1899     unsigned num_lines = lines.size();
1900     std::stringstream ss ;
1901     for(unsigned i=0 ; i < num_lines ; i++)
1902     {
1903         ss << lines[i] ;
1904         if( i < num_lines - 1 ) ss << std::endl ;
1905     }
1906     std::string str = ss.str();
1907     return str ;
1908 }
1909 
1910 inline const char* U::ReadString( const char* path_ )  // static
1911 {
1912     std::string str = ReadString_(path_);
1913     return str.empty() ? nullptr : strdup(str.c_str()) ;
1914 }
1915 
1916 
1917 inline std::string U::ReadString2_(const char* path_)  // static
1918 {
1919     const char* path = Resolve(path_);
1920     std::ifstream ifs(path);
1921     std::stringstream ss ;
1922     ss << ifs.rdbuf();
1923     std::string str = ss.str();
1924     return str ;
1925 }
1926 
1927 inline const char* U::ReadString2(const char* path_)  // static
1928 {
1929     std::string str = ReadString2_(path_);
1930     return str.empty() ? nullptr : strdup(str.c_str()) ;
1931 }
1932 
1933 
1934 inline uint64_t U::Now() // static
1935 {
1936     // from opticks/sysrap/sstamp.h
1937     using Clock = std::chrono::system_clock;
1938     using Unit  = std::chrono::microseconds ;
1939     std::chrono::time_point<Clock> t0 = Clock::now();
1940     return std::chrono::duration_cast<Unit>(t0.time_since_epoch()).count() ;
1941 }
1942 
1943 /**
1944 U::LooksLikeStampInt
1945 ----------------------
1946 
1947 Contemporary microsecond uint64_t timestamps look like below with 16 digits::
1948 
1949     1700224486350245
1950 
1951 ::
1952 
1953     In [20]: np.c_[np.array([0,int(1e15),1700224486350245,int(1e16),int(0x7ffffffffffffff) ]).view("datetime64[us]")]
1954     Out[20]:
1955     array([[ '1970-01-01T00:00:00.000000'],
1956            [ '2001-09-09T01:46:40.000000'],
1957            [ '2023-11-17T12:34:46.350245'],
1958            [ '2286-11-20T17:46:40.000000'],
1959            ['20237-04-25T10:45:03.423487']], dtype='datetime64[us]')
1960 
1961 **/
1962 
1963 inline bool U::LooksLikeStampInt(const char* str) // static
1964 {
1965     int length = strlen(str) ;
1966     int digits = 0 ;
1967     for(int i=0 ; i < length ; i++) if(str[i] >= '0' && str[i] <= '9') digits += 1 ;
1968     return length == 16 && digits == length  ;
1969 }
1970 
1971 
1972 template<typename T>
1973 inline bool U::LooksLikeTimestamp( T value )
1974 {
1975     return sizeof(T) == 8 && value > 1700000000000000 ;
1976 }
1977 
1978 
1979 /**
1980 U::LooksLikeProfileTriplet
1981 -----------------------------
1982 
1983 Follows sprof::LooksLikeProf, repeated hear for convenience.
1984 Returns true for comma delimited list of three integers where
1985 the first has 16 digits, eg::
1986 
1987     1111111111111111,2222,3333
1988 
1989 **/
1990 
1991 inline bool U::LooksLikeProfileTriplet(const char* str) // static
1992 {
1993     int len = str ? int(strlen(str)) : 0 ;
1994     int count_delim = 0 ;
1995     int count_non_digit = 0 ;
1996     int first_field_digits = 0 ;
1997 
1998     for(int i=0 ; i < len ; i++ )
1999     {
2000         char c = str[i] ;
2001         bool is_digit = c >= '0' && c <= '9' ;
2002         bool is_delim = c == ',' ;
2003         if(!is_digit) count_non_digit += 1 ;
2004         if(count_delim == 0 && is_digit ) first_field_digits += 1 ;
2005         if(is_delim) count_delim += 1 ;
2006     }
2007     bool heuristic = count_delim == 2 && count_non_digit == count_delim && first_field_digits == 16 ;
2008     return heuristic ;
2009 }
2010 
2011 
2012 inline std::string U::FormatLog(const char* msg) // static
2013 {
2014     std::string line = U::Format(0, LOG_FMT, 3);
2015     if(msg)
2016     {
2017         line += " " ;
2018         line += msg ;
2019     }
2020     return line ;
2021 }
2022 
2023 inline std::string U::Log(const char* msg) // static
2024 {
2025     std::string line = U::Format(0, LOG_FMT, 3);
2026     if(msg)
2027     {
2028         line += " " ;
2029         line += msg ;
2030     }
2031     return line ;
2032 }
2033 
2034 
2035 
2036 inline std::string U::Format(uint64_t t, const char* fmt, int _wsubsec) // static
2037 {
2038     // from opticks/sysrap/sstamp.h
2039     if(t == 0) t = Now() ;
2040     using Clock = std::chrono::system_clock;
2041     using Unit  = std::chrono::microseconds  ;
2042     std::chrono::time_point<Clock> tp{Unit{t}} ;
2043 
2044     std::time_t tt = Clock::to_time_t(tp);
2045 
2046     std::stringstream ss ;
2047     ss << std::put_time(std::localtime(&tt), fmt ) ;
2048 
2049     if(_wsubsec == 3 || _wsubsec == 6)
2050     {
2051         // extract the sub second part from the duration since epoch
2052         auto subsec = std::chrono::duration_cast<Unit>(tp.time_since_epoch()) % std::chrono::seconds{1};
2053         auto count = subsec.count() ;
2054         if( _wsubsec == 3 ) count /= 1000 ;
2055         ss << "." << std::setfill('0') << std::setw(_wsubsec) << count ;
2056     }
2057 
2058     std::string str = ss.str();
2059     return str ;
2060 }
2061 
2062 inline std::string U::FormatInt(int64_t t, int wid ) // static
2063 {
2064     std::stringstream ss ;
2065     ss.imbue(std::locale("")) ;  // commas for thousands
2066 
2067     if( t > -1 ) ss << std::setw(wid) << t ;
2068     else         ss << std::setw(wid) << "" ;
2069     std::string str = ss.str();
2070     return str ;
2071 }
2072 
2073 /**
2074 U::LastDigit
2075 -------------
2076 
2077 Start from the end of the string, returning when reach
2078 first digit from end (aka last digit from front).
2079 If no digit is found then the returned p would
2080 correspond to the start of the argument string.
2081 
2082 2025/6 : changed behavior when no digit found, now returns nullptr
2083 
2084 **/
2085 
2086 inline char* U::LastDigit(const char* str)
2087 {
2088     if(str == nullptr) return nullptr ;
2089     char* s = const_cast<char*>(str);
2090     bool first_char_is_digit = *s >= '0' && *s <= '9' ;
2091     char* p = s+strlen(s)-1 ;
2092     while( p > s )
2093     {
2094        if( *p >= '0' && *p <= '9' ) break ;
2095        p-- ;
2096     }
2097 
2098     return p == s && first_char_is_digit == false ? nullptr : p ;
2099 }
2100 
2101 /**
2102 U::FirstDigit
2103 --------------
2104 
2105 Iterate through chars until reach first digit at which
2106 point break out of the while loop and return the pointer
2107 to that first digit. If no digits are found then the
2108 loop iterate until reach a pointer to the null terminator
2109 which is returned.
2110 
2111 2025/6 : changed behavior when no digit found, now returns nullptr
2112 
2113 **/
2114 
2115 inline char* U::FirstDigit(const char* str)
2116 {
2117     if(str == nullptr) return nullptr ;
2118     char* s = const_cast<char*>(str);
2119     char* p = s ;
2120     while( *p )  // BUGFIX: that was formerly p, so just keeps looking until step onto bad memory when no digits found
2121     {
2122        if( *p >= '0' && *p <= '9' ) break ;
2123        p++ ;
2124     }
2125     return *p == '\0' ? nullptr : p ;
2126 }
2127 
2128 inline char* U::FirstToLastDigit(const char* str)
2129 {
2130     const char* s = strdup(str);
2131     char* f = FirstDigit(s);
2132     char* l = LastDigit(s);
2133 
2134     bool no_f = f == nullptr ;
2135     bool no_l = l == nullptr ;
2136 
2137     if( no_f && no_l ) return nullptr ;  // NO DIGIT FOUND FROM FRONT OR BACK
2138     if( no_f  && !no_l ) assert(0) ; // LOGICAL INCONSISTENCY
2139     if( !no_f &&  no_l ) assert(0) ; // LOGICAL INCONSISTENCY
2140 
2141     char* r = nullptr ;
2142     if( l + 1  > f)
2143     {
2144         if(*(l+1) != '\0') *(l+1) = '\0' ;
2145         r = f ;
2146     }
2147     return r ;
2148 }
2149 
2150 
2151 /**
2152 U::GetMetaKVS_   (formerly NP::GetMetaKVS_)
2153 ----------------------------------------------
2154 
2155 1. parse the metadata string, for each line split key from val using ":" delimiter
2156 2. where the value looks like a contemporary microsecond uint64_t timestamp (16 digits) extract that
2157 3. where the value looks like profile triplet eg 1111111111111111,2222,3333 with first field a 16 digit timestamp extract that
2158 
2159 Note that for only_with_stamp:false placeholder timestamp values of zero are provided
2160 for lines without stamps or profile triplets.
2161 
2162 Note that when a timestamp is detected both a string and integer version of it are provided.
2163 
2164 **/
2165 
2166 inline void U::GetMetaKVS_(
2167     const char* metadata,
2168     std::vector<std::string>* keys,
2169     std::vector<std::string>* vals,
2170     std::vector<int64_t>* stamps,
2171     bool only_with_stamp ) // static
2172 {
2173     if(metadata == nullptr) return ;
2174     std::stringstream ss;
2175     ss.str(metadata);
2176     std::string s;
2177     char delim = ':' ;
2178 
2179     while (std::getline(ss, s))
2180     {
2181         size_t pos = s.find(delim);
2182         if( pos != std::string::npos )
2183         {
2184             std::string _k = s.substr(0, pos);
2185             std::string _v = s.substr(pos+1);
2186             const char* k = _k.c_str();
2187             const char* v = _v.c_str();
2188             bool disqualify_key = strlen(k) > 0 && k[0] == '_' ;
2189             bool looks_like_stamp = U::LooksLikeStampInt(v);
2190             bool looks_like_prof  = U::LooksLikeProfileTriplet(v);
2191             int64_t t = 0 ;
2192             if(looks_like_stamp) t = U::To<int64_t>(v) ;
2193             if(looks_like_prof)  t = strtoll(v, nullptr, 10);
2194             bool select = only_with_stamp ? ( t > 0 && !disqualify_key )  : true ;
2195             if(!select) continue ;
2196 
2197             if(keys) keys->push_back(k);
2198             if(vals) vals->push_back(v);
2199             if(stamps) stamps->push_back(t);
2200         }
2201     }
2202 }
2203 
2204 /**
2205 U::GetMetaKVS (formerly NP::GetMetaKVS)
2206 ------------------------------------------
2207 
2208 **/
2209 
2210 
2211 inline void U::GetMetaKVS( const std::string& meta, std::vector<std::string>* keys, std::vector<std::string>* vals, std::vector<int64_t>* stamps, bool only_with_stamp  )
2212 {
2213     const char* metadata = meta.empty() ? nullptr : meta.c_str() ;
2214     return GetMetaKVS_( metadata, keys, vals, stamps, only_with_stamp );
2215 }
2216 
2217 
2218 
2219 inline void U::KeyIndices( std::vector<int>& indices, const std::vector<std::string>& keys, const char* key ) // static
2220 {
2221     for(int i=0 ; i < int(keys.size()) ; i++) if(strcmp(keys[i].c_str(), key) == 0) indices.push_back(i);
2222 }
2223 
2224 inline int U::KeyIndex( const std::vector<std::string>& keys, const char* key ) // static
2225 {
2226     int ikey = std::distance( keys.begin(), std::find(keys.begin(), keys.end(), key )) ;
2227     return ikey == int(keys.size()) ? -1 : ikey ;
2228 }
2229 
2230 /**
2231 U::FormattedKeyIndex   (former NP::FormattedKeyIndex)
2232 ----------------------------------------------------------
2233 
2234 Search for key within a keys[idx0:idx1].  When found returns the index, otherwise returns -1.
2235 When the key string contains a "%" character it is assumed to be a format
2236 string suitable for formatting a single integer index that is tried in the
2237 range from idx0 to idx1.
2238 
2239 **/
2240 
2241 inline int U::FormattedKeyIndex( std::string& fkey, const std::vector<std::string>& keys, const char* key, int idx0, int idx1  ) // static
2242 {
2243     int k = -1 ;
2244     if( strchr(key,'%') == nullptr )
2245     {
2246         fkey = key ;
2247         k = KeyIndex(keys, key ) ;
2248     }
2249     else
2250     {
2251         const int N = 100 ;
2252         char keybuf[N] ;
2253         for( int idx=idx0 ; idx < idx1 ; idx++)
2254         {
2255             int n = snprintf(keybuf, N, key, idx ) ;
2256             if(!(n < N)) std::cerr << "U::FormattedKeyIndex ERR n " << n << std::endl ;
2257             assert( n < N );
2258             k = KeyIndex(keys, keybuf ) ;
2259             if( k > -1 )
2260             {
2261                 fkey = keybuf ;
2262                 break ;
2263             }
2264         }
2265     }
2266     return k ;
2267 }
2268 
2269 
2270 inline void U::SplitTuple( std::vector<std::string>& keys, std::vector<int64_t>& tt, const std::vector<std::tuple<std::string,  int64_t>>& kt )
2271 {
2272     for(int i=0 ; i < int(kt.size()) ; i++)
2273     {
2274         keys.push_back(std::get<0>(kt[i]));
2275         tt.push_back(std::get<1>(kt[i]));
2276     }
2277 }
2278 
2279 
2280 
2281 
2282 
2283 
2284 
2285 
2286 
2287 
2288 
2289 
2290 
2291 
2292 
2293 
2294 
2295 
2296 
2297 
2298 
2299 
2300 
2301 
2302 
2303 struct NPU
2304 {
2305     typedef std::int64_t INT ;
2306 
2307     static constexpr char* MAGIC = (char*)"\x93NUMPY" ;
2308     static constexpr bool  FORTRAN_ORDER = false ;
2309 
2310     template<typename T>
2311     static std::string make_header(const std::vector<INT>& shape );
2312 
2313     template<typename T>
2314     static std::string make_jsonhdr(const std::vector<INT>& shape );
2315 
2316     static void parse_header(std::vector<INT>& shape, std::string& descr, char& uifc, INT& ebyte, const std::string& hdr );
2317     static int  _parse_header_length(const std::string& hdr );
2318     static void _parse_tuple(std::vector<INT>& shape, const std::string& sh );
2319     static void _parse_dict(bool& little_endian, char& uifc, INT& width, std::string& descr, bool& fortran_order, const char* dict);
2320     static void _parse_dict(std::string& descr, bool& fortran_order, const char* dict);
2321     static void _parse_descr(bool& little_endian, char& uifc, INT& width, const char* descr);
2322 
2323     static NPU::INT  _dtype_ebyte(const char* dtype);
2324     static char      _dtype_uifc(const char* dtype);
2325 
2326     static std::string _make_descr(bool little_endian, char uifc, INT width );
2327     static std::string _make_narrow(const char* descr);
2328     static std::string _make_wide(const char* descr);
2329     static std::string _make_other(const char* descr, char other);
2330 
2331     static std::string _make_preamble( INT major=1, INT minor=0 );
2332     static std::string _make_header(const std::vector<INT>& shape, const char* descr="<f4" );
2333     static std::string _make_jsonhdr(const std::vector<INT>& shape, const char* descr="<f4" );
2334     static std::string _little_endian_short_string( uint16_t dlen ) ;
2335     static std::string _make_tuple(const std::vector<INT>& shape, bool json );
2336     static std::string _make_dict(const std::vector<INT>& shape, const char* descr );
2337     static std::string _make_json(const std::vector<INT>& shape, const char* descr );
2338     static std::string _make_header(const std::string& dict);
2339     static std::string _make_jsonhdr(const std::string& json);
2340 
2341     static std::string xxdisplay(const std::string& hdr, INT width, char non_printable );
2342     static std::string _check(const char* path);
2343     static int         check(const char* path);
2344     static bool is_readable(const char* path);
2345 };
2346 
2347 template<typename T>
2348 inline std::string NPU::make_header(const std::vector<INT>& shape )
2349 {
2350     //std::string descr = Desc<T>::descr() ;
2351     std::string descr = descr_<T>::dtype() ;
2352 
2353     return _make_header( shape, descr.c_str() ) ;
2354 }
2355 
2356 template<typename T>
2357 inline std::string NPU::make_jsonhdr(const std::vector<INT>& shape )
2358 {
2359     //std::string descr = Desc<T>::descr() ;
2360     std::string descr = descr_<T>::dtype() ;
2361     return _make_jsonhdr( shape, descr.c_str() ) ;
2362 }
2363 
2364 inline std::string NPU::xxdisplay(const std::string& hdr, INT width, char non_printable)
2365 {
2366     std::stringstream ss ;
2367     for(unsigned i=0 ; i < hdr.size() ; i++)
2368     {
2369         char c = hdr[i] ;
2370         bool printable = c >= ' ' && c <= '~' ;  // https://en.wikipedia.org/wiki/ASCII
2371         ss << ( printable ? c : non_printable )  ;
2372         if((i+1) % width == 0 ) ss << "\n" ;
2373    }
2374    return ss.str();
2375 }
2376 
2377 inline int NPU::_parse_header_length(const std::string& hdr )
2378 {
2379 /*
2380 Extract from the NPY format specification
2381 -------------------------------------------
2382 
2383 * https://github.com/numpy/numpy/blob/master/doc/neps/nep-0001-npy-format.rst
2384 
2385 1. The first 6 bytes are a magic string: exactly "x93NUMPY".
2386 2. The next 1 byte is an unsigned byte: the major version number of the file format, e.g. x01.
2387 3. The next 1 byte is an unsigned byte: the minor version number of the file format, e.g. x00.
2388    Note: the version of the file format is not tied to the version of the numpy package.
2389 
2390 4. The next 2 bytes form a little-endian unsigned short int: the length of the header data HEADER_LEN.
2391 
2392 The next HEADER_LEN bytes form the header data describing the array's format.
2393 It is an ASCII string which contains a Python literal expression of a
2394 dictionary. It is terminated by a newline ('n') and padded with spaces ('x20')
2395 to make the total length of the magic string + 4 + HEADER_LEN be evenly
2396 divisible by 16 for alignment purposes.
2397 
2398 Example Headers
2399 ----------------
2400 
2401 Created by commands like::
2402 
2403     python -c "import numpy as np ; np.save('/tmp/z0.npy', np.zeros((10,4), dtype=np.float64)) "
2404 
2405 Older NumPy does not add padding::
2406 
2407     epsilon:np blyth$ xxd /tmp/z.npy
2408     00000000: 934e 554d 5059 0100 4600 7b27 6465 7363  .NUMPY..F.{'desc
2409     00000010: 7227 3a20 273c 6638 272c 2027 666f 7274  r': '<f8', 'fort
2410     00000020: 7261 6e5f 6f72 6465 7227 3a20 4661 6c73  ran_order': Fals
2411     00000030: 652c 2027 7368 6170 6527 3a20 2831 302c  e, 'shape': (10,
2412     00000040: 2034 292c 207d 2020 2020 2020 2020 200a   4), }         .
2413     00000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
2414     00000060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
2415     00000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
2416 
2417 Newer NumPy adds a little padding to the header::
2418 
2419     epsilon:np blyth$ xxd /tmp/z0.npy
2420     00000000: 934e 554d 5059 0100 7600 7b27 6465 7363  .NUMPY..v.{'desc
2421     00000010: 7227 3a20 273c 6638 272c 2027 666f 7274  r': '<f8', 'fort
2422     00000020: 7261 6e5f 6f72 6465 7227 3a20 4661 6c73  ran_order': Fals
2423     00000030: 652c 2027 7368 6170 6527 3a20 2831 302c  e, 'shape': (10,
2424     00000040: 2034 292c 207d 2020 2020 2020 2020 2020   4), }
2425     00000050: 2020 2020 2020 2020 2020 2020 2020 2020
2426     00000060: 2020 2020 2020 2020 2020 2020 2020 2020
2427     00000070: 2020 2020 2020 2020 2020 2020 2020 200a                 .
2428     00000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
2429     00000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
2430 
2431 Parsing the header
2432 -------------------
2433 
2434 The preamble is first 8 bytes, 6 bytes for the magic then 2 bytes for the version,
2435 followed by 2 bytes with the header length : making 10 bytes which are always present.
2436 The header length does not include these first 10 bytes.  The header is padded with x20
2437 to make (hlen+10)%16 == 0 and it is terminated with a newline hex:0a dec:10
2438 
2439 NumPy np.save / np.load
2440 -------------------------
2441 
2442 * https://github.com/numpy/numpy/blob/master/numpy/lib/npyio.py
2443 * https://github.com/numpy/numpy/blob/master/numpy/lib/format.py
2444 
2445 */
2446     std::string preamble = hdr.substr(0,8) ;  // 6 char MAGIC + 2 char version
2447     std::string PREAMBLE = _make_preamble();
2448     assert( preamble.compare(PREAMBLE) == 0 );
2449 
2450     // previously used "char" here,
2451     // but thats a bug as when the header exceeds 128 bytes
2452     // it flips -ve (twos complement)
2453     // observed this first with the bnd.npy which has 5 dimensions
2454     // causing the header to be larger than for example icdf.npy with 3 dimensions
2455 
2456     unsigned char hlen_lsb = hdr[8] ;
2457     unsigned char hlen_msb = hdr[9] ;
2458     int hlen = hlen_msb << 8 | hlen_lsb ;
2459 
2460 #ifdef NPU_DEBUG
2461     std::cout
2462         << " _parse_header_length  "  << std::endl
2463         << " hdr               " << std::endl << xxdisplay(hdr, 16, '.' ) << std::endl
2464         << " preamble          " << preamble << std::endl
2465         << " hlen_lsb(hex)     " << std::hex << int(hlen_lsb) << std::endl
2466         << " hlen_msb(hex)     " << std::hex << int(hlen_msb) << std::endl
2467         << " hlen(hex)         " << std::hex << hlen << std::endl
2468         << " hlen_lsb(dec)     " << std::dec << int(hlen_lsb) << std::endl
2469         << " hlen_msb(dec)     " << std::dec << int(hlen_msb) << std::endl
2470         << " hlen(dec)         " << std::dec << hlen << std::endl
2471         << " hlen+10(dec)      " << std::dec << hlen+10 << std::endl
2472         << " (hlen+10)%16(dec) " << (hlen+10)%16 << std::endl
2473         << " hdr.size() (dec)  " << std::dec << hdr.size() << std::endl
2474         << " preamble.size()   " << std::dec << preamble.size() << std::endl
2475         << std::endl
2476         ;
2477 
2478 #endif
2479     assert( hlen > 0 );
2480     assert( (hlen+10) % 16 == 0 ) ;
2481     assert( hlen+10 == int(hdr.size()) ) ;
2482 
2483     return hlen ;
2484 }
2485 
2486 
2487 inline void NPU::parse_header(std::vector<INT>& shape, std::string& descr, char& uifc, INT& ebyte, const std::string& hdr )
2488 {
2489     int hlen = _parse_header_length( hdr ) ;
2490 
2491     std::string dict = hdr.substr(10,10+hlen) ;
2492 
2493     char last = dict[dict.size()-1] ;
2494     bool ends_with_newline = last == '\n' ;
2495     if(!ends_with_newline) std::cerr << "NPU::parse_header UNEXPECTED ends_with_newline  " << std::endl ;
2496     assert(ends_with_newline) ;
2497     dict[dict.size()-1] = '\0' ;
2498 
2499     std::string::size_type p0 = dict.find("(") + 1;
2500     std::string::size_type p1 = dict.find(")");
2501     assert( p0 != std::string::npos );
2502     assert( p1 != std::string::npos );
2503 
2504     std::string sh = dict.substr( p0, p1 - p0 ) ;
2505 
2506     _parse_tuple( shape, sh );
2507 
2508 
2509     bool little_endian ;
2510     bool fortran_order ;
2511 
2512     _parse_dict(little_endian, uifc, ebyte, descr, fortran_order, dict.c_str());
2513 
2514 
2515     assert( fortran_order == FORTRAN_ORDER );
2516     assert( little_endian == true );
2517 
2518 #ifdef NPU_DEBUG
2519     std::cout
2520         << " parse_header  "  << std::endl
2521         << " hdr               " << std::endl << xxdisplay(hdr, 16, '.' ) << std::endl
2522         << " hlen(hex)         " << std::hex << hlen << std::endl
2523         << " hlen(dec)         " << std::dec << hlen << std::endl
2524         << " hlen+10(dec)      " << std::dec << hlen+10 << std::endl
2525         << " (hlen+10)%16(dec) " << (hlen+10)%16 << std::endl
2526         << " dict [" << xxdisplay(dict,200,'.') << "]"<< std::endl
2527         << " p0( " << p0 << std::endl
2528         << " p1) " << p1 << std::endl
2529         << " shape " << sh << std::endl
2530         << " last(dec)         " << std::dec << int(last) << std::endl
2531         << " newline(dec)      " << std::dec << int('\n') << std::endl
2532         << " hdr.size() (dec)  " << std::dec << hdr.size() << std::endl
2533         << " dict.size() (dec) " << std::dec << dict.size() << std::endl
2534         << " descr " << descr
2535         << " uifc " << uifc
2536         << " ebyte " << ebyte
2537         << std::endl
2538         ;
2539 
2540 #endif
2541 
2542 }
2543 
2544 inline void NPU::_parse_tuple(std::vector<INT>& shape, const std::string& sh )
2545 {
2546     std::istringstream f(sh);
2547     std::string s;
2548 
2549     char delim = ',' ;
2550     const char* trim = " " ;
2551 
2552     INT ival(0) ;
2553 
2554     while (getline(f, s, delim))
2555     {
2556        s.erase(0, s.find_first_not_of(trim));  // left trim
2557        s.erase(s.find_last_not_of(trim) + 1);   // right trim
2558        if( s.size() == 0 ) continue ;
2559 
2560        std::istringstream ic(s) ;
2561        ic >> ival ;
2562 
2563        shape.push_back(ival) ;
2564 
2565 #ifdef NPU_DEBUG
2566        std::cout << "[" << s << "] -> " << ival << std::endl ;
2567 #endif
2568 
2569     }
2570 
2571 #ifdef NPU_DEBUG
2572     std::cout << " parse_tuple "
2573               << " sh  [" << sh << "]"
2574               << " shape " << shape.size()
2575               << std::endl
2576               ;
2577 
2578 #endif
2579 }
2580 
2581 
2582 inline void NPU::_parse_dict(bool& little_endian, char& uifc, INT& ebyte, std::string& descr, bool& fortran_order, const char* dict)  // static
2583 {
2584     _parse_dict(descr, fortran_order, dict);
2585     _parse_descr(little_endian, uifc, ebyte, descr.c_str() );
2586 }
2587 
2588 
2589 /**
2590 NPU::_parse_dict
2591 ------------------
2592 
2593 ::
2594 
2595     const char* dict = R"({'descr': '<f4', 'fortran_order': False, 'shape': (10, 4), })" ;
2596     //       nq:           1     2  3   4  5             6         7     8
2597     //     elem:                 0      1                2       3       4
2598 
2599 **/
2600 
2601 inline void NPU::_parse_dict(std::string& descr, bool& fortran_order, const char* dict) // static
2602 {
2603     char q = '\'' ;
2604     char x = '\0' ;   // "wildcard" extra delim
2605 
2606     std::vector<std::string> elem ;
2607     std::stringstream ss ;
2608     unsigned nq = 0 ;
2609     for(unsigned i=0 ; i < strlen(dict) ; i++)
2610     {
2611         if(dict[i] == q || dict[i] == x)
2612         {
2613             nq += 1 ;
2614             if(nq == 6 ) x = ' ' ;
2615             if(nq == 7 ) x = ',' ;
2616             if(nq == 8 ) x = '\0' ;
2617 
2618             if( nq % 2 == 0 )
2619             {
2620                 elem.push_back(ss.str());
2621                 ss.str("");
2622             }
2623         }
2624         else
2625         {
2626             if(nq % 2 == 1 ) ss << dict[i] ;
2627         }
2628     }
2629 
2630     assert( elem[0].compare("descr") == 0 );
2631     assert( elem[2].compare("fortran_order") == 0 );
2632     assert( elem[3].compare("False") == 0 || elem[3].compare("True") == 0);
2633     assert( elem[4].compare("shape") == 0 );
2634 
2635     descr = elem[1];
2636     assert( descr.length() == 3 );
2637 
2638     fortran_order = elem[3].compare("False") == 0 ? false : true ;
2639 }
2640 
2641 inline void NPU::_parse_descr(bool& little_endian, char& uifc, INT& ebyte, const char* descr)  // static
2642 {
2643     assert( strlen(descr) == 3 );
2644 
2645     char c_endian = descr[0] ;
2646     char c_uifc = descr[1] ;
2647     char c_ebyte = descr[2] ;
2648 
2649     bool expect_endian = c_endian == '<' || c_endian == '>' || c_endian == '|' ;
2650     if(!expect_endian)
2651     {
2652         std::cerr
2653             << "unexpected endian "
2654             << " c_endian " << c_endian
2655             << " descr [" << descr << "]"
2656             << std::endl
2657              ;
2658     }
2659     assert( expect_endian );
2660     little_endian = c_endian == '<' || c_endian == '|' ;
2661 
2662     assert( c_uifc == 'u' || c_uifc == 'i' || c_uifc == 'f' || c_uifc == 'c' );
2663     uifc = c_uifc ;
2664 
2665     ebyte = c_ebyte - '0' ;
2666     assert( ebyte == 1 || ebyte == 2 || ebyte == 4 || ebyte == 8 );
2667 }
2668 
2669 inline NPU::INT NPU::_dtype_ebyte(const char* dtype)  // static
2670 {
2671     unsigned len = strlen(dtype) ;
2672     assert( len == 2 || len == 3 );
2673 
2674     char c_ebyte = dtype[len-1] ;
2675     INT ebyte = c_ebyte - '0' ;
2676 
2677     assert( ebyte == 1 || ebyte == 2 || ebyte == 4 || ebyte == 8 );
2678     return ebyte ;
2679 }
2680 inline char NPU::_dtype_uifc(const char* dtype) // static
2681 {
2682     unsigned len = strlen(dtype) ;
2683     assert( len == 2 || len == 3 );
2684     char c_uifc = dtype[len-2] ;
2685     assert( c_uifc == 'u' || c_uifc == 'i' || c_uifc == 'f' );  // dont bother with 'c' complex
2686     return c_uifc ;
2687 }
2688 
2689 inline std::string NPU::_make_descr(bool little_endian, char uifc, INT width ) // static
2690 {
2691     std::stringstream ss ;
2692     ss << ( little_endian ? '<' : '>' ) << uifc << width ;
2693     return ss.str();
2694 }
2695 
2696 inline std::string NPU::_make_narrow(const char* descr) // static
2697 {
2698     bool little_endian ;
2699     char uifc ;
2700     INT ebyte ;
2701     _parse_descr( little_endian, uifc, ebyte, descr );
2702     return _make_descr(little_endian, uifc, ebyte/2  );
2703 }
2704 
2705 inline std::string NPU::_make_wide(const char* descr) // static
2706 {
2707     bool little_endian ;
2708     char uifc ;
2709     INT ebyte ;
2710     _parse_descr( little_endian, uifc, ebyte, descr );
2711     return _make_descr(little_endian, uifc, ebyte*2  );
2712 }
2713 
2714 
2715 inline std::string NPU::_make_other(const char* descr, char other) // static
2716 {
2717     bool little_endian ;
2718     char uifc ;
2719     INT ebyte ;
2720     _parse_descr( little_endian, uifc, ebyte, descr );
2721     return _make_descr(little_endian, other, ebyte  );
2722 }
2723 
2724 
2725 
2726 
2727 inline bool NPU::is_readable(const char* path)  // static
2728 {
2729     std::ifstream fp(path, std::ios::in|std::ios::binary);
2730     bool readable = !fp.fail();
2731     fp.close();
2732     return readable ;
2733 }
2734 
2735 
2736 inline std::string NPU::_check(const char* path)
2737 {
2738     char* py = getenv("PYTHON");
2739     std::stringstream ss ;
2740     ss << ( py ? py : "python" )
2741        << " -c \"import numpy as np ; print(np.load('"
2742        << path
2743        << "')) \" && xxd "
2744        << path
2745        ;
2746     return ss.str();
2747 }
2748 
2749 inline int NPU::check(const char* path)
2750 {
2751     std::string cmd = _check(path);
2752     return system(cmd.c_str());
2753 }
2754 
2755 
2756 
2757 inline std::string NPU::_make_header(const std::vector<INT>& shape, const char* descr )
2758 {
2759     std::string dict = _make_dict( shape, descr );
2760     std::string header = _make_header( dict );
2761     return header ;
2762 }
2763 
2764 inline std::string NPU::_make_jsonhdr(const std::vector<INT>& shape, const char* descr )
2765 {
2766     std::string json = _make_json( shape, descr );
2767     return json ;
2768 }
2769 
2770 
2771 
2772 inline std::string NPU::_make_dict(const std::vector<INT>& shape, const char* descr )
2773 {
2774     std::stringstream ss ;
2775     ss << "{" ;
2776     ss << "'descr': '" << descr << "', " ;
2777     ss << "'fortran_order': " << ( FORTRAN_ORDER ? "True" : "False" ) << ", " ;
2778     ss << "'shape': " ;
2779     bool json = false ;
2780     ss << _make_tuple( shape, json ) ;
2781     ss << "}" ;
2782     return ss.str();
2783 }
2784 
2785 inline std::string NPU::_make_json(const std::vector<INT>& shape, const char* descr )
2786 {
2787     std::stringstream ss ;
2788     ss << "{" ;
2789     ss << "\"descr\": \"" << descr << "\", " ;
2790     ss << "\"fortran_order\": " << ( FORTRAN_ORDER ? "true" : "false" ) << ", " ;
2791     ss << "\"shape\": " ;
2792     bool json = true ;
2793     ss << _make_tuple( shape, json) ;
2794     ss << "}" ;
2795     return ss.str();
2796 }
2797 
2798 
2799 
2800 
2801 inline std::string NPU::_make_tuple( const std::vector<INT>& shape, bool json )
2802 {
2803     INT ndim = shape.size() ;
2804     std::stringstream ss ;
2805     ss <<  ( json ? "[" : "(" ) ;
2806 
2807     if( ndim == 1)
2808     {
2809         ss << shape[0] << "," ;
2810     }
2811     else
2812     {
2813         for(INT i=0 ; i < ndim ; i++ ) ss << shape[i] << ( i == ndim - 1 ? "" : ", " )  ;
2814     }
2815     ss << ( json ?  "] " : "), " ) ;    // hmm assuming shape comes last in json
2816     return ss.str();
2817 }
2818 
2819 
2820 inline std::string NPU::_little_endian_short_string( uint16_t dlen )
2821 {
2822     // https://github.com/numpy/numpy/blob/master/doc/neps/nep-0001-npy-format.rst
2823     // The next 2 bytes form a little-endian unsigned short int: the length of the header data HEADER_LEN
2824 
2825     union u16c2_t {
2826         uint16_t u16 ;
2827         char     c[2] ;
2828     };
2829 
2830     u16c2_t len ;
2831     len.u16 = dlen ;
2832 
2833     char e = endian::detect() ;
2834     std::string hlen(2, ' ') ;
2835     hlen[0] = e == endian::LITTLE ? len.c[0] : len.c[1] ;
2836     hlen[1] = e == endian::LITTLE ? len.c[1] : len.c[0] ;
2837 
2838 #ifdef NPU_DEBUG
2839     std::cout << " dlen " << dlen << std::endl ;
2840     std::cout << " len.c[0] " << len.c[0] << std::endl ;
2841     std::cout << " len.c[1] " << len.c[1] << std::endl ;
2842     std::cout << ( e == endian::LITTLE ? "little_endian" : "big_endian" ) << std::endl ;
2843 #endif
2844 
2845     return hlen ;
2846 }
2847 
2848 
2849 inline std::string NPU::_make_preamble( INT major, INT minor )
2850 {
2851     std::string preamble(MAGIC) ;
2852     preamble.push_back((char)major);
2853     preamble.push_back((char)minor);
2854     return preamble ;
2855 }
2856 
2857 inline std::string NPU::_make_header(const std::string& dict)
2858 {
2859     uint16_t dlen = dict.size() ;
2860     uint16_t padding = 16 - ((10 + dlen ) % 16 ) - 1 ;
2861     padding += 3*16 ; // adhoc extra padding for bit-perfect matching to NumPy (for test array anyhow)
2862     uint16_t hlen = dlen + padding + 1 ;
2863 
2864 #ifdef NPU_DEBUG
2865     std::cout
2866         << " dlen " << dlen
2867         << " padding " << padding
2868         << " hlen " << hlen
2869         << std::endl
2870         ;
2871 #endif
2872 
2873     assert( (hlen + 10) % 16 == 0 );
2874     std::stringstream ss ;
2875     ss << _make_preamble() ;
2876     ss << _little_endian_short_string( hlen ) ;
2877     ss << dict ;
2878 
2879     for(INT i=0 ; i < padding ; i++ ) ss << " " ;
2880     ss << "\n" ;
2881 
2882     return ss.str();
2883 }
2884 
2885 
2886 
2887 
2888 
2889 
2890 
2891 
2892 
2893 
2894 
2895 
2896 
2897 
2898 
2899 
2900 
2901 
2902 
2903 
2904 
2905 
2906 
2907 
2908 
2909 
2910 
2911 
2912 
2913 
2914 
2915 
2916 
2917 
2918 
2919 
2920 
2921 
2922 
2923 
2924 /**
2925 nview.h
2926 =========
2927 
2928 Templated reinterpretation of bits allowing to view
2929 unsigned int as float and double and vice versa.
2930 
2931 Note this is present in sysrap/sview.h
2932 
2933 **/
2934 
2935 #if defined(__CUDACC__) || defined(__CUDABE__)
2936 #    define NVIEW_METHOD __host__ __device__ __forceinline__
2937 #else
2938 #    define NVIEW_METHOD inline
2939 #endif
2940 
2941 struct nview
2942 {
2943     union UIF32
2944     {
2945         unsigned u ;
2946         int i  ;
2947         float f ;
2948     };
2949 
2950     struct uint2 { unsigned x, y ; };
2951     struct int2  { unsigned x, y ; };
2952 
2953     union UIF64
2954     {
2955         uint2  uu ;
2956         int2   ii ;
2957         double f ;
2958     };
2959 
2960     template<typename T> static T int_as( int i );
2961     template<typename T> static int int_from( T v );
2962 
2963     template<typename T> static T uint_as( unsigned u );
2964     template<typename T> static unsigned uint_from( T v );
2965 };
2966 
2967 template<> NVIEW_METHOD float nview::int_as<float>( int i )
2968 {
2969      UIF32 u32 ;
2970      u32.i = i ;
2971      return u32.f ;
2972 }
2973 template<> NVIEW_METHOD double nview::int_as<double>( int i )
2974 {
2975      UIF64 u64 ;
2976      u64.ii.x = i ;
2977      return u64.f ;
2978 }
2979 
2980 
2981 template<> NVIEW_METHOD int nview::int_from<float>( float f )
2982 {
2983      UIF32 u32 ;
2984      u32.f = f ;
2985      return u32.i ;
2986 }
2987 template<> NVIEW_METHOD int nview::int_from<double>( double f )
2988 {
2989      UIF64 u64 ;
2990      u64.f = f ;
2991      return u64.ii.x  ;
2992 }
2993 
2994 template<> NVIEW_METHOD float nview::uint_as<float>( unsigned u )
2995 {
2996      UIF32 u32 ;
2997      u32.u = u ;
2998      return u32.f ;
2999 }
3000 template<> NVIEW_METHOD double nview::uint_as<double>( unsigned v )
3001 {
3002      UIF64 u64 ;
3003      u64.uu.x = v ;
3004      return u64.f ;
3005 }
3006 
3007 template<> NVIEW_METHOD unsigned nview::uint_from<float>( float f )
3008 {
3009      UIF32 u32 ;
3010      u32.f = f ;
3011      return u32.u ;
3012 }
3013 template<> NVIEW_METHOD unsigned nview::uint_from<double>( double f )
3014 {
3015      UIF64 u64 ;
3016      u64.f = f ;
3017      return u64.uu.x ;
3018 }
3019 
3020 
3021 
3022 
3023 
3024 
3025 struct UName
3026 {
3027     static constexpr const char* UNSET = "UNSET" ;
3028     std::vector<std::string> names ;
3029     int count ;
3030 
3031     UName();
3032     int get(const char* name) ;
3033     int add(const char* name, bool dump=false) ;
3034     std::string desc() const ;
3035     std::string as_str() const ;
3036 };
3037 
3038 
3039 inline UName::UName()
3040     :
3041     count(0)
3042 {
3043     names.push_back(UNSET);
3044 }
3045 
3046 inline int UName::add(const char* name, bool dump)
3047 {
3048     if(std::find(names.begin(), names.end(), name) == names.end()) names.push_back(name) ;
3049     int idx = get(name) ;
3050     if(dump) std::cerr
3051         << "UName::add"
3052         << " idx " << std::setw(4) << idx
3053         << " name " << name
3054         << " count " << std::setw(5) << count
3055         << " size " << std::setw(5) << names.size()
3056         << std::endl
3057         ;
3058 
3059     //if(dump) std::cerr << desc() ;
3060     count += 1 ;
3061     return idx ;
3062 }
3063 inline int UName::get(const char* name)
3064 {
3065     size_t idx = std::distance( names.begin(), std::find(names.begin(), names.end(), name) ) ;
3066     size_t num = names.size() ;
3067     std::cout << "UName::get " << ( name ? name : "-" ) << " num " << num << " idx " << idx << std::endl ;
3068     return idx == num ? -1 : int(idx) ;
3069 }
3070 inline std::string UName::desc() const
3071 {
3072     std::stringstream ss ;
3073     ss << "UName::desc"
3074        << " count " << std::setw(5) << count
3075        << " size " << std::setw(5) << names.size()
3076        << std::endl
3077        ;
3078 
3079     for(int i=0 ; i < int(names.size()) ; i++ ) ss << std::setw(5) << i << " : " << names[i] << std::endl ;
3080     std::string str = ss.str();
3081     return str ;
3082 }
3083 inline std::string UName::as_str() const
3084 {
3085     int num_names = names.size();
3086     std::stringstream ss ;
3087     for(int i=0 ; i < num_names ; i++ )
3088     {
3089         ss << names[i] ;
3090         if( i < num_names - 1 ) ss << std::endl ;
3091     }
3092     std::string str = ss.str();
3093     return str ;
3094 }
3095 
3096 
3097 
3098 
3099 
3100 union uc4
3101 {
3102     char c[4] ;
3103     unsigned u ;
3104 
3105     void set(const char* s4) ;
3106     std::string get() const ;
3107     std::string desc() const ;
3108 };
3109 
3110 inline void uc4::set(const char* s4)
3111 {
3112     for(unsigned i=0 ; i < 4 ; i++) c[i] = i < strlen(s4) ? s4[i] : '\0' ;
3113 }
3114 inline std::string uc4::get() const
3115 {
3116     const char* p = &c[0] ;
3117     std::string str(p, p+4) ;
3118     return str ;
3119 }
3120 inline std::string uc4::desc() const
3121 {
3122     std::stringstream ss ;
3123     ss << get() << " " << u  ;
3124     std::string str = ss.str();
3125     return str ;
3126 }
3127 
3128 
3129 
3130 
3131 
3132 
3133 union uc8
3134 {
3135     char     c[8] ;
3136     uint64_t u ;
3137 
3138     void set(const char* s8) ;
3139     std::string get() const ;
3140     std::string desc() const ;
3141 };
3142 inline void uc8::set(const char* s8)
3143 {
3144     for(unsigned i=0 ; i < 8 ; i++) c[i] = i < strlen(s8) ? s8[i] : '\0' ;
3145 }
3146 inline std::string uc8::get() const
3147 {
3148     const char* p = &c[0] ;
3149     std::string str(p, p+8) ;
3150     return str ;
3151 }
3152 inline std::string uc8::desc() const
3153 {
3154     std::stringstream ss ;
3155     ss << get() << " " << u  ;
3156     std::string str = ss.str();
3157     return str ;
3158 }
3159 
3160 #endif