dynamic operations: partial static call works consistently
This commit is contained in:
parent
cb6a6ccdb3
commit
67b343d217
6 changed files with 119 additions and 41 deletions
|
@ -47,29 +47,25 @@ namespace MultiArrayTools
|
||||||
: mOp(op),
|
: mOp(op),
|
||||||
mMa(std::make_shared<MultiArray<T,Ranges...>>(mkArray<T>(inds->range()...))),
|
mMa(std::make_shared<MultiArray<T,Ranges...>>(mkArray<T>(inds->range()...))),
|
||||||
mProto(OperationRoot<T,Ranges...>(*mMa,inds...)),
|
mProto(OperationRoot<T,Ranges...>(*mMa,inds...)),
|
||||||
mL(std::make_tuple(mProto.mOp,mOp), std::make_tuple(inds...),
|
mL(std::make_tuple(*mProto.mOp,mOp), std::make_tuple(inds...),
|
||||||
std::make_tuple(mMa), std::make_tuple(mProto.mOp.assign( mOp )),
|
std::make_tuple(mMa), std::make_tuple(mProto.mOp->assign( mOp, mkMIndex(inds...) )),
|
||||||
std::array<size_t,1>({0}), std::array<size_t,1>({0}))
|
std::array<size_t,1>({1}), std::array<size_t,1>({0}))
|
||||||
{
|
{
|
||||||
VCHECK(reinterpret_cast<std::intptr_t>(mProto.mOp.data()));
|
*mMa = 0;
|
||||||
VCHECK(reinterpret_cast<std::intptr_t>(mMa->data()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, class Operation, class... Ranges>
|
template <typename T, class Operation, class... Ranges>
|
||||||
OpH<OperationRoot<T,Ranges...>> DynamicOuterOp<T,Operation,Ranges...>::get(const DExtT& pos) const
|
OpH<OperationRoot<T,Ranges...>> DynamicOuterOp<T,Operation,Ranges...>::get(const DExtT& pos) const
|
||||||
{
|
{
|
||||||
CHECK;
|
mL(0,pos.expl<ET>());
|
||||||
//mOp.get(pos.expl<ET>());
|
|
||||||
//mL(0,pos.expl<ET>());
|
|
||||||
// execute assignment... care about threads!!!
|
// execute assignment... care about threads!!!
|
||||||
return mProto.mOp; // empty
|
return mProto; // empty
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, class Operation, class... Ranges>
|
template <typename T, class Operation, class... Ranges>
|
||||||
DynamicOperationBase<OpH<OperationRoot<T,Ranges...>>>&
|
DynamicOperationBase<OpH<OperationRoot<T,Ranges...>>>&
|
||||||
DynamicOuterOp<T,Operation,Ranges...>::set(const DExtT& pos)
|
DynamicOuterOp<T,Operation,Ranges...>::set(const DExtT& pos)
|
||||||
{
|
{
|
||||||
CHECK;
|
|
||||||
mOp.set(pos.expl<ET>());
|
mOp.set(pos.expl<ET>());
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
@ -77,21 +73,18 @@ namespace MultiArrayTools
|
||||||
template <typename T, class Operation, class... Ranges>
|
template <typename T, class Operation, class... Ranges>
|
||||||
DExtT DynamicOuterOp<T,Operation,Ranges...>::rootSteps(std::intptr_t iPtrNum) const
|
DExtT DynamicOuterOp<T,Operation,Ranges...>::rootSteps(std::intptr_t iPtrNum) const
|
||||||
{
|
{
|
||||||
CHECK;
|
|
||||||
return DExtT(mkDExt(mkExtT(mL.rootSteps(iPtrNum))),None(0));
|
return DExtT(mkDExt(mkExtT(mL.rootSteps(iPtrNum))),None(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, class Operation, class... Ranges>
|
template <typename T, class Operation, class... Ranges>
|
||||||
DynamicExpression DynamicOuterOp<T,Operation,Ranges...>::loop(const DynamicExpression& exp) const
|
DynamicExpression DynamicOuterOp<T,Operation,Ranges...>::loop(const DynamicExpression& exp) const
|
||||||
{
|
{
|
||||||
CHECK;
|
|
||||||
return mOp.loop(exp); // ???!!
|
return mOp.loop(exp); // ???!!
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, class Operation, class... Ranges>
|
template <typename T, class Operation, class... Ranges>
|
||||||
const OpH<OperationRoot<T,Ranges...>>* DynamicOuterOp<T,Operation,Ranges...>::data() const
|
const OpH<OperationRoot<T,Ranges...>>* DynamicOuterOp<T,Operation,Ranges...>::data() const
|
||||||
{
|
{
|
||||||
CHECK;
|
|
||||||
return &mProto;
|
return &mProto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +92,6 @@ namespace MultiArrayTools
|
||||||
std::shared_ptr<DynamicOperationBase<OpH<OperationRoot<T,Ranges...>>>>
|
std::shared_ptr<DynamicOperationBase<OpH<OperationRoot<T,Ranges...>>>>
|
||||||
DynamicOuterOp<T,Operation,Ranges...>::deepCopy() const
|
DynamicOuterOp<T,Operation,Ranges...>::deepCopy() const
|
||||||
{
|
{
|
||||||
CHECK;
|
|
||||||
return std::make_shared<DynamicOuterOp<T,Operation,Ranges...>>(*this);
|
return std::make_shared<DynamicOuterOp<T,Operation,Ranges...>>(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,8 +61,9 @@ namespace MultiArrayTools
|
||||||
template <class Op>
|
template <class Op>
|
||||||
struct OpH
|
struct OpH
|
||||||
{
|
{
|
||||||
Op mOp;
|
std::shared_ptr<Op> mOp;
|
||||||
OpH(const Op& op) : mOp(op) {}
|
OpH(const Op& op) : mOp(std::make_shared<Op>(op)) {}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, class Operation, class... Ranges>
|
template <typename T, class Operation, class... Ranges>
|
||||||
|
@ -77,7 +78,7 @@ namespace MultiArrayTools
|
||||||
typedef ILoop<std::tuple<OperationRoot<T,Ranges...>,Operation>,
|
typedef ILoop<std::tuple<OperationRoot<T,Ranges...>,Operation>,
|
||||||
std::tuple<std::shared_ptr<typename Ranges::IndexType>...>,
|
std::tuple<std::shared_ptr<typename Ranges::IndexType>...>,
|
||||||
std::tuple<std::shared_ptr<MultiArray<T,Ranges...>>>,
|
std::tuple<std::shared_ptr<MultiArray<T,Ranges...>>>,
|
||||||
std::tuple<decltype(mProto.mOp.assign( mOp ))>> LoopT;
|
std::tuple<decltype(mProto.mOp->assign( mOp, mkMIndex(std::shared_ptr<typename Ranges::IndexType>()...) ))>> LoopT;
|
||||||
|
|
||||||
mutable LoopT mL;
|
mutable LoopT mL;
|
||||||
|
|
||||||
|
|
|
@ -211,6 +211,52 @@ namespace MultiArrayTools
|
||||||
return nullptr; //???!!!
|
return nullptr; //???!!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class OpClass, class NextExpr>
|
||||||
|
GetExpr<OpClass,NextExpr>::GetExpr(const OpClass& sec, const NextExpr& nexpr) :
|
||||||
|
mSec(sec), mNExpr(nexpr) {}
|
||||||
|
|
||||||
|
template <class OpClass, class NextExpr>
|
||||||
|
inline void GetExpr<OpClass,NextExpr>::operator()(size_t start)
|
||||||
|
{
|
||||||
|
ExtType last = rootSteps();
|
||||||
|
last.zero();
|
||||||
|
mSec.get(last);
|
||||||
|
mNExpr(start,last.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class OpClass, class NextExpr>
|
||||||
|
inline void GetExpr<OpClass,NextExpr>::operator()(size_t start, ExtType last)
|
||||||
|
{
|
||||||
|
mSec.get(last);
|
||||||
|
mNExpr(start,last.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class OpClass, class NextExpr>
|
||||||
|
typename GetExpr<OpClass,NextExpr>::ExtType
|
||||||
|
GetExpr<OpClass,NextExpr>::rootSteps(std::intptr_t iPtrNum) const
|
||||||
|
{
|
||||||
|
return mSec.rootSteps(iPtrNum).extend( mNExpr.rootSteps(iPtrNum) );
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class OpClass, class NextExpr>
|
||||||
|
inline void GetExpr<OpClass,NextExpr>::operator()(size_t mlast, DExt last)
|
||||||
|
{
|
||||||
|
(*this)(mlast, std::dynamic_pointer_cast<ExtT<ExtType>>(last)->ext());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class OpClass, class NextExpr>
|
||||||
|
inline DExt GetExpr<OpClass,NextExpr>::dRootSteps(std::intptr_t iPtrNum) const
|
||||||
|
{
|
||||||
|
return std::make_shared<ExtT<ExtType>>(rootSteps(iPtrNum));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class OpClass, class NextExpr>
|
||||||
|
inline DExt GetExpr<OpClass,NextExpr>::dExtension() const
|
||||||
|
{
|
||||||
|
CHECK;
|
||||||
|
return nullptr; //???!!!
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, class Target, class OpClass, OpIndexAff OIA>
|
template <typename T, class Target, class OpClass, OpIndexAff OIA>
|
||||||
AddExpr<T,Target,OpClass,OIA>::AddExpr(T* dataPtr, const Target& tar, const OpClass& sec) :
|
AddExpr<T,Target,OpClass,OIA>::AddExpr(T* dataPtr, const Target& tar, const OpClass& sec) :
|
||||||
mTar(tar), mSec(sec), mDataPtr(dataPtr) {}
|
mTar(tar), mSec(sec), mDataPtr(dataPtr) {}
|
||||||
|
|
|
@ -142,6 +142,41 @@ namespace MultiArrayTools
|
||||||
inline DExt dExtension() const override final;
|
inline DExt dExtension() const override final;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <class OpClass, class NextExpr>
|
||||||
|
class GetExpr : public ExpressionBase
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
GetExpr() = default;
|
||||||
|
|
||||||
|
OpClass mSec;
|
||||||
|
NextExpr mNExpr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
static constexpr size_t LAYER = 0;
|
||||||
|
static constexpr size_t SIZE = OpClass::SIZE + NextExpr::SIZE;
|
||||||
|
typedef decltype(mSec.rootSteps(0).extend( mNExpr.rootSteps(0) ) ) ExtType;
|
||||||
|
|
||||||
|
GetExpr(const OpClass& sec, const NextExpr& nexpr);
|
||||||
|
GetExpr(const GetExpr& in) = default;
|
||||||
|
GetExpr(GetExpr&& in) = default;
|
||||||
|
|
||||||
|
inline void operator()(size_t start = 0);
|
||||||
|
inline void operator()(size_t start, ExtType last);
|
||||||
|
auto rootSteps(std::intptr_t iPtrNum = 0) const -> ExtType;
|
||||||
|
|
||||||
|
inline void operator()(size_t mlast, DExt last) override final;
|
||||||
|
|
||||||
|
inline DExt dRootSteps(std::intptr_t iPtrNum = 0) const override final;
|
||||||
|
inline DExt dExtension() const override final;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class OpClass, class NextExpr>
|
||||||
|
auto mkGetExpr(const OpClass& op, const NextExpr& nexpr)
|
||||||
|
{
|
||||||
|
return GetExpr<OpClass,NextExpr>(op, nexpr);
|
||||||
|
}
|
||||||
|
|
||||||
//template <typename T, class OpClass>
|
//template <typename T, class OpClass>
|
||||||
template <typename T, class Target, class OpClass, OpIndexAff OIA=OpIndexAff::EXTERN>
|
template <typename T, class Target, class OpClass, OpIndexAff OIA=OpIndexAff::EXTERN>
|
||||||
class AddExpr : public ExpressionBase
|
class AddExpr : public ExpressionBase
|
||||||
|
|
|
@ -102,6 +102,9 @@ namespace MultiArrayHelper
|
||||||
private:
|
private:
|
||||||
mutable DExt mDExt = nullptr;
|
mutable DExt mDExt = nullptr;
|
||||||
X mNext;
|
X mNext;
|
||||||
|
|
||||||
|
template <class Y>
|
||||||
|
friend class DExtTX;
|
||||||
public:
|
public:
|
||||||
static constexpr size_t NUM = X::SIZE;
|
static constexpr size_t NUM = X::SIZE;
|
||||||
static constexpr size_t SIZE = NUM + 1;
|
static constexpr size_t SIZE = NUM + 1;
|
||||||
|
@ -119,8 +122,12 @@ namespace MultiArrayHelper
|
||||||
template <class Y>
|
template <class Y>
|
||||||
DExtTX(const Y& y) : mDExt(std::make_shared<ExtT<Y>>(y)) {}
|
DExtTX(const Y& y) : mDExt(std::make_shared<ExtT<Y>>(y)) {}
|
||||||
*/
|
*/
|
||||||
explicit DExtTX(const DExt& y, const X& x) : mDExt(y->deepCopy()),
|
|
||||||
mNext(x) {}
|
template <class Y>
|
||||||
|
DExtTX(const DExtTX<Y>& in) : mDExt(in.mDExt), mNext(in.mNext) {}
|
||||||
|
|
||||||
|
DExtTX(const DExt& y, const X& x) : mDExt(y->deepCopy()),
|
||||||
|
mNext(x) {}
|
||||||
|
|
||||||
virtual size_t size() const { return mDExt->size(); }
|
virtual size_t size() const { return mDExt->size(); }
|
||||||
inline const DExt& get() const { return mDExt; }
|
inline const DExt& get() const { return mDExt; }
|
||||||
|
|
|
@ -53,10 +53,11 @@ namespace
|
||||||
std::map<std::string,std::shared_ptr<IndexW>> imap;
|
std::map<std::string,std::shared_ptr<IndexW>> imap;
|
||||||
|
|
||||||
std::shared_ptr<DR> dr1;
|
std::shared_ptr<DR> dr1;
|
||||||
std::shared_ptr<DR> dr1a;
|
//std::shared_ptr<DR> dr1a;
|
||||||
std::shared_ptr<DR> dr2;
|
std::shared_ptr<DR> dr2;
|
||||||
std::shared_ptr<DR> dr3;
|
std::shared_ptr<DR> dr3;
|
||||||
std::shared_ptr<DR> dr4;
|
std::shared_ptr<DR> dr4;
|
||||||
|
std::shared_ptr<DR> dr4a;
|
||||||
std::shared_ptr<DR> dr5;
|
std::shared_ptr<DR> dr5;
|
||||||
std::shared_ptr<DR> dr6;
|
std::shared_ptr<DR> dr6;
|
||||||
std::shared_ptr<CR> cr1;
|
std::shared_ptr<CR> cr1;
|
||||||
|
@ -73,13 +74,14 @@ namespace
|
||||||
auto cr5 = createRangeE<CR>(13);
|
auto cr5 = createRangeE<CR>(13);
|
||||||
|
|
||||||
dr1 = createRangeE<DR>(cr2,cr2,cr3,cr4);
|
dr1 = createRangeE<DR>(cr2,cr2,cr3,cr4);
|
||||||
dr1a = createRangeE<DR>(cr2,cr2,cr3);
|
//dr1a = createRangeE<DR>(cr2,cr2,cr3);
|
||||||
dr2 = createRangeE<DR>(cr3,cr3,cr4);
|
dr2 = createRangeE<DR>(cr3,cr3,cr4);
|
||||||
dr3 = createRangeE<DR>(cr2,cr5);
|
dr3 = createRangeE<DR>(cr2,cr5);
|
||||||
dr5 = createRangeE<DR>(cr5);
|
dr5 = createRangeE<DR>(cr5);
|
||||||
dr6 = createRangeE<DR>(cr3,cr4);
|
dr6 = createRangeE<DR>(cr3,cr4);
|
||||||
|
|
||||||
dr4 = createRangeE<DR>(cr2,cr3,cr4,cr4);
|
dr4 = createRangeE<DR>(cr2,cr3,cr4,cr4);
|
||||||
|
dr4a = createRangeE<DR>(cr2,cr3,cr4);
|
||||||
|
|
||||||
ma1 = mkArray<double>(cr1,dr1);
|
ma1 = mkArray<double>(cr1,dr1);
|
||||||
ma2 = mkArray<double>(cr1,dr2);
|
ma2 = mkArray<double>(cr1,dr2);
|
||||||
|
@ -99,8 +101,8 @@ namespace
|
||||||
imap["i3_1"] = mkIndexW(getIndex(cr3));
|
imap["i3_1"] = mkIndexW(getIndex(cr3));
|
||||||
imap["i3_2"] = mkIndexW(getIndex(cr3));
|
imap["i3_2"] = mkIndexW(getIndex(cr3));
|
||||||
ci4 = getIndex(cr4);
|
ci4 = getIndex(cr4);
|
||||||
imap["i4_1"] = mkIndexW(ci4);
|
imap["i4_1"] = mkIndexW(getIndex(cr4));
|
||||||
imap["i4_2"] = mkIndexW(getIndex(cr4));
|
imap["i4_2"] = mkIndexW(ci4);
|
||||||
imap["i5_1"] = mkIndexW(getIndex(cr5));
|
imap["i5_1"] = mkIndexW(getIndex(cr5));
|
||||||
imap["i5_2"] = mkIndexW(getIndex(cr5));
|
imap["i5_2"] = mkIndexW(getIndex(cr5));
|
||||||
}
|
}
|
||||||
|
@ -110,16 +112,18 @@ namespace
|
||||||
{
|
{
|
||||||
auto i1 = getIndex(cr1);
|
auto i1 = getIndex(cr1);
|
||||||
auto di1 = getIndex(dr1);
|
auto di1 = getIndex(dr1);
|
||||||
auto di1a = getIndex(dr1a);
|
//auto di1a = getIndex(dr1a);
|
||||||
auto di2 = getIndex(dr2);
|
auto di2 = getIndex(dr2);
|
||||||
auto di4 = getIndex(dr4);
|
auto di4 = getIndex(dr4);
|
||||||
|
auto di4a = getIndex(dr4a);
|
||||||
auto mi = mkMIndex(i1,di1a);
|
|
||||||
|
|
||||||
(*di1)({imap["i2_1"],imap["i2_2"],imap["i3_1"],imap["i4_1"]});
|
(*di1)({imap["i2_1"],imap["i2_2"],imap["i3_1"],imap["i4_1"]});
|
||||||
(*di1a)({imap["i2_1"],imap["i2_2"],imap["i3_1"]});
|
//(*di1a)({imap["i2_1"],imap["i2_2"],imap["i3_1"]});
|
||||||
(*di2)({imap["i3_1"],imap["i3_1"],imap["i4_2"]});
|
(*di2)({imap["i3_1"],imap["i3_1"],imap["i4_2"]});
|
||||||
(*di4)({imap["i2_1"],imap["i3_1"],imap["i4_1"],imap["i4_2"]});
|
(*di4)({imap["i2_1"],imap["i3_1"],imap["i4_1"],imap["i4_2"]});
|
||||||
|
(*di4a)({imap["i2_1"],imap["i3_1"],imap["i4_1"]});
|
||||||
|
|
||||||
|
auto mi = mkMIndex(i1,di4a);
|
||||||
|
|
||||||
auto resx1 = res1;
|
auto resx1 = res1;
|
||||||
auto resx2 = res1;
|
auto resx2 = res1;
|
||||||
|
@ -130,24 +134,15 @@ namespace
|
||||||
resx1(i1,di4) = mkDynOp(ma1(i1,di1)) * mkDynOp(ma2(i1,di2));
|
resx1(i1,di4) = mkDynOp(ma1(i1,di1)) * mkDynOp(ma2(i1,di2));
|
||||||
resx2(i1,di4) = mkDynOp(ma1(i1,di1) * ma2(i1,di2));
|
resx2(i1,di4) = mkDynOp(ma1(i1,di1) * ma2(i1,di2));
|
||||||
resx3(i1,di4) = mkDynOp(mkDynOp(ma1(i1,di1)) * mkDynOp(ma2(i1,di2)));
|
resx3(i1,di4) = mkDynOp(mkDynOp(ma1(i1,di1)) * mkDynOp(ma2(i1,di2)));
|
||||||
|
|
||||||
auto op1 = mkDynOutOp(ma1(i1,di1) * ma2(i1,di2), ci4);
|
auto op1 = mkDynOutOp((ma1(i1,di1) * ma2(i1,di2)), ci4);
|
||||||
auto op1x = ma1(i1,di1) * ma2(i1,di2);
|
|
||||||
auto opr = resx4(i1,di4);
|
auto opr = resx4(i1,di4);
|
||||||
|
|
||||||
auto loop = mkILoop(std::make_tuple(opr,op1,op1.data()->mOp), std::make_tuple(ci4),
|
auto loop = mkILoop(std::make_tuple(opr,op1,*op1.data()->mOp), std::make_tuple(ci4),
|
||||||
std::make_tuple(xx), std::make_tuple(opr.assign( op1.data()->mOp, ci4 )),
|
std::make_tuple(xx), std::make_tuple(opr.assign( *op1.data()->mOp, ci4 )),
|
||||||
//std::make_tuple(), std::make_tuple(),
|
|
||||||
std::array<size_t,1>({1}), std::array<size_t,1>({0}));
|
std::array<size_t,1>({1}), std::array<size_t,1>({0}));
|
||||||
|
|
||||||
/*
|
mi->ifor(1, mkGetExpr(op1,loop))();
|
||||||
auto loop = mkILoop(std::make_tuple(opr,op1x), std::make_tuple(ci4),
|
|
||||||
std::make_tuple(xx), std::make_tuple(opr.assign( op1x, ci4 )),
|
|
||||||
//std::make_tuple(), std::make_tuple(),
|
|
||||||
std::array<size_t,1>({1}), std::array<size_t,1>({0}));
|
|
||||||
*/
|
|
||||||
//mi->ifor(1, loop)();
|
|
||||||
mi->ifor(1, loop)();
|
|
||||||
|
|
||||||
auto i2_1 = imap.at("i2_1");
|
auto i2_1 = imap.at("i2_1");
|
||||||
auto i2_2 = imap.at("i2_2");
|
auto i2_2 = imap.at("i2_2");
|
||||||
|
@ -168,11 +163,13 @@ namespace
|
||||||
auto resx1v = xround(resx1.vdata()[jr]);
|
auto resx1v = xround(resx1.vdata()[jr]);
|
||||||
auto resx2v = xround(resx2.vdata()[jr]);
|
auto resx2v = xround(resx2.vdata()[jr]);
|
||||||
auto resx3v = xround(resx3.vdata()[jr]);
|
auto resx3v = xround(resx3.vdata()[jr]);
|
||||||
|
auto resx4v = xround(resx4.vdata()[jr]);
|
||||||
auto x12 = xround(ma1.vdata()[j1]*ma2.vdata()[j2]);
|
auto x12 = xround(ma1.vdata()[j1]*ma2.vdata()[j2]);
|
||||||
EXPECT_EQ( resv, x12 );
|
EXPECT_EQ( resv, x12 );
|
||||||
EXPECT_EQ( resx1v, x12 );
|
EXPECT_EQ( resx1v, x12 );
|
||||||
EXPECT_EQ( resx2v, x12 );
|
EXPECT_EQ( resx2v, x12 );
|
||||||
EXPECT_EQ( resx3v, x12 );
|
EXPECT_EQ( resx3v, x12 );
|
||||||
|
EXPECT_EQ( resx4v, x12 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue