mrange: different members for blockSizes and lexBlockSizes
This commit is contained in:
parent
2745eee0ff
commit
2f5f29f577
18 changed files with 326 additions and 213 deletions
|
@ -83,6 +83,9 @@ namespace CNORXZ
|
|||
F = Functional, Map,...
|
||||
***/
|
||||
|
||||
// default template parameter
|
||||
class None {};
|
||||
|
||||
// definition: base/dtype.h
|
||||
class DType;
|
||||
|
||||
|
@ -174,8 +177,8 @@ namespace CNORXZ
|
|||
class MRange; // multi range
|
||||
|
||||
// definition: ranges/mrange.h
|
||||
template <class... Indices>
|
||||
class MIndex;
|
||||
template <class BlockType, class... Indices>
|
||||
class GMIndex;
|
||||
|
||||
// definition: ranges/xindex.h
|
||||
class XIndexBase; // dynamic index wrapper
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace CNORXZ
|
|||
template <class Xpr, class F>
|
||||
decltype(auto) CIndex::ifor(const Xpr& xpr, F&& f) const
|
||||
{
|
||||
return For<0,Xpr,F>(this->pmax(), this->id(), xpr, std::forward<F>(f));
|
||||
return For<0,Xpr,F>(this->pmax().val(), this->id(), xpr, std::forward<F>(f));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ namespace CNORXZ
|
|||
CIndex& operator-=(Int n);
|
||||
|
||||
SizeT lex() const;
|
||||
SizeT pmax() const;
|
||||
SizeT lmax() const;
|
||||
UPos pmax() const;
|
||||
UPos lmax() const;
|
||||
IndexId<0> id() const;
|
||||
|
||||
SizeT operator*() const;
|
||||
|
|
|
@ -33,8 +33,8 @@ namespace CNORXZ
|
|||
DIndex& operator-=(Int n);
|
||||
|
||||
SizeT lex() const;
|
||||
SizeT pmax() const;
|
||||
SizeT lmax() const;
|
||||
UPos pmax() const;
|
||||
UPos lmax() const;
|
||||
IndexId<0> id() const;
|
||||
|
||||
DType operator*() const;
|
||||
|
|
|
@ -30,8 +30,8 @@ namespace CNORXZ
|
|||
|
||||
SizeT pos() const; // 'memory' pos
|
||||
SizeT lex() const { return THIS().lex(); } // lexicographic pos
|
||||
SizeT pmax() const { return THIS().pmax(); } // memory pos max
|
||||
SizeT lmax() const { return THIS().lmax(); } // lexicographic pos max
|
||||
SizeT pmax() const { return static_cast<SizeT>(THIS().pmax()); } // memory pos max
|
||||
SizeT lmax() const { return static_cast<SizeT>(THIS().lmax()); } // lexicographic pos max
|
||||
PtrId ptrId() const;
|
||||
decltype(auto) id() const { return THIS().id(); }
|
||||
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
namespace CNORXZ
|
||||
{
|
||||
/***********************
|
||||
* MIndex (private) *
|
||||
***********************/
|
||||
/************************
|
||||
* GMIndex (private) *
|
||||
************************/
|
||||
|
||||
template <class... Indices>
|
||||
template <class BlockType, class... Indices>
|
||||
template <SizeT... Is>
|
||||
constexpr decltype(auto) MIndex<Indices...>::mkIPack(Isq<Is...> is) const
|
||||
constexpr decltype(auto) GMIndex<BlockType,Indices...>::mkIPack(Isq<Is...> is) const
|
||||
{
|
||||
static_assert(sizeof...(Is) == NI,
|
||||
"sequence sioze does not match number of indices");
|
||||
|
@ -21,55 +21,87 @@ namespace CNORXZ
|
|||
return std::make_tuple( std::make_shared<Indices>( mRange->sub(Is) )... );
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
template <class BlockType, class... Indices>
|
||||
constexpr decltype(auto) GMIndex<BlockType,Indices...>::mkLMax(const IndexPack& ipack)
|
||||
{
|
||||
return iter<0,NI>( [&](auto i) { return std::get<i>(ipack)->lmax(); },
|
||||
[](auto... e) { return (e * ...); });
|
||||
}
|
||||
|
||||
template <class BlockType, class... Indices>
|
||||
constexpr decltype(auto) GMIndex<BlockType,Indices...>::mkPMax(const IndexPack& ipack, const BlockType& blockSizes)
|
||||
{
|
||||
if constexpr(std::is_same<BlockType,None>::value){
|
||||
return mkLMax(ipack);
|
||||
}
|
||||
else {
|
||||
return iter<0,NI>
|
||||
( [&](auto i)
|
||||
{ return (std::get<i>(ipack)->pmax() - SPos<1>()) * std::get<i>(blockSizes); },
|
||||
[](auto... e) { return (e + ...); }) + SPos<1>();
|
||||
}
|
||||
}
|
||||
|
||||
template <class BlockType, class... Indices>
|
||||
template <SizeT... Is>
|
||||
constexpr decltype(auto) MIndex<Indices...>::mkBlockSizes(const IndexPack& ipack, Isq<Is...> is)
|
||||
constexpr decltype(auto) GMIndex<BlockType,Indices...>::mkLexBlockSizes(const IndexPack& ipack, Isq<Is...> is)
|
||||
{
|
||||
return std::make_tuple
|
||||
( iter<Is,NI>
|
||||
// replace UPos by SPos where possible !!!
|
||||
( [&](auto i) { return UPos(std::get<i>(ipack)->pmax()); },
|
||||
( [&](auto i) { return std::get<i>(ipack)->pmax(); },
|
||||
[&](const auto&... as) { return (as * ...); } )...,
|
||||
SPos<1>() );
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
template <class BlockType, class... Indices>
|
||||
template <SizeT I>
|
||||
inline void MIndex<Indices...>::up()
|
||||
inline void GMIndex<BlockType,Indices...>::up()
|
||||
{
|
||||
auto& i = std::get<I>(mIPack);
|
||||
if constexpr(I != 0){
|
||||
if(i->lex() == i->lmax()-1){
|
||||
IB::mPos -= std::get<I>(mBlockSizes).val() * i->pos();
|
||||
if(i->lex() == i->lmax().val()-1){
|
||||
IB::mPos -= std::get<I>(blockSizes()).val() * i->pos();
|
||||
if constexpr(not std::is_same<BlockType,None>::value){
|
||||
mLex -= std::get<I>(lexBlockSizes()).val() * i->lex();
|
||||
}
|
||||
(*i) = 0;
|
||||
up<I-1>();
|
||||
return;
|
||||
}
|
||||
}
|
||||
IB::mPos += std::get<I>(mBlockSizes).val();
|
||||
IB::mPos += std::get<I>(blockSizes()).val();
|
||||
if constexpr(not std::is_same<BlockType,None>::value){
|
||||
mLex += std::get<I>(lexBlockSizes()).val();
|
||||
}
|
||||
++(*i);
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
template <class BlockType, class... Indices>
|
||||
template <SizeT I>
|
||||
inline void MIndex<Indices...>::down()
|
||||
inline void GMIndex<BlockType,Indices...>::down()
|
||||
{
|
||||
auto& i = std::get<I>(mIPack);
|
||||
if constexpr(I != 0){
|
||||
if(i->lex() == 0){
|
||||
(*i) = i->lmax()-1;
|
||||
IB::mPos += std::get<I>(mBlockSizes).val() * i->pos();
|
||||
(*i) = i->lmax().val()-1;
|
||||
IB::mPos += std::get<I>(blockSizes()).val() * i->pos();
|
||||
if constexpr(not std::is_same<BlockType,None>::value){
|
||||
mLex += std::get<I>(lexBlockSizes()).val() * i->lex();
|
||||
}
|
||||
down<I-1>();
|
||||
return;
|
||||
}
|
||||
}
|
||||
IB::mPos -= std::get<I>(mBlockSizes).val();
|
||||
IB::mPos -= std::get<I>(blockSizes()).val();
|
||||
if constexpr(not std::is_same<BlockType,None>::value){
|
||||
mLex -= std::get<I>(lexBlockSizes()).val();
|
||||
}
|
||||
--(*i);
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
template <class BlockType, class... Indices>
|
||||
template <SizeT I, class Xpr, class F>
|
||||
constexpr decltype(auto) MIndex<Indices...>::mkIFor(const Xpr& xpr, F&& f) const
|
||||
constexpr decltype(auto) GMIndex<BlockType,Indices...>::mkIFor(const Xpr& xpr, F&& f) const
|
||||
{
|
||||
if constexpr(I == sizeof...(Indices)-1){
|
||||
return std::get<I>(mIPack)->ifor(xpr,f);
|
||||
|
@ -79,169 +111,202 @@ namespace CNORXZ
|
|||
}
|
||||
}
|
||||
|
||||
/**************
|
||||
* MIndex *
|
||||
**************/
|
||||
/***************
|
||||
* GMIndex *
|
||||
***************/
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...>::MIndex(const MIndex& i) :
|
||||
IndexInterface<MIndex<Indices...>,Tuple<typename Indices::MetaType...>>(0),
|
||||
template <class BlockType, class... Indices>
|
||||
constexpr GMIndex<BlockType,Indices...>::GMIndex(const GMIndex& i) :
|
||||
IndexInterface<GMIndex<BlockType,Indices...>,Tuple<typename Indices::MetaType...>>(0),
|
||||
mRange(rangeCast<RangeType>(i.range())),
|
||||
mIPack(mkIPack(Isqr<0,NI>{})),
|
||||
mBlockSizes(mkBlockSizes(mIPack,Isqr<0,NI-1>{}))
|
||||
mLexBlockSizes(mkLexBlockSizes(mIPack,Isqr<0,NI-1>{})),
|
||||
mBlockSizes(i.mBlockSizes),
|
||||
mLMax(mkLMax(mIPack)),
|
||||
mPMax(mkPMax(mIPack,mBlockSizes))
|
||||
{
|
||||
mPMax = iter<0,NI>( [&](auto i) { return std::get<i>(mIPack)->pmax(); },
|
||||
[](auto... e) { return (e * ...); });
|
||||
*this = i.pos();
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...>& MIndex<Indices...>::operator=(const MIndex& i)
|
||||
template <class BlockType, class... Indices>
|
||||
constexpr GMIndex<BlockType,Indices...>& GMIndex<BlockType,Indices...>::operator=(const GMIndex& i)
|
||||
{
|
||||
IndexInterface<MIndex<Indices...>,Tuple<typename Indices::MetaType...>>::operator=(0);
|
||||
IndexInterface<GMIndex<BlockType,Indices...>,Tuple<typename Indices::MetaType...>>::operator=(0);
|
||||
mRange = rangeCast<RangeType>(i.range());
|
||||
mIPack = mkIPack(Isqr<0,NI>{});
|
||||
mBlockSizes = mkBlockSizes(mIPack,Isqr<0,NI-1>{});
|
||||
mPMax = iter<0,NI>( [&](auto i) { return std::get<i>(mIPack)->pmax(); },
|
||||
[](auto... e) { return (e * ...); });
|
||||
mLexBlockSizes = mkLexBlockSizes(mIPack,Isqr<0,NI-1>{});
|
||||
mBlockSizes = i.mBlockSizes;
|
||||
mLMax = mkLMax(mIPack);
|
||||
mPMax = mkPMax(mIPack,mBlockSizes);
|
||||
return *this = i.pos();
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...>::MIndex(const RangePtr& range, SizeT lexpos) :
|
||||
IndexInterface<MIndex<Indices...>,Tuple<typename Indices::MetaType...>>(0),
|
||||
template <class BlockType, class... Indices>
|
||||
constexpr GMIndex<BlockType,Indices...>::GMIndex(const RangePtr& range, SizeT lexpos) :
|
||||
IndexInterface<GMIndex<BlockType,Indices...>,Tuple<typename Indices::MetaType...>>(0),
|
||||
mRange(rangeCast<RangeType>(range)),
|
||||
mIPack(mkIPack(Isqr<0,NI>{})),
|
||||
mBlockSizes(mkBlockSizes(mIPack,Isqr<0,NI-1>{}))
|
||||
mLexBlockSizes(mkLexBlockSizes(mIPack,Isqr<0,NI-1>{})),
|
||||
mBlockSizes(),
|
||||
mLMax(mkLMax(mIPack)),
|
||||
mPMax(mkPMax(mIPack,mBlockSizes))
|
||||
{
|
||||
mPMax = iter<0,NI>( [&](auto i) { return std::get<i>(mIPack)->pmax(); },
|
||||
[](auto... e) { return (e * ...); });
|
||||
*this = lexpos;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...>& MIndex<Indices...>::operator=(SizeT lexpos)
|
||||
template <class BlockType, class... Indices>
|
||||
constexpr GMIndex<BlockType,Indices...>::GMIndex(const RangePtr& range, const BlockType& blockSizes, SizeT lexpos) :
|
||||
IndexInterface<GMIndex<BlockType,Indices...>,Tuple<typename Indices::MetaType...>>(0),
|
||||
mRange(rangeCast<RangeType>(range)),
|
||||
mIPack(mkIPack(Isqr<0,NI>{})),
|
||||
mLexBlockSizes(mkLexBlockSizes(mIPack,Isqr<0,NI-1>{})),
|
||||
mBlockSizes(blockSizes),
|
||||
mLMax(mkLMax(mIPack)),
|
||||
mPMax(mkPMax(mIPack,mBlockSizes))
|
||||
{
|
||||
// Adapt in GMIndex
|
||||
IB::mPos = lexpos;
|
||||
iter<0,NI>( [&](auto i) { *std::get<i>(mIPack) = (IB::mPos / std::get<i>(mBlockSizes).val()) % std::get<i>(mIPack)->pmax(); }, NoF{} );
|
||||
*this = lexpos;
|
||||
}
|
||||
|
||||
template <class BlockType, class... Indices>
|
||||
GMIndex<BlockType,Indices...>& GMIndex<BlockType,Indices...>::operator=(SizeT lexpos)
|
||||
{
|
||||
if(lexpos >= lmax().val()){
|
||||
if constexpr(not std::is_same<BlockType,None>::value){ mLex = lmax().val(); }
|
||||
IB::mPos = pmax().val();
|
||||
return *this;
|
||||
}
|
||||
if constexpr(not std::is_same<BlockType,None>::value){ mLex = lexpos; }
|
||||
IB::mPos = iter<0,NI>( [&](auto i) {
|
||||
*std::get<i>(mIPack) = (lex() / std::get<i>(lexBlockSizes()).val()) % std::get<i>(mIPack)->lmax().val();
|
||||
return std::get<i>(blockSizes()).val() * std::get<i>(mIPack)->pos();
|
||||
}, [](const auto&... e) { return (e + ...); } );
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...>& MIndex<Indices...>::operator++()
|
||||
template <class BlockType, class... Indices>
|
||||
GMIndex<BlockType,Indices...>& GMIndex<BlockType,Indices...>::operator++()
|
||||
{
|
||||
// End state is defined by high-index being end while all other indices are zero
|
||||
if(lex() != lmax()){
|
||||
if(lex() == lmax().val()-1){
|
||||
return *this = lmax().val();
|
||||
}
|
||||
if(lex() != lmax().val()){
|
||||
up<NI-1>();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...>& MIndex<Indices...>::operator--()
|
||||
template <class BlockType, class... Indices>
|
||||
GMIndex<BlockType,Indices...>& GMIndex<BlockType,Indices...>::operator--()
|
||||
{
|
||||
if(lex() == lmax().val()){
|
||||
return *this = lmax().val()-1;
|
||||
}
|
||||
if(lex() != 0){
|
||||
down<NI-1>();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...> MIndex<Indices...>::operator+(Int n) const
|
||||
template <class BlockType, class... Indices>
|
||||
GMIndex<BlockType,Indices...> GMIndex<BlockType,Indices...>::operator+(Int n) const
|
||||
{
|
||||
MIndex o(*this);
|
||||
GMIndex o(*this);
|
||||
return o += n;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...> MIndex<Indices...>::operator-(Int n) const
|
||||
template <class BlockType, class... Indices>
|
||||
GMIndex<BlockType,Indices...> GMIndex<BlockType,Indices...>::operator-(Int n) const
|
||||
{
|
||||
MIndex o(*this);
|
||||
GMIndex o(*this);
|
||||
return o -= n;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...>& MIndex<Indices...>::operator+=(Int n)
|
||||
template <class BlockType, class... Indices>
|
||||
GMIndex<BlockType,Indices...>& GMIndex<BlockType,Indices...>::operator+=(Int n)
|
||||
{
|
||||
if(-n > static_cast<long int>(lex())){
|
||||
(*this) = 0;
|
||||
}
|
||||
const SizeT p = lex() + n;
|
||||
if(p > lmax()){
|
||||
(*this) = lmax();
|
||||
if(p > lmax().val()){
|
||||
(*this) = lmax().val();
|
||||
}
|
||||
(*this) = p;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...>& MIndex<Indices...>::operator-=(Int n)
|
||||
template <class BlockType, class... Indices>
|
||||
GMIndex<BlockType,Indices...>& GMIndex<BlockType,Indices...>::operator-=(Int n)
|
||||
{
|
||||
if(n > static_cast<long int>(lex())){
|
||||
(*this) = 0;
|
||||
}
|
||||
const SizeT p = lex() + n;
|
||||
if(p > lmax()){
|
||||
(*this) = lmax();
|
||||
if(p > lmax().val()){
|
||||
(*this) = lmax().val();
|
||||
}
|
||||
(*this) = p;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
SizeT MIndex<Indices...>::lex() const
|
||||
template <class BlockType, class... Indices>
|
||||
SizeT GMIndex<BlockType,Indices...>::lex() const
|
||||
{
|
||||
if constexpr(std::is_same<BlockType,None>::value){
|
||||
return IB::mPos;
|
||||
}
|
||||
else {
|
||||
return mLex;
|
||||
}
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
SizeT MIndex<Indices...>::pmax() const
|
||||
template <class BlockType, class... Indices>
|
||||
constexpr decltype(auto) GMIndex<BlockType,Indices...>::pmax() const
|
||||
{
|
||||
return mPMax;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
SizeT MIndex<Indices...>::lmax() const
|
||||
template <class BlockType, class... Indices>
|
||||
constexpr decltype(auto) GMIndex<BlockType,Indices...>::lmax() const
|
||||
{
|
||||
return mPMax;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
IndexId<0> MIndex<Indices...>::id() const
|
||||
template <class BlockType, class... Indices>
|
||||
IndexId<0> GMIndex<BlockType,Indices...>::id() const
|
||||
{
|
||||
return IndexId<0>(this->ptrId());
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
typename MIndex<Indices...>::MetaType MIndex<Indices...>::operator*() const
|
||||
template <class BlockType, class... Indices>
|
||||
typename GMIndex<BlockType,Indices...>::MetaType GMIndex<BlockType,Indices...>::operator*() const
|
||||
{
|
||||
return meta();
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
SizeT MIndex<Indices...>::dim() const
|
||||
template <class BlockType, class... Indices>
|
||||
constexpr SizeT GMIndex<BlockType,Indices...>::dim() const
|
||||
{
|
||||
return NI;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
Sptr<typename MIndex<Indices...>::RangeType> MIndex<Indices...>::range() const
|
||||
template <class BlockType, class... Indices>
|
||||
Sptr<typename GMIndex<BlockType,Indices...>::RangeType> GMIndex<BlockType,Indices...>::range() const
|
||||
{
|
||||
return mRange;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
template <class BlockType, class... Indices>
|
||||
template <SizeT I>
|
||||
decltype(auto) MIndex<Indices...>::stepSize(const IndexId<I>& id) const
|
||||
decltype(auto) GMIndex<BlockType,Indices...>::stepSize(const IndexId<I>& id) const
|
||||
{
|
||||
return iter<0,NI>
|
||||
( [&](auto i) { return std::get<i>(mIPack)->stepSize(id) * std::get<i>(mBlockSizes); },
|
||||
( [&](auto i) { return std::get<i>(mIPack)->stepSize(id) * std::get<i>(blockSizes()); },
|
||||
[](const auto&... ss) { return ( ss + ... ); });
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
String MIndex<Indices...>::stringMeta() const
|
||||
template <class BlockType, class... Indices>
|
||||
String GMIndex<BlockType,Indices...>::stringMeta() const
|
||||
{
|
||||
const String blim = "(";
|
||||
const String elim = ")";
|
||||
|
@ -253,51 +318,62 @@ namespace CNORXZ
|
|||
} );
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
typename MIndex<Indices...>::MetaType MIndex<Indices...>::meta() const
|
||||
template <class BlockType, class... Indices>
|
||||
typename GMIndex<BlockType,Indices...>::MetaType GMIndex<BlockType,Indices...>::meta() const
|
||||
{
|
||||
return iter<0,NI>( [&](auto i) { return std::get<i>(mIPack)->meta(); },
|
||||
[](const auto&... xs) { return std::make_tuple(xs...); } );
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...>& MIndex<Indices...>::at(const MetaType& metaPos)
|
||||
template <class BlockType, class... Indices>
|
||||
GMIndex<BlockType,Indices...>& GMIndex<BlockType,Indices...>::at(const MetaType& metaPos)
|
||||
{
|
||||
iter<0,NI>( [&](auto i) { std::get<i>(mIPack)->at( std::get<i>(metaPos) ); }, NoF {} );
|
||||
IB::mPos = iter<0,NI>
|
||||
( [&](auto i) { return std::get<i>(mIPack)->pos()*std::get<i>(mBlockSizes).val(); },
|
||||
( [&](auto i) { return std::get<i>(mIPack)->pos()*std::get<i>(blockSizes()).val(); },
|
||||
[](const auto&... xs) { return (xs + ...); });
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
template <class BlockType, class... Indices>
|
||||
template <class Xpr, class F>
|
||||
constexpr decltype(auto) MIndex<Indices...>::ifor(const Xpr& xpr, F&& f) const
|
||||
constexpr decltype(auto) GMIndex<BlockType,Indices...>::ifor(const Xpr& xpr, F&& f) const
|
||||
{
|
||||
return mkIFor<0>(xpr, f);
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
MIndex<Indices...>& MIndex<Indices...>::operator()(const Sptr<MIndex>& mi)
|
||||
template <class BlockType, class... Indices>
|
||||
GMIndex<BlockType,Indices...>& GMIndex<BlockType,Indices...>::operator()(const Sptr<GMIndex>& mi)
|
||||
{
|
||||
mIPack = mi.mIPack;
|
||||
IB::mPos = iter<0,NI>
|
||||
( [&](auto i) { return std::get<i>(mIPack)->pos()*std::get<i>(mBlockSizes).val(); },
|
||||
( [&](auto i) { return std::get<i>(mIPack)->pos()*std::get<i>(blockSizes()).val(); },
|
||||
[](const auto&... xs) { return (xs + ...); });
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
const typename MIndex<Indices...>::IndexPack& MIndex<Indices...>::pack() const
|
||||
template <class BlockType, class... Indices>
|
||||
const typename GMIndex<BlockType,Indices...>::IndexPack& GMIndex<BlockType,Indices...>::pack() const
|
||||
{
|
||||
return mIPack;
|
||||
}
|
||||
|
||||
template <class... Indices>
|
||||
const auto& MIndex<Indices...>::blockSizes() const
|
||||
template <class BlockType, class... Indices>
|
||||
const auto& GMIndex<BlockType,Indices...>::blockSizes() const
|
||||
{
|
||||
if constexpr(std::is_same<BlockType,None>::value){
|
||||
return mLexBlockSizes;
|
||||
}
|
||||
else {
|
||||
return mBlockSizes;
|
||||
}
|
||||
}
|
||||
|
||||
template <class BlockType, class... Indices>
|
||||
const auto& GMIndex<BlockType,Indices...>::lexBlockSizes() const
|
||||
{
|
||||
return mLexBlockSizes;
|
||||
}
|
||||
|
||||
|
||||
/*********************
|
||||
|
|
|
@ -11,13 +11,13 @@
|
|||
namespace CNORXZ
|
||||
{
|
||||
|
||||
template <class... Indices>
|
||||
class MIndex : public IndexInterface<MIndex<Indices...>,
|
||||
template <class BlockType, class... Indices>
|
||||
class GMIndex : public IndexInterface<GMIndex<BlockType,Indices...>,
|
||||
Tuple<typename Indices::MetaType...> >
|
||||
{
|
||||
public:
|
||||
|
||||
typedef IndexInterface<MIndex<Indices...>,
|
||||
typedef IndexInterface<GMIndex<BlockType,Indices...>,
|
||||
Tuple<typename Indices::MetaType...>> IB;
|
||||
typedef Tuple<Sptr<Indices>...> IndexPack;
|
||||
typedef Tuple<typename Indices::MetaType...> MetaType;
|
||||
|
@ -26,31 +26,32 @@ namespace CNORXZ
|
|||
|
||||
// NO DEFAULT HERE !!!
|
||||
// ( have to assign sub-indices (ptr!) correctly )
|
||||
MIndex() = default;
|
||||
MIndex(MIndex&& i) = default;
|
||||
MIndex& operator=(MIndex&& i) = default;
|
||||
constexpr GMIndex() = default;
|
||||
constexpr GMIndex(GMIndex&& i) = default;
|
||||
constexpr GMIndex& operator=(GMIndex&& i) = default;
|
||||
|
||||
MIndex(const MIndex& i);
|
||||
MIndex& operator=(const MIndex& i);
|
||||
constexpr GMIndex(const GMIndex& i);
|
||||
constexpr GMIndex& operator=(const GMIndex& i);
|
||||
|
||||
MIndex(const RangePtr& range, SizeT lexpos = 0);
|
||||
constexpr GMIndex(const RangePtr& range, SizeT lexpos = 0);
|
||||
constexpr GMIndex(const RangePtr& range, const BlockType& blockSizes, SizeT lexpos = 0);
|
||||
|
||||
MIndex& operator=(SizeT pos);
|
||||
MIndex& operator++();
|
||||
MIndex& operator--();
|
||||
MIndex operator+(Int n) const;
|
||||
MIndex operator-(Int n) const;
|
||||
MIndex& operator+=(Int n);
|
||||
MIndex& operator-=(Int n);
|
||||
GMIndex& operator=(SizeT pos);
|
||||
GMIndex& operator++();
|
||||
GMIndex& operator--();
|
||||
GMIndex operator+(Int n) const;
|
||||
GMIndex operator-(Int n) const;
|
||||
GMIndex& operator+=(Int n);
|
||||
GMIndex& operator-=(Int n);
|
||||
|
||||
SizeT lex() const;
|
||||
SizeT pmax() const;
|
||||
SizeT lmax() const;
|
||||
constexpr decltype(auto) pmax() const;
|
||||
constexpr decltype(auto) lmax() const;
|
||||
IndexId<0> id() const;
|
||||
|
||||
MetaType operator*() const;
|
||||
|
||||
SizeT dim() const;
|
||||
constexpr SizeT dim() const;
|
||||
Sptr<RangeType> range() const;
|
||||
|
||||
template <SizeT I>
|
||||
|
@ -58,20 +59,25 @@ namespace CNORXZ
|
|||
|
||||
String stringMeta() const;
|
||||
MetaType meta() const;
|
||||
MIndex& at(const MetaType& metaPos);
|
||||
GMIndex& at(const MetaType& metaPos);
|
||||
|
||||
template <class Xpr, class F>
|
||||
constexpr decltype(auto) ifor(const Xpr& xpr, F&& f) const;
|
||||
|
||||
// replace sub-index instances; only use if you know what you are doing!
|
||||
MIndex& operator()(const Sptr<MIndex>& mi);
|
||||
GMIndex& operator()(const Sptr<GMIndex>& mi);
|
||||
|
||||
const IndexPack& pack() const;
|
||||
const auto& blockSizes() const;
|
||||
const auto& lexBlockSizes() const;
|
||||
|
||||
private:
|
||||
template <SizeT... Is>
|
||||
static constexpr decltype(auto) mkBlockSizes(const IndexPack& ipack, Isq<Is...> is);
|
||||
static constexpr decltype(auto) mkLexBlockSizes(const IndexPack& ipack, Isq<Is...> is);
|
||||
|
||||
static constexpr decltype(auto) mkLMax(const IndexPack& ipack);
|
||||
|
||||
static constexpr decltype(auto) mkPMax(const IndexPack& ipack, const BlockType& blockSizes);
|
||||
|
||||
template <SizeT... Is>
|
||||
constexpr decltype(auto) mkIPack(Isq<Is...> is) const;
|
||||
|
@ -87,31 +93,19 @@ namespace CNORXZ
|
|||
|
||||
Sptr<RangeType> mRange;
|
||||
IndexPack mIPack;
|
||||
typedef RemoveRef<decltype(mkBlockSizes(mIPack,Isqr<0,NI-1>{}))> BlockTuple;
|
||||
BlockTuple mBlockSizes;
|
||||
SizeT mPMax = 0; // = LMax here, add new variable in GMIndex!
|
||||
typedef RemoveRef<decltype(mkLexBlockSizes(mIPack,Isqr<0,NI-1>{}))> LexBlockType;
|
||||
LexBlockType mLexBlockSizes;
|
||||
BlockType mBlockSizes;
|
||||
SizeT mLex;
|
||||
typedef RemoveRef<decltype(mkLMax(mIPack))> LMaxT;
|
||||
LMaxT mLMax;
|
||||
typedef RemoveRef<decltype(mkPMax(mIPack,mBlockSizes))> PMaxT;
|
||||
PMaxT mPMax;
|
||||
};
|
||||
|
||||
// modified blockSizes; to be used for Slices; can be created from MIndices
|
||||
template <class MIndexType, class BlockType>
|
||||
class GMIndex : public MIndexType
|
||||
{
|
||||
public:
|
||||
// override everything that modyfies IB::mPos or uses mBlockSizes!!!
|
||||
template <class... Indices>
|
||||
using MIndex = GMIndex<None,Indices...>;
|
||||
|
||||
constexpr GMIndex(const MIndexType& mi, const BlockType& b);
|
||||
|
||||
template <class Xpr, class F>
|
||||
constexpr decltype(auto) ifor(const Xpr& xpr, F&& f) const;
|
||||
|
||||
private:
|
||||
BlockType mLexBlockSizes;
|
||||
|
||||
template <SizeT... Is>
|
||||
constexpr decltype(auto) mkPos(Isq<Is...> is) const;
|
||||
};
|
||||
|
||||
// NOT THREAD SAVE
|
||||
template <class... Ranges>
|
||||
class MRangeFactory : public RangeFactoryBase
|
||||
{
|
||||
|
|
|
@ -75,15 +75,15 @@ namespace CNORXZ
|
|||
}
|
||||
|
||||
template <typename MetaType>
|
||||
SizeT UIndex<MetaType>::pmax() const
|
||||
UPos UIndex<MetaType>::pmax() const
|
||||
{
|
||||
return mRangePtr->size();
|
||||
return UPos(mRangePtr->size());
|
||||
}
|
||||
|
||||
template <typename MetaType>
|
||||
SizeT UIndex<MetaType>::lmax() const
|
||||
UPos UIndex<MetaType>::lmax() const
|
||||
{
|
||||
return mRangePtr->size();
|
||||
return UPos(mRangePtr->size());
|
||||
}
|
||||
|
||||
template <typename MetaType>
|
||||
|
@ -140,7 +140,7 @@ namespace CNORXZ
|
|||
template <class Xpr, class F>
|
||||
decltype(auto) UIndex<MetaType>::ifor(const Xpr& xpr, F&& f) const
|
||||
{
|
||||
return For<0,Xpr,F>(this->pmax(), this->id(), xpr, std::forward<F>(f));
|
||||
return For<0,Xpr,F>(this->pmax().val(), this->id(), xpr, std::forward<F>(f));
|
||||
}
|
||||
|
||||
/**********************
|
||||
|
|
|
@ -31,8 +31,8 @@ namespace CNORXZ
|
|||
UIndex& operator-=(Int n);
|
||||
|
||||
SizeT lex() const;
|
||||
SizeT pmax() const;
|
||||
SizeT lmax() const;
|
||||
UPos pmax() const;
|
||||
UPos lmax() const;
|
||||
IndexId<0> id() const;
|
||||
|
||||
const MetaT& operator*() const;
|
||||
|
|
|
@ -86,15 +86,15 @@ namespace CNORXZ
|
|||
}
|
||||
|
||||
template <class Index, typename Meta>
|
||||
SizeT XIndex<Index,Meta>::pmax() const
|
||||
UPos XIndex<Index,Meta>::pmax() const
|
||||
{
|
||||
return mI->pmax();
|
||||
return UPos(mI->pmax());
|
||||
}
|
||||
|
||||
template <class Index, typename Meta>
|
||||
SizeT XIndex<Index,Meta>::lmax() const
|
||||
UPos XIndex<Index,Meta>::lmax() const
|
||||
{
|
||||
return mI->lmax();
|
||||
return UPos(mI->lmax());
|
||||
}
|
||||
|
||||
template <class Index, typename Meta>
|
||||
|
|
|
@ -26,8 +26,8 @@ namespace CNORXZ
|
|||
virtual XIndexBase& operator-=(Int n) = 0;
|
||||
|
||||
virtual SizeT lex() const = 0;
|
||||
virtual SizeT pmax() const = 0;
|
||||
virtual SizeT lmax() const = 0;
|
||||
virtual UPos pmax() const = 0;
|
||||
virtual UPos lmax() const = 0;
|
||||
virtual IndexId<0> id() const = 0;
|
||||
|
||||
virtual DType operator*() const = 0;
|
||||
|
@ -74,8 +74,8 @@ namespace CNORXZ
|
|||
virtual XIndex& operator-=(Int n) override final;
|
||||
|
||||
virtual SizeT lex() const override final;
|
||||
virtual SizeT pmax() const override final;
|
||||
virtual SizeT lmax() const override final;
|
||||
virtual UPos pmax() const override final;
|
||||
virtual UPos lmax() const override final;
|
||||
virtual IndexId<0> id() const override final;
|
||||
|
||||
virtual DType operator*() const override final;
|
||||
|
|
|
@ -35,8 +35,8 @@ namespace CNORXZ
|
|||
YIndex& operator-=(Int n);
|
||||
|
||||
SizeT lex() const;
|
||||
SizeT pmax() const;
|
||||
SizeT lmax() const;
|
||||
UPos pmax() const;
|
||||
UPos lmax() const;
|
||||
IndexId<0> id() const;
|
||||
|
||||
DType operator*() const;
|
||||
|
@ -69,8 +69,8 @@ namespace CNORXZ
|
|||
Vector<SizeT> mBlockSizes; // dim() elements only!!!
|
||||
Vector<SizeT> mLexBlockSizes; // dim() elements only!!!
|
||||
SizeT mLex = 0;
|
||||
SizeT mPMax = 0;
|
||||
SizeT mLMax = 0;
|
||||
UPos mPMax = 0;
|
||||
UPos mLMax = 0;
|
||||
};
|
||||
|
||||
class YRangeFactory : public RangeFactoryBase
|
||||
|
|
|
@ -83,6 +83,12 @@ namespace CNORXZ
|
|||
return UPos(N);
|
||||
}
|
||||
|
||||
template <SizeT N>
|
||||
constexpr SPos<N>::operator SizeT() const
|
||||
{
|
||||
return val();
|
||||
}
|
||||
|
||||
/************
|
||||
* UPos *
|
||||
************/
|
||||
|
@ -139,6 +145,11 @@ namespace CNORXZ
|
|||
return extend(a);
|
||||
}
|
||||
|
||||
constexpr UPos::operator SizeT() const
|
||||
{
|
||||
return val();
|
||||
}
|
||||
|
||||
/************
|
||||
* FPos *
|
||||
************/
|
||||
|
@ -185,6 +196,11 @@ namespace CNORXZ
|
|||
return extend(a);
|
||||
}
|
||||
|
||||
constexpr FPos::operator SizeT() const
|
||||
{
|
||||
return val();
|
||||
}
|
||||
|
||||
/*************
|
||||
* SFPos *
|
||||
*************/
|
||||
|
@ -265,6 +281,12 @@ namespace CNORXZ
|
|||
return FPos(N, &sMs[0]);
|
||||
}
|
||||
|
||||
template <SizeT N, SizeT... Ms>
|
||||
constexpr SFPos<N,Ms...>::operator SizeT() const
|
||||
{
|
||||
return val();
|
||||
}
|
||||
|
||||
/************
|
||||
* MPos *
|
||||
************/
|
||||
|
@ -456,6 +478,11 @@ namespace CNORXZ
|
|||
return MPos<DPos,PosT>(*this,a);
|
||||
}
|
||||
|
||||
inline DPos::operator SizeT() const
|
||||
{
|
||||
return val();
|
||||
}
|
||||
|
||||
/***************
|
||||
* DPosRef *
|
||||
***************/
|
||||
|
@ -532,6 +559,11 @@ namespace CNORXZ
|
|||
return MPos<DPos,PosT>(*this,a);
|
||||
}
|
||||
|
||||
inline DPosRef::operator SizeT() const
|
||||
{
|
||||
return val();
|
||||
}
|
||||
|
||||
/************
|
||||
* EPos *
|
||||
************/
|
||||
|
|
|
@ -36,6 +36,7 @@ namespace CNORXZ
|
|||
constexpr decltype(auto) operator<<(const PosT& a) const;
|
||||
|
||||
explicit constexpr operator UPos() const;
|
||||
explicit constexpr operator SizeT() const;
|
||||
};
|
||||
|
||||
class UPos
|
||||
|
@ -68,6 +69,8 @@ namespace CNORXZ
|
|||
|
||||
template <class PosT>
|
||||
constexpr decltype(auto) operator<<(const PosT& a) const;
|
||||
|
||||
explicit constexpr operator SizeT() const;
|
||||
};
|
||||
|
||||
class FPos
|
||||
|
@ -98,6 +101,8 @@ namespace CNORXZ
|
|||
|
||||
template <class PosT>
|
||||
constexpr decltype(auto) operator<<(const PosT& a) const;
|
||||
|
||||
explicit constexpr operator SizeT() const;
|
||||
};
|
||||
|
||||
template <SizeT N, SizeT... Ms>
|
||||
|
@ -129,6 +134,8 @@ namespace CNORXZ
|
|||
constexpr decltype(auto) operator<<(const PosT& a) const;
|
||||
|
||||
explicit constexpr operator FPos() const;
|
||||
|
||||
explicit constexpr operator SizeT() const;
|
||||
};
|
||||
|
||||
template <class BPosT, class NPosT>
|
||||
|
@ -204,6 +211,8 @@ namespace CNORXZ
|
|||
|
||||
template <class PosT>
|
||||
inline decltype(auto) operator<<(const PosT& a) const;
|
||||
|
||||
explicit inline operator SizeT() const;
|
||||
};
|
||||
|
||||
class DPosRef
|
||||
|
@ -238,6 +247,8 @@ namespace CNORXZ
|
|||
|
||||
template <class PosT>
|
||||
inline decltype(auto) operator<<(const PosT& a) const;
|
||||
|
||||
explicit inline operator SizeT() const;
|
||||
};
|
||||
|
||||
// for common call of extension vector elements
|
||||
|
|
|
@ -56,14 +56,14 @@ namespace CNORXZ
|
|||
return IB::mPos;
|
||||
}
|
||||
|
||||
SizeT CIndex::lmax() const
|
||||
UPos CIndex::lmax() const
|
||||
{
|
||||
return mRangePtr->size();
|
||||
return UPos(mRangePtr->size());
|
||||
}
|
||||
|
||||
SizeT CIndex::pmax() const
|
||||
UPos CIndex::pmax() const
|
||||
{
|
||||
return mRangePtr->size();
|
||||
return UPos(mRangePtr->size());
|
||||
}
|
||||
|
||||
IndexId<0> CIndex::id() const
|
||||
|
|
|
@ -87,12 +87,12 @@ namespace CNORXZ
|
|||
return mI->lex();
|
||||
}
|
||||
|
||||
SizeT DIndex::pmax() const
|
||||
UPos DIndex::pmax() const
|
||||
{
|
||||
return mI->pmax();
|
||||
}
|
||||
|
||||
SizeT DIndex::lmax() const
|
||||
UPos DIndex::lmax() const
|
||||
{
|
||||
return mI->lmax();
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace CNORXZ
|
|||
for(SizeT i = o.size(); i != 0; --i){
|
||||
const SizeT j = i-1;
|
||||
o[j] = b;
|
||||
b *= mIs[j]->pmax();
|
||||
b *= mIs[j]->pmax().val();
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ namespace CNORXZ
|
|||
for(SizeT i = o.size(); i != 0; --i){
|
||||
const SizeT j = i-1;
|
||||
o[j] = b;
|
||||
b *= mIs[j]->lmax();
|
||||
b *= mIs[j]->lmax().val();
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ namespace CNORXZ
|
|||
auto& idx = mIs[i];
|
||||
// it is guaranteed that the last accessible position
|
||||
// is one less than the max position (=end)
|
||||
if(i != 0 and idx->lex() == idx->lmax()-1){
|
||||
if(i != 0 and idx->lex() == idx->lmax().val()-1){
|
||||
IB::mPos -= mBlockSizes[i] * idx->pos();
|
||||
mLex -= mLexBlockSizes[i] * idx->lex();
|
||||
(*idx) = 0;
|
||||
|
@ -63,7 +63,7 @@ namespace CNORXZ
|
|||
{
|
||||
auto& idx = mIs[i];
|
||||
if(i != 0 and idx->pos() == 0){
|
||||
(*idx) = idx->lmax()-1;
|
||||
(*idx) = idx->lmax().val()-1;
|
||||
IB::mPos += mBlockSizes[i] * idx->pos();
|
||||
mLex += mLexBlockSizes[i] * idx->lex();
|
||||
down(i-1);
|
||||
|
@ -89,7 +89,7 @@ namespace CNORXZ
|
|||
{
|
||||
SizeT o = 0;
|
||||
for(SizeT i = 0; i != mIs.size(); ++i){
|
||||
o += (mIs[i]->pmax()-1) * mBlockSizes[i];
|
||||
o += (mIs[i]->pmax().val()-1) * mBlockSizes[i];
|
||||
}
|
||||
return o+1;
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ namespace CNORXZ
|
|||
inline SizeT YIndex::mkLMax() const
|
||||
{
|
||||
return std::accumulate(mIs.begin(), mIs.end(),1,
|
||||
[](const auto& res, const auto& el) { return res * el->lmax(); } );
|
||||
[](const auto& res, const auto& el) { return res * el->lmax().val(); } );
|
||||
}
|
||||
|
||||
|
||||
|
@ -143,15 +143,15 @@ namespace CNORXZ
|
|||
|
||||
YIndex& YIndex::operator=(SizeT lexpos)
|
||||
{
|
||||
if(lexpos >= lmax()){
|
||||
mLex = lmax();
|
||||
IB::mPos = pmax();
|
||||
if(lexpos >= lmax().val()){
|
||||
mLex = lmax().val();
|
||||
IB::mPos = pmax().val();
|
||||
return *this;
|
||||
}
|
||||
mLex = lexpos;
|
||||
IB::mPos = 0;
|
||||
for(SizeT i = 0; i != mIs.size(); ++i){
|
||||
*mIs[i] = (lex() / mLexBlockSizes[i]) % mIs[i]->lmax();
|
||||
*mIs[i] = (lex() / mLexBlockSizes[i]) % mIs[i]->lmax().val();
|
||||
IB::mPos += mBlockSizes[i] * mIs[i]->pos();
|
||||
}
|
||||
return *this;
|
||||
|
@ -159,22 +159,19 @@ namespace CNORXZ
|
|||
|
||||
YIndex& YIndex::operator++()
|
||||
{
|
||||
auto& i0 = mIs[0];
|
||||
if(i0->lex() != i0->lmax()){
|
||||
up(mIs.size()-1);
|
||||
if(lex() == lmax().val()-1){
|
||||
return *this = lmax().val();
|
||||
}
|
||||
// no else! up() changes i0!
|
||||
if(i0->lex() == i0->lmax()){
|
||||
IB::mPos = pmax();
|
||||
if(lex() != lmax().val()){
|
||||
up(mIs.size()-1);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
YIndex& YIndex::operator--()
|
||||
{
|
||||
auto& i0 = mIs[0];
|
||||
if(i0->lex() == i0->lmax()){
|
||||
IB::mPos = mBlockSizes[0] * i0->pmax();
|
||||
if(lex() == lmax().val()){
|
||||
return *this = lmax().val()-1;
|
||||
}
|
||||
if(lex() != 0){
|
||||
down(mIs.size()-1);
|
||||
|
@ -215,12 +212,12 @@ namespace CNORXZ
|
|||
return mLex;
|
||||
}
|
||||
|
||||
SizeT YIndex::pmax() const
|
||||
UPos YIndex::pmax() const
|
||||
{
|
||||
return mPMax;
|
||||
}
|
||||
|
||||
SizeT YIndex::lmax() const
|
||||
UPos YIndex::lmax() const
|
||||
{
|
||||
return mLMax;
|
||||
}
|
||||
|
|
|
@ -231,8 +231,8 @@ namespace
|
|||
auto yrx = std::dynamic_pointer_cast<YRange>(yr);
|
||||
auto yi = yrx->begin();
|
||||
|
||||
EXPECT_EQ(yi.pmax(), yr->size());
|
||||
EXPECT_EQ(yi.lmax(), yr->size());
|
||||
EXPECT_EQ(yi.pmax().val(), yr->size());
|
||||
EXPECT_EQ(yi.lmax().val(), yr->size());
|
||||
EXPECT_EQ(yi.range(), yr);
|
||||
EXPECT_EQ(yi.range(), yrx);
|
||||
EXPECT_EQ(yi.dim(), 2u);
|
||||
|
@ -258,11 +258,11 @@ namespace
|
|||
EXPECT_EQ(b.stringMeta(), toString(mmj));
|
||||
}
|
||||
}
|
||||
yi += yi.lmax() + 10;
|
||||
EXPECT_EQ(yi.lex(), yi.lmax());
|
||||
EXPECT_EQ(yi.pos(), yi.pmax());
|
||||
yi += yi.lmax().val() + 10;
|
||||
EXPECT_EQ(yi.lex(), yi.lmax().val());
|
||||
EXPECT_EQ(yi.pos(), yi.pmax().val());
|
||||
|
||||
yi -= yi.lmax() + 20;
|
||||
yi -= yi.lmax().val() + 20;
|
||||
EXPECT_EQ(yi.lex(), 0u);
|
||||
EXPECT_EQ(yi.pos(), 0u);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue