1
0
mirror of https://github.com/paboyle/Grid.git synced 2024-11-09 23:45:36 +00:00

Serialisation fixes after Antonin's review

This commit is contained in:
Michael Marshall 2019-04-03 22:30:07 +01:00
parent b5eb97206b
commit 4161429dcc
4 changed files with 56 additions and 351 deletions

View File

@ -61,15 +61,25 @@ namespace Grid {
template<typename T, typename V = void> struct is_tensor_of_container : public std::false_type {};
template<typename T> struct is_tensor_of_container<T, typename std::enable_if<is_tensor<T>::value && isGridTensor<typename T::Scalar>::value>::type> : public std::true_type {};
// Traits are the default for scalars, or come from GridTypeMapper for GridTensors
// These traits describe the scalars inside Eigen tensors
// I wish I could define these in reference to the scalar type (so there would be fewer traits defined)
// but I'm unable to find a syntax to make this work
template<typename T, typename V = void> struct Traits {};
template<typename T> struct Traits<T, typename std::enable_if<is_tensor_of_scalar<T>::value>::type> : public GridTypeMapper_Base {
using scalar_type = typename T::Scalar;
// Traits are the default for scalars, or come from GridTypeMapper for GridTensors
template<typename T> struct Traits<T, typename std::enable_if<is_tensor_of_scalar<T>::value>::type>
: public GridTypeMapper_Base {
using scalar_type = typename T::Scalar; // ultimate base scalar
static constexpr bool is_complex = ::Grid::EigenIO::is_complex<scalar_type>::value;
};
template<typename T> struct Traits<T, typename std::enable_if<is_tensor_of_container<T>::value>::type> : public GridTypeMapper<typename T::Scalar> {
using scalar_type = typename GridTypeMapper<typename T::Scalar>::scalar_type;
static constexpr bool is_complex = ::Grid::EigenIO::is_complex<scalar_type>::value;
// Traits are the default for scalars, or come from GridTypeMapper for GridTensors
template<typename T> struct Traits<T, typename std::enable_if<is_tensor_of_container<T>::value>::type> {
using BaseTraits = GridTypeMapper<typename T::Scalar>;
using scalar_type = typename BaseTraits::scalar_type; // ultimate base scalar
static constexpr bool is_complex = ::Grid::EigenIO::is_complex<scalar_type>::value;
static constexpr int TensorLevel = BaseTraits::TensorLevel;
static constexpr int Rank = BaseTraits::Rank;
static constexpr std::size_t count = BaseTraits::count;
static constexpr int Dimension(int dim) { return BaseTraits::Dimension(dim); }
};
// Is this a fixed-size Eigen tensor
@ -310,15 +320,15 @@ namespace Grid {
// If the Tensor isn't in Row-Major order, then we'll need to copy it's data
const bool CopyData{NumElements > 1 && ETensor::Layout != Eigen::StorageOptions::RowMajor};
const Scalar * pWriteBuffer;
Scalar * pCopyBuffer = nullptr;
std::vector<Scalar> CopyBuffer;
const Index TotalNumElements = NumElements * Traits::count;
if( !CopyData ) {
pWriteBuffer = getFirstScalar( output );
} else {
// Regardless of the Eigen::Tensor storage order, the copy will be Row Major
pCopyBuffer = new Scalar[TotalNumElements];
pWriteBuffer = pCopyBuffer;
Scalar * pCopy = pCopyBuffer;
CopyBuffer.resize( TotalNumElements );
Scalar * pCopy = &CopyBuffer[0];
pWriteBuffer = pCopy;
std::array<Index, TensorRank> MyIndex;
for( auto &idx : MyIndex ) idx = 0;
for( auto n = 0; n < NumElements; n++ ) {
@ -330,7 +340,6 @@ namespace Grid {
}
}
upcast->template writeMultiDim<Scalar>(s, TotalDims, pWriteBuffer, TotalNumElements);
if( pCopyBuffer ) delete [] pCopyBuffer;
}
template <typename T>

View File

@ -51,7 +51,7 @@ namespace Grid
std::vector<std::string> path_;
H5NS::H5File file_;
H5NS::Group group_;
unsigned int dataSetThres_{HDF5_DEF_DATASET_THRES};
const unsigned int dataSetThres_{HDF5_DEF_DATASET_THRES};
};
class Hdf5Reader: public Reader<Hdf5Reader>
@ -117,15 +117,8 @@ namespace Grid
// write the entire dataset to file
H5NS::DataSpace dataSpace(rank, dim.data());
size_t DataSize = NumElements * sizeof(U);
if (DataSize > dataSetThres_)
if (NumElements > dataSetThres_)
{
// First few prime numbers from https://oeis.org/A000040
static const unsigned short Primes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31,
37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109,
113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193,
197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271 };
constexpr int NumPrimes = sizeof( Primes ) / sizeof( Primes[0] );
// Make sure 1) each dimension; and 2) chunk size is < 4GB
const hsize_t MaxElements = ( sizeof( U ) == 1 ) ? 0xffffffff : 0x100000000 / sizeof( U );
hsize_t ElementsPerChunk = 1;
@ -136,13 +129,9 @@ namespace Grid
d = 1; // Chunk size is already as big as can be - remaining dimensions = 1
else {
// If individual dimension too big, reduce by prime factors if possible
for( int PrimeIdx = 0; d > MaxElements && PrimeIdx < NumPrimes; ) {
if( d % Primes[PrimeIdx] )
PrimeIdx++;
else
d /= Primes[PrimeIdx];
}
const char ErrorMsg[] = " dimension > 4GB without small prime factors. "
while( d > MaxElements && ( d & 1 ) == 0 )
d >>= 1;
const char ErrorMsg[] = " dimension > 4GB and not divisible by 2^n. "
"Hdf5IO chunk size will be inefficient. NB Serialisation is not intended for large datasets - please consider alternatives.";
if( d > MaxElements ) {
std::cout << GridLogWarning << "Individual" << ErrorMsg << std::endl;
@ -156,17 +145,13 @@ namespace Grid
ElementsPerChunk *= d;
assert( OverflowCheck == ElementsPerChunk / d && "Product of dimensions overflowed hsize_t" );
// If product of dimensions too big, reduce by prime factors
for( int PrimeIdx = 0; ElementsPerChunk > MaxElements && PrimeIdx < NumPrimes; ) {
while( ElementsPerChunk > MaxElements && ( ElementsPerChunk & 1 ) == 0 ) {
bTooBig = true;
if( d % Primes[PrimeIdx] )
PrimeIdx++;
else {
d /= Primes[PrimeIdx];
ElementsPerChunk /= Primes[PrimeIdx];
}
d >>= 1;
ElementsPerChunk >>= 1;
}
if( ElementsPerChunk > MaxElements ) {
std::cout << GridLogMessage << "Product of" << ErrorMsg << std::endl;
std::cout << GridLogWarning << "Product of" << ErrorMsg << std::endl;
hsize_t quotient = ElementsPerChunk / MaxElements;
if( ElementsPerChunk % MaxElements )
quotient++;
@ -270,7 +255,7 @@ namespace Grid
// read the flat vector
buf.resize(size);
if (size * sizeof(Element) > dataSetThres_)
if (size > dataSetThres_)
{
H5NS::DataSet dataSet;

View File

@ -1,271 +0,0 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: Grid/util/EigenUtil.h
Copyright (C) 2019
Author: Michael Marshall <michael.marshall@ed.ac.uk>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#ifndef GRID_UTIL_EIGENUTIL_H
#define GRID_UTIL_EIGENUTIL_H
#include <Grid/tensors/Tensor_traits.h>
#include <Grid/Eigen/unsupported/CXX11/Tensor>
namespace Grid {
// Custom iterator for Eigen tensors
namespace EigenUtil {
template <typename ETensor, bool bConst> // Is the tensor constant
class TensorIterator_raw{
public:
using Index = typename ETensor::Index;
using Scalar = typename ETensor::Scalar;
using FullIndex = std::array<Index, ETensor::NumIndices>;
const ETensor * pET;
const Index end; // same as pET->size()
Index position; // position (memory order)
Index Seq; // sequence (what our position would be if we were column major)
FullIndex indexPos;
FullIndex indexSize;
inline TensorIterator_raw( ETensor & eT, Index pos = 0 ) : pET{&eT}, position{pos}, Seq{pos}, end{pET->size()} {
for( int i = 0 ; i < ETensor::NumIndices ; i++ ) {
indexPos[i] = 0;
indexSize[i] = pET->dimension(i);
}
}
inline TensorIterator_raw<ETensor, bConst> & operator++() {
auto sz = pET->size();
if( position < sz ) {
position++;
if( ETensor::Options & Eigen::RowMajor ) {
for( int i = ETensor::NumIndices - 1; i != -1 && ++indexPos[i] == indexSize[i]; i-- )
indexPos[i] = 0;
Seq++;
} else {
for( int i = 0; i < ETensor::NumIndices && ++indexPos[i] == indexSize[i]; i++ )
indexPos[i] = 0;
Seq = 0;
for( int i = 0; i < ETensor::NumIndices; i++ ) {
Seq *= indexSize[i];
Seq += indexPos[i];
}
}
}
return * this;
}
inline typename std::conditional<bConst,const Scalar &,Scalar &>::type operator*() const {
assert( position >= 0 && position < pET->size() && "Attempt to access Eigen tensor iterator out of range" );
return ( ( typename std::conditional<bConst,const Scalar *,Scalar*>::type ) pET->data() )[position];
}
inline bool operator!=(const TensorIterator_raw<ETensor, bConst> &r)
{ return pET == nullptr || pET != r.pET || position != r.position; }
// These functions aren't required for iterators, but they make using them easier
inline bool AtEnd() { return position == end; }
inline void DumpIndex(void) {
for( auto dim : indexPos )
std::cout << "[" << dim << "]";
}
};
}
}
// The only way I could get these iterators to work is to put the begin() and end() functions in the Eigen namespace
// So if Eigen ever defines these, we'll have a conflict and have to change this
namespace Eigen {
template <typename ETensor> using TensorIterator = Grid::EigenUtil::TensorIterator_raw< ETensor, false>;
template <typename ETensor> using TensorIteratorConst = Grid::EigenUtil::TensorIterator_raw<const ETensor, true>;
template <typename ETensor>
inline typename std::enable_if<Grid::EigenIO::is_tensor<ETensor>::value, TensorIterator<ETensor>>::type
begin( ETensor & ET ) { return TensorIterator<ETensor>(ET); }
template <typename ETensor>
inline typename std::enable_if<Grid::EigenIO::is_tensor<ETensor>::value, TensorIterator<ETensor>>::type
end( ETensor & ET ) { return TensorIterator<ETensor>(ET, ET.size()); }
template <typename ETensor>
inline typename std::enable_if<Grid::EigenIO::is_tensor<ETensor>::value, TensorIteratorConst<ETensor>>::type
begin( const ETensor & ET ) { return TensorIteratorConst<ETensor>(ET); }
template <typename ETensor>
inline typename std::enable_if<Grid::EigenIO::is_tensor<ETensor>::value, TensorIteratorConst<ETensor>>::type
end( const ETensor & ET ) { return TensorIteratorConst<ETensor>(ET, ET.size()); }
}
namespace Grid {
// for_all helper function to call the lambda for scalar
template <typename ETensor, typename Lambda>
typename std::enable_if<EigenIO::is_tensor_of_scalar<ETensor>::value, void>::type
for_all_do_lambda( Lambda lambda, typename ETensor::Scalar &scalar, typename ETensor::Index &Seq,
const std::array<typename ETensor::Index, ETensor::NumIndices> &MyIndex,
const std::array<int, EigenIO::Traits<ETensor>::Rank> &DimGridTensor,
std::array<int, EigenIO::Traits<ETensor>::Rank> &GridTensorIndex)
{
lambda( scalar, Seq++, MyIndex, GridTensorIndex );
}
// for_all helper function to call the lambda for container
template <typename ETensor, typename Lambda>
typename std::enable_if<EigenIO::is_tensor_of_container<ETensor>::value, void>::type
for_all_do_lambda( Lambda lambda, typename ETensor::Scalar &container, typename ETensor::Index &Seq,
const std::array<typename ETensor::Index, ETensor::NumIndices> &MyIndex,
const std::array<int, EigenIO::Traits<ETensor>::Rank> &DimGridTensor,
std::array<int, EigenIO::Traits<ETensor>::Rank> &GridTensorIndex)
{
using Traits = EigenIO::Traits<ETensor>;
const int InnerRank = Traits::Rank;
for( typename Traits::scalar_type &Source : container ) {
lambda(Source, Seq++, MyIndex, GridTensorIndex );
// Now increment SubIndex
for( int i = InnerRank - 1; i != -1 && ++GridTensorIndex[i] == DimGridTensor[i]; i-- )
GridTensorIndex[i] = 0;
}
}
// Calls a lamda (passing index and sequence number) for every member of an Eigen::Tensor
// For efficiency, iteration proceeds in memory order,
// ... but parameters guaranteed to be the same regardless of memory order
template <typename ETensor, typename Lambda>
typename std::enable_if<EigenIO::is_tensor<ETensor>::value, void>::type
for_all( ETensor &ET, Lambda lambda )
{
using Scalar = typename ETensor::Scalar; // This could be a Container - we'll check later
using Index = typename ETensor::Index;
using Traits = EigenIO::Traits<ETensor>;
// Check that the number of elements in the container is the product of tensor dimensions
const Index NumScalars = ET.size();
assert( NumScalars > 0 && "EigenUtil: tensor has no elements" );
Index ScalarElementCount{1};
const int rank{ETensor::NumIndices};
std::array<Index, rank> DimTensor, MyIndex;
for(int i = 0; i < rank; i++ ) {
DimTensor[i] = ET.dimension(i);
ScalarElementCount *= DimTensor[i];
MyIndex[i] = 0;
}
assert( NumScalars == ScalarElementCount && "EigenUtil: tensor size not product of dimensions" );
// Save the GridTensor dimensions
const int InnerRank{Traits::Rank};
std::array<int, InnerRank> DimGridTensor, GridTensorIndex;
for(int i = 0; i < InnerRank; i++ ) {
DimGridTensor[i] = Traits::Dimension(i);
GridTensorIndex[i] = 0;
}
// Now walk the tensor in memory order
Index Seq = 0;
Scalar * pScalar = ET.data();
for( Index j = 0; j < NumScalars; j++ ) {
for_all_do_lambda<ETensor, Lambda>( lambda, * pScalar, Seq, MyIndex, DimGridTensor, GridTensorIndex );
// Now increment the index to pass to the lambda (bearing in mind we're walking in memory order)
if( ETensor::Options & Eigen::RowMajor ) {
for( int i = rank - 1; i != -1 && ++MyIndex[i] == DimTensor[i]; i-- )
MyIndex[i] = 0;
} else {
for( int i = 0; i < rank && ++MyIndex[i] == DimTensor[i]; i++ )
MyIndex[i] = 0;
Seq = 0;
for( int i = 0; i < rank; i++ ) {
Seq *= DimTensor[i];
Seq += MyIndex[i];
}
Seq *= Traits::count;
}
pScalar++;
}
}
// Sequential initialisation of tensors up to specified precision
// Would have preferred to define template variables for this, but that's c++ 17
template <typename ETensor>
typename std::enable_if<EigenIO::is_tensor<ETensor>::value && !EigenIO::Traits<ETensor>::is_complex>::type
SequentialInit( ETensor &ET, typename EigenIO::Traits<ETensor>::scalar_type Inc = 1, unsigned short Precision = 0 )
{
using Traits = EigenIO::Traits<ETensor>;
using scalar_type = typename Traits::scalar_type;
using Index = typename ETensor::Index;
for_all( ET, [&](scalar_type &c, Index n, const std::array<Index, ETensor::NumIndices> &TensorIndex,
const std::array<int, Traits::Rank> &ScalarIndex ) {
scalar_type x = Inc * static_cast<scalar_type>(n);
if( Precision ) {
std::stringstream s;
s << std::setprecision(Precision) << x;
s >> x;
}
c = x;
} );
}
template <typename ETensor>
typename std::enable_if<EigenIO::is_tensor<ETensor>::value && EigenIO::Traits<ETensor>::is_complex>::type
SequentialInit( ETensor &ET, typename EigenIO::Traits<ETensor>::scalar_type Inc={1,-1}, unsigned short Precision = 0 )
{
using Traits = EigenIO::Traits<ETensor>;
using scalar_type = typename Traits::scalar_type;
using Index = typename ETensor::Index;
for_all( ET, [&](scalar_type &c, Index n, const std::array<Index, ETensor::NumIndices> &TensorIndex,
const std::array<int, Traits::Rank> &ScalarIndex ) {
auto re = Inc.real();
auto im = Inc.imag();
re *= n;
im *= n;
if( Precision ) {
std::stringstream s;
s << std::setprecision(Precision) << re;
s >> re;
s.clear();
s << im;
s >> im;
}
c = scalar_type(re,im);
} );
}
// Helper to dump a tensor
template <typename T>
typename std::enable_if<EigenIO::is_tensor<T>::value, void>::type
dump_tensor(T &t, const char * pName = nullptr)
{
using Traits = EigenIO::Traits<T>;
using scalar_type = typename Traits::scalar_type;
using Index = typename T::Index;
const int rank{T::NumIndices};
const auto &dims = t.dimensions();
std::cout << "Dumping rank " << rank + Traits::Rank << ((T::Options & Eigen::RowMajor) ? ", row" : ", column") << "-major tensor ";
if( pName )
std::cout << pName;
for( int i = 0 ; i < rank; i++ ) std::cout << "[" << dims[i] << "]";
for( int i = 0 ; i < Traits::Rank; i++ ) std::cout << "(" << Traits::Dimension(i) << ")";
std::cout << " in memory order:" << std::endl;
for( auto it = begin(t); !it.AtEnd(); ++it ) {
std::cout << " ";
it.DumpIndex();
std::cout << " = " << (const typename T::Scalar)(*it) << std::endl;
}
std::cout << "========================================" << std::endl;
}
template <typename T>
typename std::enable_if<!EigenIO::is_tensor<T>::value, void>::type
dump_tensor(T &t, const char * pName = nullptr)
{
std::cout << "Dumping non-tensor object ";
if( pName ) std::cout << pName;
std::cout << "=" << t;
}
}
#endif

View File

@ -29,7 +29,6 @@ Author: Michael Marshall <michael.marshall@ed.ac.uk>
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include <Grid/util/EigenUtil.h>
#include <typeinfo>
using namespace Grid;
@ -103,13 +102,22 @@ void ioTest(const std::string &filename, const O &object, const std::string &nam
bool good = Serializable::CompareMember(object, *buf);
if (!good) {
std::cout << " failure!" << std::endl;
if (EigenIO::is_tensor<O>::value)
dump_tensor(*buf);
exit(EXIT_FAILURE);
}
std::cout << " done." << std::endl;
}
// The only way I could get these iterators to work is to put the begin() and end() functions in the Eigen namespace
// So if Eigen ever defines these, we'll have a conflict and have to change this
namespace Eigen {
template <typename ET>
inline typename std::enable_if<EigenIO::is_tensor<ET>::value, typename EigenIO::Traits<ET>::scalar_type *>::type
begin( ET & et ) { return reinterpret_cast<typename Grid::EigenIO::Traits<ET>::scalar_type *>(et.data()); }
template <typename ET>
inline typename std::enable_if<EigenIO::is_tensor<ET>::value, typename EigenIO::Traits<ET>::scalar_type *>::type
end( ET & et ) { return begin(et) + et.size() * EigenIO::Traits<ET>::count; }
}
// Perform I/O tests on a range of tensor types
// Test coverage: scalars, complex and GridVectors in single, double and default precision
class TensorIO : public Serializable {
@ -133,14 +141,14 @@ class TensorIO : public Serializable {
void Init(unsigned short Precision)
{
SequentialInit(Perambulator1, Flag, Precision);
SequentialInit(Perambulator2, Flag, Precision);
SequentialInit(tensorR5, FlagR, Precision);
SequentialInit(tensorRank3, FlagF, Precision);
SequentialInit(tensor_9_4_2, FlagTS, Precision);
for( auto &s : Perambulator1 ) s = Flag;
for( auto &s : Perambulator2 ) s = Flag;
for( auto &s : tensorR5 ) s = FlagR;
for( auto &s : tensorRank3 ) s = FlagF;
for( auto &s : tensor_9_4_2 ) s = FlagTS;
for( auto &t : atensor_9_4_2 )
SequentialInit(t, FlagTS, Precision);
SequentialInit(MyLSCTensor, Flag, Precision);
for( auto &s : t ) s = FlagTS;
for( auto &s : MyLSCTensor ) s = Flag;
}
// Perform an I/O test for a single Eigen tensor (of any type)
@ -152,7 +160,7 @@ class TensorIO : public Serializable {
using Traits = EigenIO::Traits<T>;
using scalar_type = typename Traits::scalar_type;
std::unique_ptr<T> pTensor{new T(otherDims...)};
SequentialInit( * pTensor, Flag, Precision );
for( auto &s : * pTensor ) s = Flag;
filename = pszFilePrefix + std::to_string(++TestNum) + "_" + MyTypeName + pszExtension;
ioTest<W, R, T>(filename, * pTensor, MyTypeName, MyTypeName);
}
@ -191,41 +199,15 @@ public:
// Rank 1 tensor containing a single integer
using TensorSingle = Eigen::TensorFixedSize<Integer, Eigen::Sizes<1>>;
TestOne<WTR_, RDR_, TensorSingle>( TEST_PARAMS( TensorSingle ), 7 ); // lucky!
// Rather convoluted way of defining a single complex number
using TensorSimple = Eigen::Tensor<iMatrix<TestScalar,1>, 6>;
// Rather convoluted way of defining four complex numbers
using TensorSimple = Eigen::Tensor<iMatrix<TestScalar,2>, 6>;
using I = typename TensorSimple::Index; // NB: Never specified, so same for all my test tensors
// Try progressively more complicated tensors
TestOne<WTR_, RDR_, TensorSimple, I,I,I,I,I,I>( TEST_PARAMS( TensorSimple ), FlagTS, 1,1,1,1,1,1 );
TestOne<WTR_, RDR_, TensorRank3, I, I, I>( TEST_PARAMS( TensorRank3 ), FlagF, 6, 3, 2 );
TestOne<WTR_, RDR_, Tensor942>(TEST_PARAMS( Tensor942 ), FlagTS);
TestOne<WTR_, RDR_, LSCTensor>(TEST_PARAMS( LSCTensor ), Flag );
// Now see whether we can write a tensor in one memory order and read back in the other
{
TestOne<WTR_, RDR_, TensorR5>(TEST_PARAMS( TensorR5 ), FlagR);
std::cout << " Testing alternate memory order read ... ";
TensorR5Alt t2;
RDR_ reader(filename);
::Grid::read(reader, "TensorR5", t2);
bool good = true;
TensorR5 cf;
SequentialInit( cf, FlagR, Precision );
for_all( t2, [&](typename EigenIO::Traits<TensorR5Alt>::scalar_type c, I n,
const std::array<I, TensorR5Alt::NumIndices> &TensorIndex,
const std::array<int, EigenIO::Traits<TensorR5Alt>::Rank> &GridTensorIndex ){
Real &r = cf(TensorIndex);
if( c != r ){
good = false;
std::cout << "\nError: " << n << ": " << c << " != " << r;
}
} );
if (!good) {
std::cout << std::endl;
dump_tensor(t2,"t2");
exit(EXIT_FAILURE);
}
std::cout << " done." << std::endl;
}
TestOne<WTR_, RDR_, TensorR5>(TEST_PARAMS( TensorR5 ), FlagR);
// Now test a serialisable object containing a number of tensors
{
static const char MyTypeName[] = "TensorIO";
@ -247,10 +229,10 @@ public:
}
};
const Real TensorIO::FlagR {-1.001};
const Complex TensorIO::Flag {1,-3.1415927};
const ComplexF TensorIO::FlagF {1,-3.1415927};
const TensorIO::TestScalar TensorIO::FlagTS{1,-3.1415927};
const Real TensorIO::FlagR {1};
const Complex TensorIO::Flag {1,-1};
const ComplexF TensorIO::FlagF {1,-1};
const TensorIO::TestScalar TensorIO::FlagTS{1,-1};
const char * const TensorIO::pszFilePrefix = "tensor_";
template <typename T>