#include "PETSc.hpp"

#ifdef WITH_slepc
#define WITH_SLEPC
#endif
#ifdef WITH_slepccomplex
#define WITH_SLEPC
#endif

#ifdef WITH_SLEPC

#include <slepc.h>

#if defined(__clang__)
  #pragma clang diagnostic push
  #pragma clang diagnostic ignored "-Wunused-value"
#endif

namespace SLEPc {
template<class Type, class K>
struct _m_User;
template<class Type, class K, class SType>
struct _n_User;
template<class Type, class K, class SType>
using User = typename std::conditional<!std::is_same<SType, NEP>::value, _n_User<Type, K, SType>*, _m_User<Type, K>*>::type;
template<class Type, class K, class SType>
static PetscErrorCode MatMult_User(Mat A, Vec x, Vec y);
template<class K, typename std::enable_if<std::is_same<K, double>::value || !std::is_same<PetscScalar, double>::value>::type* = nullptr>
void copy(K*, PetscInt, PetscScalar*, PetscScalar*) { }
template<class K, typename std::enable_if<!std::is_same<K, double>::value && std::is_same<PetscScalar, double>::value>::type* = nullptr>
void copy(K* pt, PetscInt n, PetscScalar* xr, PetscScalar* xi) {
    for(int i = 0; i < n; ++i)
        pt[i] = K(xr[i], xi[i]);
}
template<class SType, class K, typename std::enable_if<!std::is_same<SType, SVD>::value && (std::is_same<K, double>::value || !std::is_same<PetscScalar, double>::value)>::type* = nullptr>
void assign(K* pt, PetscScalar& kr, PetscScalar&) {
    *pt = kr;
}
template<class SType, class K, typename std::enable_if<!std::is_same<SType, SVD>::value && (!std::is_same<K, double>::value && std::is_same<PetscScalar, double>::value)>::type* = nullptr>
void assign(K* pt, PetscScalar& kr, PetscScalar& ki) {
    *pt = K(kr, ki);
}
template<class SType, class K, typename std::enable_if<std::is_same<SType, SVD>::value>::type* = nullptr>
void assign(K*, PetscScalar&, PetscScalar&) { }
template<class K, typename std::enable_if<(std::is_same<PetscScalar, double>::value && std::is_same<K, std::complex<double>>::value)>::type* = nullptr>
void distributedVec(PetscInt*, PetscInt, PetscInt, K* const, PetscScalar*, PetscInt) { }
template<class K, typename std::enable_if<!(std::is_same<PetscScalar, double>::value && std::is_same<K, std::complex<double>>::value)>::type* = nullptr>
void distributedVec(PetscInt* num, PetscInt first, PetscInt last, K* const in, PetscScalar* pt, PetscInt n) {
    HPDDM::Subdomain<K>::template distributedVec<0>(num, first, last, in, pt, n, 1);
}
template<class Type, class K, class SType>
class eigensolver : public OneOperator {
    public:
        typedef KN<PetscScalar> Kn;
        typedef KN_<PetscScalar> Kn_;
        class MatF_O : public RNM_VirtualMatrix<PetscScalar> {
            public:
                Stack stack;
                mutable Kn x;
                C_F0 c_x;
                Expression mat;
                typedef typename RNM_VirtualMatrix<PetscScalar>::plusAx plusAx;
                MatF_O(int n, Stack stk, const OneOperator* op) :
                    RNM_VirtualMatrix<PetscScalar>(n), stack(stk), x(n), c_x(CPValue(x)),
                    mat(op ? CastTo<Kn_>(C_F0(op->code(basicAC_F0_wa(c_x)), (aType)*op)) : nullptr) { }
                ~MatF_O() {
                    delete mat;
                    Expression zzz = c_x;
                    delete zzz;
                }
                void addMatMul(const Kn_& xx, Kn_& Ax) const {
                    ffassert(xx.N() == Ax.N());
                    x = xx;
                    Ax += GetAny<Kn_>((*mat)(stack));
                    WhereStackOfPtr2Free(stack)->clean();
                }
                plusAx operator*(const Kn& x) const { return plusAx(this, x); }
        };
        class ScalarF_O {
            public:
                Stack stack;
                mutable PetscScalar x;
                C_F0 c_x;
                Expression mat;
                ScalarF_O(Stack stk, const OneOperator* op)
                    : stack(stk), x(0), c_x(CPValue(x)),
                    mat(op ? CastTo< long >(C_F0(op->code(basicAC_F0_wa(c_x)), (aType)*op)) : nullptr) {}
                ~ScalarF_O( ) {
                    delete mat;
                    Expression zzz = c_x;
                    delete zzz;
                }
                long apply(const PetscScalar xx, int* f) const {
                    x = xx;
                    ffassert(mat);
                    int ret = GetAny< int >((*mat)(stack));
                    *f = ret;
                    WhereStackOfPtr2Free(stack)->clean( );
                    return 0;
                }
        };
        const int c;
        class E_eigensolver : public E_F0mps {
            public:
                Expression A;
                Expression B;
                const OneOperator* codeA, *codeB;
                const int c;
                static const int n_name_param = 14;
                static basicAC_F0::name_and_type name_param[];
                Expression nargs[n_name_param];
                E_eigensolver(const basicAC_F0& args, int d) : A(nullptr), B(nullptr), codeA(nullptr), codeB(nullptr), c(d) {
                    args.SetNameParam(n_name_param, name_param, nargs);
                    if(c != 4) {
                        A = to<Type*>(args[0]);
                        if(c == 1 || c == 3) {
                            const Polymorphic* op = dynamic_cast<const Polymorphic*>(args[1].LeftValue());
                            ffassert(op);
                            if(c == 3) {
                                codeA = op->Find("(", ArrayOfaType(atype<PetscScalar>(), false));
                                ffassert(codeA);
                                B = to<Type*>(args[2]);
                                op = dynamic_cast<const Polymorphic*>(args[3].LeftValue());
                                ffassert(op);
                                codeB = op->Find("(", ArrayOfaType(atype<PetscScalar>(), false));
                                ffassert(codeB);
                            }
                            else
                                codeA = op->Find("(", ArrayOfaType(atype<KN<PetscScalar>*>(), false));
                        }
                        else if(c == 0) {
                            B = to<Type*>(args[1]);
                        }
                    }
                    else {
                        A = to<KN<Type>*>(args[0]);
                    }
                }

                AnyType operator()(Stack stack) const;
                operator aType() const { return atype<long>(); }
        };
        E_F0* code(const basicAC_F0 & args) const { return new E_eigensolver(args, c); }
        eigensolver() : OneOperator(atype<long>(), atype<Type*>(), atype<Type*>()), c(0) { }
        eigensolver(int) : OneOperator(atype<long>(), atype<Type*>(), atype<Polymorphic*>()), c(1) { }
        eigensolver(int, int) : OneOperator(atype<long>(), atype<Type*>()), c(2) { }
        eigensolver(int, int, int) : OneOperator(atype<long>(), atype<Type*>(), atype<Polymorphic*>(), atype<Type*>(), atype<Polymorphic*>()), c(3) { }
        eigensolver(int, int, int, int) : OneOperator(atype<long>(), atype<KN<Type>*>()), c(4) { }
};
template<class Type, class K, class SType>
basicAC_F0::name_and_type eigensolver<Type, K, SType>::E_eigensolver::name_param[] = {
    {"sparams", &typeid(std::string*)},
    {"prefix", &typeid(std::string*)},
    {"values", &typeid(KN<typename std::conditional<!std::is_same<SType, SVD>::value, K, PetscReal>::type>*)},
    {!std::is_same<SType, SVD>::value ? "vectors" : "lvectors", &typeid(FEbaseArrayKn<K>*)},
    {!std::is_same<SType, SVD>::value ? "array" : "larray", &typeid(KNM<K>*)},
    {"fields", &typeid(KN<double>*)},
    {"names", &typeid(KN<String>*)},
    {!std::is_same<SType, SVD>::value ? "lvectors" : "rvectors", &typeid(FEbaseArrayKn<K>*)},
    {!std::is_same<SType, SVD>::value ? "larray" : "rarray", &typeid(KNM<K>*)},
    {"deflation", &typeid(KNM<PetscScalar>*)},
    {"errorestimate", &typeid(KN<PetscReal>*)},
    {"schurPreconditioner", &typeid(KN<Matrice_Creuse<upscaled_type<PetscScalar>>>*)},
    {"schurList", &typeid(KN<double>*)},
    {"conjugate", &typeid(KN<long>*)},
};
template<class Type, class K, class SType>
struct _n_User {
    typename eigensolver<Type, K, SType>::MatF_O* mat;
};
template<class Type, class K>
struct _m_User {
    typename eigensolver<Type, K, NEP>::ScalarF_O* F;
    typename eigensolver<Type, K, NEP>::ScalarF_O* J;
};
PetscErrorCode FormFun(NEP, PetscScalar lambda, Mat, Mat, void* ctx) {
    User<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, NEP>* user;

    PetscFunctionBeginUser;
    user = reinterpret_cast< User< PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, NEP >* >(ctx);
    typename eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, NEP>::ScalarF_O* f =
        reinterpret_cast< typename eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, NEP>::ScalarF_O* >((*user)->F);
    int ret;
    f->apply(lambda, &ret);
    PetscFunctionReturn(PetscErrorCode(ret));
}
PetscErrorCode FormJac(NEP, PetscScalar lambda, Mat, void* ctx) {
    User<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, NEP>* user;

    PetscFunctionBeginUser;
    user = reinterpret_cast< User< PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, NEP >* >(ctx);
    typename eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, NEP>::ScalarF_O* f =
        reinterpret_cast< typename eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, NEP>::ScalarF_O* >((*user)->J);
    int ret;
    f->apply(lambda, &ret);
    PetscFunctionReturn(PetscErrorCode(ret));
}
template<class Type, class K, class SType>
AnyType eigensolver<Type, K, SType>::E_eigensolver::operator()(Stack stack) const {
    if(A && (c == 2 || c == 4 || B || codeA)) {
        KN<Type>* ptTab;
        Type* ptA;
        if(c != 4)
            ptA = GetAny<Type*>((*A)(stack));
        else {
            ptTab = GetAny<KN<Type>*>((*A)(stack));
            ffassert(ptTab && ptTab->N());
            ptA = &(ptTab->operator[](0));
        }
        if(ptA->_petsc) {
            EPS eps;
            SVD svd;
            NEP nep;
            PEP pep;
            if(std::is_same<SType, EPS>::value)
                EPSCreate(PetscObjectComm((PetscObject)ptA->_petsc), &eps);
            else if(std::is_same<SType, SVD>::value)
                SVDCreate(PetscObjectComm((PetscObject)ptA->_petsc), &svd);
            else if(std::is_same<SType, NEP>::value)
                NEPCreate(PetscObjectComm((PetscObject)ptA->_petsc), &nep);
            else
                PEPCreate(PetscObjectComm((PetscObject)ptA->_petsc), &pep);
            Mat S;
            User<Type, K, typename std::conditional<std::is_same<SType, NEP>::value, EPS, SType>::type> user = nullptr;
            User<Type, K, NEP> func = nullptr;
            MatType type;
            PetscBool isType;
            PetscBool isTwoSided;
            MatGetType(ptA->_petsc, &type);
            PetscStrcmp(type, MATNEST, &isType);
            PetscInt m;
            MatGetLocalSize(ptA->_petsc, &m, NULL);
            if(!codeA) {
                Type* ptB = (c == 0 ? GetAny<Type*>((*B)(stack)) : NULL);
                if(std::is_same<SType, EPS>::value)
                    EPSSetOperators(eps, ptA->_petsc, c == 0 && ptB ? ptB->_petsc : NULL);
                else if(std::is_same<SType, SVD>::value)
                    SVDSetOperators(svd, ptA->_petsc, NULL);
                else if(std::is_same<SType, PEP>::value) {
                    Mat* tab = new Mat[ptTab->N()];
                    for(int i = 0; i < ptTab->N(); ++i)
                        tab[i] = ptTab->operator[](i)._petsc;
                    PEPSetOperators(pep, ptTab->N(), tab);
                    delete [] tab;
                }
            }
            else {
                PetscInt M;
                MatGetSize(ptA->_petsc, &M, NULL);
                if(!std::is_same<SType, NEP>::value) {
                    PetscNew(&user);
                    user->mat = new typename eigensolver<Type, K, typename std::conditional<std::is_same<SType, NEP>::value, EPS, SType>::type>::MatF_O(m, stack, codeA);
                    MatCreateShell(PetscObjectComm((PetscObject)ptA->_petsc), m, m, M, M, user, &S);
                    MatShellSetOperation(S, MATOP_MULT, (PetscErrorCodeFn *)MatMult_User<Type, K, typename std::conditional<std::is_same<SType, NEP>::value, EPS, SType>::type>);
                    if(std::is_same<SType, EPS>::value)
                        EPSSetOperators(eps, S, NULL);
                    else if(std::is_same<SType, SVD>::value)
                        SVDSetOperators(svd, S, NULL);
                }
                else {
                    Type* ptB = GetAny<Type*>((*B)(stack));
                    PetscNew(&func);
                    func->F = new typename eigensolver<Type, K, NEP>::ScalarF_O(stack, codeA);
                    func->J = new typename eigensolver<Type, K, NEP>::ScalarF_O(stack, codeB);
                    NEPSetFunction(nep, ptA->_petsc, ptA->_petsc, FormFun, &func);
                    NEPSetJacobian(nep, ptB->_petsc, FormJac, &func);
                }
            }
            if (nargs[0]) {
                std::string* options = GetAny< std::string* >((*nargs[0])(stack));
                PetscOptionsInsertString(NULL, options->c_str());
            }
            if(nargs[1]) {
                if(std::is_same<SType, EPS>::value)
                    EPSSetOptionsPrefix(eps, GetAny<std::string*>((*nargs[1])(stack))->c_str());
                else if(std::is_same<SType, SVD>::value)
                    SVDSetOptionsPrefix(svd, GetAny<std::string*>((*nargs[1])(stack))->c_str());
                else if(std::is_same<SType, NEP>::value)
                    NEPSetOptionsPrefix(nep, GetAny<std::string*>((*nargs[1])(stack))->c_str());
                else
                    PEPSetOptionsPrefix(pep, GetAny<std::string*>((*nargs[1])(stack))->c_str());
            }
            KSP empty = NULL;
            if(std::is_same<SType, EPS>::value) {
                EPSSetFromOptions(eps);
                ST st;
                EPSGetST(eps, &st);
                if(ptA->_ksp) {
                    STGetKSP(st, &empty);
                    PetscObjectReference((PetscObject)empty);
                    STSetKSP(st, ptA->_ksp);
                }
                else {
                    KSP ksp;
                    PC pc;
                    STGetKSP(st, &ksp);
                    KSPGetPC(ksp, &pc);
                    PCSetFromOptions(pc);
                    PCType type;
                    PCGetType(pc, &type);
                    PetscBool isFieldSplit;
                    PetscStrcmp(type, PCFIELDSPLIT, &isFieldSplit);
                    if(isFieldSplit) {
                        KN<double>* fields = nargs[5] ? GetAny<KN<double>*>((*nargs[5])(stack)) : nullptr;
                        KN<String>* names = nargs[6] ? GetAny<KN<String>*>((*nargs[6])(stack)) : nullptr;
                        KN<Matrice_Creuse<upscaled_type<PetscScalar>>>* mS = nargs[11] ? GetAny<KN<Matrice_Creuse<upscaled_type<PetscScalar>>>*>((*nargs[11])(stack)) : nullptr;
                        KN<double>* pL = nargs[12] ? GetAny<KN<double>*>((*nargs[12])(stack)) : nullptr;
                        STGetKSP(st, &ksp);
                        KSPSetOperators(ksp, ptA->_petsc, ptA->_petsc);
                        setFieldSplitPC(ptA, ksp, fields, names, mS, pL);
                        EPSSetUp(eps);
                        if(ptA->_vS && !ptA->_vS->empty()) {
                            PC pc;
                            KSPGetPC(ksp, &pc);
                            PCSetUp(pc);
                            PETSc::setCompositePC(pc, ptA->_vS);
                        }
                    }
                }
            }
            else if(std::is_same<SType, SVD>::value) {
                SVDSetFromOptions(svd);
                SVDSetUp(svd);
            }
            else if(std::is_same<SType, NEP>::value) {
                NEPSetFromOptions(nep);
                NEPSetUp(nep);
            }
            else {
                PEPSetFromOptions(pep);
                PEPSetUp(pep);
            }
            FEbaseArrayKn<K>* eigenvectors = nargs[3] ? GetAny<FEbaseArrayKn<K>*>((*nargs[3])(stack)) : nullptr;
            Vec* basis = nullptr;
            PetscInt n = 0;
            if(eigenvectors) {
                ffassert(!isType);
                if(eigenvectors->N > 0 && eigenvectors->get(0) && eigenvectors->get(0)->n > 0) {
                    n = eigenvectors->N;
                    basis = new Vec[n];
                    for(int i = 0; i < n; ++i) {
                        MatCreateVecs(ptA->_petsc, &basis[i], NULL);
                        PetscScalar* pt;
                        VecGetArray(basis[i], &pt);
                        if(!(std::is_same<PetscScalar, double>::value && std::is_same<K, std::complex<double>>::value))
                            distributedVec(ptA->_num, ptA->_first, ptA->_last, static_cast<K*>(*(eigenvectors->get(i))), pt, eigenvectors->get(i)->n);
                        VecRestoreArray(basis[i], &pt);
                    }
                }
                eigenvectors->resize(0);
            }
            FEbaseArrayKn<K>* othervectors = nargs[7] ? GetAny<FEbaseArrayKn<K>*>((*nargs[7])(stack)) : nullptr;
            Vec* otherbasis = nullptr;
            PetscInt othern = 0;
            if(othervectors) {
                ffassert(!isType);
                if(othervectors->N > 0 && othervectors->get(0) && othervectors->get(0)->n > 0) {
                    othern = othervectors->N;
                    otherbasis = new Vec[othern];
                    for(int i = 0; i < othern; ++i) {
                        MatCreateVecs(ptA->_petsc, &otherbasis[i], NULL);
                        PetscScalar* pt;
                        VecGetArray(otherbasis[i], &pt);
                        if(!(std::is_same<PetscScalar, double>::value && std::is_same<K, std::complex<double>>::value))
                            distributedVec(ptA->_num, ptA->_first, ptA->_last, static_cast<K*>(*(othervectors->get(i))), pt, othervectors->get(i)->n);
                        VecRestoreArray(otherbasis[i], &pt);
                    }
                }
                othervectors->resize(0);
            }
            if(std::is_same<SType, EPS>::value) {
                EPSGetTwoSided(eps, &isTwoSided);
                if(n)
                    EPSSetInitialSpace(eps, n, basis);
                if(othern && isTwoSided)
                    EPSSetLeftInitialSpace(eps, othern, otherbasis);
                KNM<PetscScalar>* ptDeflation = nargs[9] ? GetAny<KNM<PetscScalar>*>((*nargs[9])(stack)) : NULL;
                if(ptDeflation && ptDeflation->M()) {
                    PetscInt m;
                    MatGetLocalSize(ptA->_petsc, &m, NULL);
                    ffassert(m == ptDeflation->N());
                    Vec* deflation = new Vec[ptDeflation->M()];
                    for(int i = 0; i < ptDeflation->M(); ++i)
                        VecCreateMPIWithArray(PetscObjectComm((PetscObject)ptA->_petsc), 1, ptDeflation->N(), PETSC_DECIDE, &ptDeflation->operator( )(0, i), deflation + i);
                    EPSSetDeflationSpace(eps, ptDeflation->M(), deflation);
                    for(int i = 0; i < ptDeflation->M(); ++i)
                        VecDestroy(deflation + i);
                    delete [] deflation;
                }
                EPSSolve(eps);
            }
            else if(std::is_same<SType, SVD>::value) {
                SVDSetInitialSpaces(svd, othern, otherbasis, n, basis);
                SVDSolve(svd);
            }
            else if(std::is_same<SType, NEP>::value) {
                if(n)
                    NEPSetInitialSpace(nep, n, basis);
                NEPSolve(nep);
            }
            else {
                if(n)
                    PEPSetInitialSpace(pep, n, basis);
                PEPSolve(pep);
            }
            for(int i = 0; i < n; ++i)
                VecDestroy(&basis[i]);
            delete [] basis;
            for(int i = 0; i < othern; ++i)
                VecDestroy(&otherbasis[i]);
            delete [] otherbasis;
            PetscInt nconv;
            if(std::is_same<SType, EPS>::value)
                EPSGetConverged(eps, &nconv);
            else if(std::is_same<SType, SVD>::value)
                SVDGetConverged(svd, &nconv);
            else if(std::is_same<SType, NEP>::value)
                NEPGetConverged(nep, &nconv);
            else
                PEPGetConverged(pep, &nconv);
            if(nconv > 0 && ((nargs[2] || nargs[3] || nargs[4] || nargs[10]) || ((std::is_same<SType, SVD>::value || (std::is_same<SType, EPS>::value && isTwoSided)) && (nargs[7] || nargs[8])))) {
                KN<typename std::conditional<!std::is_same<SType, SVD>::value, K, PetscReal>::type>* eigenvalues = nargs[2] ? GetAny<KN<typename std::conditional<!std::is_same<SType, SVD>::value, K, PetscReal>::type>*>((*nargs[2])(stack)) : nullptr;
                KN<PetscReal>* errorestimate = nargs[10] ? GetAny<KN<PetscReal>*>((*nargs[10])(stack)) : nullptr;
                KNM<K>* eigenarray = nargs[4] ? GetAny<KNM<K>*>((*nargs[4])(stack)) : nullptr;
                KNM<K>* otherarray = nargs[8] ? GetAny<KNM<K>*>((*nargs[8])(stack)) : nullptr;
                KN<long>* conjugatearray = nargs[13] ? GetAny<KN<long>*>((*nargs[13])(stack)) : nullptr;
                if(eigenvalues)
                    eigenvalues->resize(nconv);
                if(eigenvectors && !isType)
                    eigenvectors->resize(nconv);
                if(othervectors && !isType)
                    othervectors->resize(nconv);
                if(errorestimate)
                    errorestimate->resize(nconv);
                if(eigenarray)
                    eigenarray->resize(m, nconv);
                if(conjugatearray) {
                    conjugatearray->resize(nconv);
                    *conjugatearray = 0L;
                }
                Vec xr, xi, yr, yi;
                PetscInt n, nr;
                if(eigenvectors || eigenarray || othervectors || otherarray) {
                    MatCreateVecs(ptA->_petsc, &xi, &xr);
                    VecGetLocalSize(xi, &nr);
                    if(otherarray)
                        otherarray->resize(nr, nconv);
                    VecGetLocalSize(xr, &n);
                } else xr = xi = NULL;
                if(std::is_same<SType, EPS>::value && (othervectors || otherarray))
                    MatCreateVecs(ptA->_petsc, &yi, &yr);
                else yr = yi = NULL;
                for(PetscInt i = 0; i < nconv; ++i) {
                    PetscScalar kr, ki = 0;
                    PetscReal sigma;
                    PetscReal errest;
                    if(std::is_same<SType, EPS>::value) {
                        EPSGetEigenpair(eps, i, &kr, &ki, (eigenvectors || eigenarray) ? xr : NULL, (eigenvectors || eigenarray) && std::is_same<PetscScalar, double>::value ? xi : NULL);
                        if((othervectors || otherarray) && isTwoSided)
                            EPSGetLeftEigenvector(eps, i, yr, yi);
                        if(errorestimate)
                            EPSGetErrorEstimate(eps, i, &errest);
                    }
                    else if(std::is_same<SType, SVD>::value)
                        SVDGetSingularTriplet(svd, i, &sigma, xr, xi);
                    else if(std::is_same<SType, NEP>::value) {
                        NEPGetEigenpair(nep, i, &kr, &ki, (eigenvectors || eigenarray) ? xr : NULL, (eigenvectors || eigenarray) && std::is_same<PetscScalar, double>::value ? xi : NULL);
                        if(errorestimate)
                            NEPGetErrorEstimate(nep, i, &errest);
                    }
                    else {
                        PEPGetEigenpair(pep, i, &kr, &ki, (eigenvectors || eigenarray) ? xr : NULL, (eigenvectors || eigenarray) && std::is_same<PetscScalar, double>::value ? xi : NULL);
                        if(errorestimate)
                            PEPGetErrorEstimate(pep, i, &errest);
                    }
                    const bool conjugate = std::is_same<PetscScalar, double>::value && !std::is_same<SType, SVD>::value && ki != 0.0;
                    if(conjugate && conjugatearray) {
                        (*conjugatearray)[i] = 1L;
                        if(i + 1 < nconv) (*conjugatearray)[i + 1] = 1L;
                    }
                    if(eigenvectors || eigenarray || othervectors || otherarray) {
                        PetscScalar* tmpr;
                        PetscScalar* tmpi;
                        PetscScalar* tmp2r;
                        PetscScalar* tmp2i;
                        VecGetArray(xr, &tmpr);
                        if(std::is_same<SType, EPS>::value && (othervectors || otherarray))
                            VecGetArray(yr, &tmp2r);
                        K* pt, *pti;
                        if(!std::is_same<SType, SVD>::value) {
                            if(std::is_same<PetscScalar, double>::value && std::is_same<K, std::complex<double>>::value) {
                                VecGetArray(xi, &tmpi);
                                pt = new K[n];
                                copy(pt, n, tmpr, tmpi);
                                if(std::is_same<SType, EPS>::value && (othervectors || otherarray)) {
                                    VecGetArray(yi, &tmp2i);
                                    pti = new K[nr];
                                    copy(pti, nr, tmp2r, tmp2i);
                                }
                            }
                            else {
                                pt = reinterpret_cast<K*>(tmpr);
                                if(std::is_same<SType, EPS>::value && (othervectors || otherarray))
                                    pti = reinterpret_cast<K*>(tmp2r);
                            }
                        }
                        else {
                            pt = reinterpret_cast<K*>(tmpr);
                            VecGetArray(xi, reinterpret_cast<PetscScalar**>(&pti));
                            tmpi = reinterpret_cast<PetscScalar*>(pti);
                        }
                        if(!isType && (ptA->_A || ptA->_exchange)) {
                            KN<K> cpy(ptA->_A ? ptA->_A->getDof() : ptA->_exchange[0]->getDof());
                            cpy = K(0.0);
                            HPDDM::Subdomain<K>::template distributedVec<1>(ptA->_num, ptA->_first, ptA->_last, static_cast<K*>(cpy), pt, static_cast<PetscInt>(cpy.n), 1);
                            if(ptA->_A)
                                ptA->_A->HPDDM::template Subdomain<PetscScalar>::exchange(static_cast<K*>(cpy));
                            else
                                ptA->_exchange[0]->HPDDM::template Subdomain<PetscScalar>::exchange(static_cast<K*>(cpy));
                            if(eigenvectors)
                                eigenvectors->set(i, cpy);
                            if(eigenarray && !codeA) {
                                KN<K> cpy(m, pt);
                                (*eigenarray)(':', long(i)) = cpy;
                            }
                            if(std::is_same<SType, SVD>::value || (std::is_same<SType, EPS>::value && (othervectors || otherarray) && isTwoSided)) {
                                KN<K> cpy(ptA->_cnum && ptA->_exchange[1] ? ptA->_exchange[1]->getDof() : (ptA->_A ? ptA->_A->getDof() : 0));
                                cpy = K(0.0);
                                if(ptA->_cnum && ptA->_exchange[1]) {
                                    HPDDM::Subdomain<K>::template distributedVec<1>(ptA->_cnum, ptA->_cfirst, ptA->_clast, static_cast<K*>(cpy), pti, static_cast<PetscInt>(cpy.n), 1);
                                    ptA->_exchange[1]->HPDDM::template Subdomain<PetscScalar>::exchange(static_cast<K*>(cpy));
                                }
                                else if(ptA->_A) {
                                    HPDDM::Subdomain<K>::template distributedVec<1>(ptA->_num, ptA->_first, ptA->_last, static_cast<K*>(cpy), pti, static_cast<PetscInt>(cpy.n), 1);
                                    ptA->_A->HPDDM::template Subdomain<PetscScalar>::exchange(static_cast<K*>(cpy));
                                }
                                else
                                    ffassert(0);
                                if(othervectors)
                                    othervectors->set(i, cpy);
                                if(otherarray && !codeA) {
                                    KN<K> cpy(nr, pti);
                                    (*otherarray)(':', long(i)) = cpy;
                                }
                            }
                            if(conjugate && i + 1 < nconv) {
                                PetscScalar* tmpi_conj;
                                VecGetArray(xi, &tmpi_conj);
                                K* pt_imag = reinterpret_cast<K*>(tmpi_conj);
                                KN<K> cpy(ptA->_A ? ptA->_A->getDof() : ptA->_exchange[0]->getDof());
                                cpy = K(0.0);
                                HPDDM::Subdomain<K>::template distributedVec<1>(ptA->_num, ptA->_first, ptA->_last, static_cast<K*>(cpy), pt_imag, static_cast<PetscInt>(cpy.n), 1);
                                if(ptA->_A)
                                    ptA->_A->HPDDM::template Subdomain<PetscScalar>::exchange(static_cast<K*>(cpy));
                                else
                                    ptA->_exchange[0]->HPDDM::template Subdomain<PetscScalar>::exchange(static_cast<K*>(cpy));
                                if(eigenvectors)
                                    eigenvectors->set(i + 1, cpy);
                                if(eigenarray && !codeA) {
                                    KN<K> tmp(m, pt_imag);
                                    (*eigenarray)(':', long(i + 1)) = tmp;
                                }
                                VecRestoreArray(xi, &tmpi_conj);
                                if(std::is_same<SType, EPS>::value && (othervectors || otherarray) && isTwoSided) {
                                    PetscScalar* tmp2i_conj;
                                    VecGetArray(yi, &tmp2i_conj);
                                    K* pti_imag = reinterpret_cast<K*>(tmp2i_conj);
                                    KN<K> cpy_imag2(ptA->_cnum && ptA->_exchange[1] ? ptA->_exchange[1]->getDof() : (ptA->_A ? ptA->_A->getDof() : 0));
                                    cpy_imag2 = K(0.0);
                                    if(ptA->_cnum && ptA->_exchange[1]) {
                                        HPDDM::Subdomain<K>::template distributedVec<1>(ptA->_cnum, ptA->_cfirst, ptA->_clast, static_cast<K*>(cpy_imag2), pti_imag, static_cast<PetscInt>(cpy_imag2.n), 1);
                                        ptA->_exchange[1]->HPDDM::template Subdomain<PetscScalar>::exchange(static_cast<K*>(cpy_imag2));
                                    }
                                    else if(ptA->_A) {
                                        HPDDM::Subdomain<K>::template distributedVec<1>(ptA->_num, ptA->_first, ptA->_last, static_cast<K*>(cpy_imag2), pti_imag, static_cast<PetscInt>(cpy_imag2.n), 1);
                                        ptA->_A->HPDDM::template Subdomain<PetscScalar>::exchange(static_cast<K*>(cpy_imag2));
                                    }
                                    else
                                        ffassert(0);
                                    if(othervectors)
                                        othervectors->set(i + 1, cpy_imag2);
                                    if(otherarray && !codeA) {
                                        KN<K> tmp(nr, pti_imag);
                                        (*otherarray)(':', long(i + 1)) = tmp;
                                    }
                                    VecRestoreArray(yi, &tmp2i_conj);
                                }
                            }
                        }
                        if(codeA || isType || !ptA->_A) {
                            if(eigenarray) {
                                KN<K> cpy(m, pt);
                                (*eigenarray)(':', long(i)) = cpy;
                            }
                            if(otherarray) {
                                KN<K> cpy(nr, pti);
                                (*otherarray)(':', long(i)) = cpy;
                            }
                            if(conjugate && i + 1 < nconv) {
                                PetscScalar* tmpi_conj;
                                VecGetArray(xi, &tmpi_conj);
                                K* pt_imag = reinterpret_cast<K*>(tmpi_conj);
                                if(eigenarray) {
                                    KN<K> tmp(m, pt_imag);
                                    (*eigenarray)(':', long(i + 1)) = tmp;
                                }
                                VecRestoreArray(xi, &tmpi_conj);
                                if(std::is_same<SType, EPS>::value && otherarray && isTwoSided) {
                                    PetscScalar* tmp2i_conj;
                                    VecGetArray(yi, &tmp2i_conj);
                                    K* pti_imag = reinterpret_cast<K*>(tmp2i_conj);
                                    KN<K> tmp(nr, pti_imag);
                                    (*otherarray)(':', long(i + 1)) = tmp;
                                    VecRestoreArray(yi, &tmp2i_conj);
                                }
                            }
                        }
                        if(!std::is_same<SType, SVD>::value && std::is_same<PetscScalar, double>::value && std::is_same<K, std::complex<double>>::value) {
                            delete [] pt;
                            delete [] pti;
                        }
                        if(std::is_same<SType, SVD>::value || (std::is_same<PetscScalar, double>::value && std::is_same<K, std::complex<double>>::value)) {
                            VecRestoreArray(xi, &tmpi);
                            if(std::is_same<SType, EPS>::value && (othervectors || otherarray))
                                VecRestoreArray(yi, &tmp2i);
                        }
                        if(std::is_same<SType, EPS>::value && (othervectors || otherarray))
                            VecRestoreArray(yr, &tmp2r);
                        VecRestoreArray(xr, &tmpr);
                    }
                    if(eigenvalues) {
                        if(!std::is_same<SType, SVD>::value) {
                            assign<SType>(static_cast<typename std::conditional<!std::is_same<SType, SVD>::value, K*, PetscReal*>::type>(*eigenvalues + i), kr, ki);
                            if(conjugate && i + 1 < nconv)
                                eigenvalues->operator[](i + 1) = PetscRealPart(ki);
                        }
                        else
                            eigenvalues->operator[](i) = sigma;
                    }
                    if(errorestimate) {
                        errorestimate->operator[](i) = errest;
                        if(conjugate && i + 1 < nconv)
                            errorestimate->operator[](i + 1) = errest;
                    }
                    if(conjugate)
                        ++i;
                }
                if(eigenvectors || eigenarray || othervectors || otherarray) {
                    VecDestroy(&xr);
                    VecDestroy(&xi);
                    VecDestroy(&yr);
                    VecDestroy(&yi);
                }
            }
            if(user) {
                MatDestroy(&S);
                delete user->mat;
                PetscFree(user);
            }
            if(std::is_same<SType, EPS>::value) {
                if(empty) {
                    ST st;
                    EPSGetST(eps, &st);
                    STSetKSP(st, empty);
                    PetscObjectDereference((PetscObject)empty);
                }
                EPSDestroy(&eps);
            }
            else if(std::is_same<SType, SVD>::value)
                SVDDestroy(&svd);
            else if(std::is_same<SType, NEP>::value)
                NEPDestroy(&nep);
            else
                PEPDestroy(&pep);
            return static_cast<long>(nconv);
        }
        else
            return 0L;
    }
    else
        return 0L;
}
template<class Type, class K, class SType>
static PetscErrorCode MatMult_User(Mat A, Vec x, Vec y) {
    User<Type, K, SType>   user;
    const PetscScalar*       in;
    PetscScalar*            out;
    PetscErrorCode         ierr;

    PetscFunctionBegin;
    ierr = MatShellGetContext(A, &user); CHKERRQ(ierr);
    typename SLEPc::eigensolver<Type, K, SType>::MatF_O* mat = reinterpret_cast<typename SLEPc::eigensolver<Type, K, SType>::MatF_O*>(user->mat);
    VecGetArrayRead(x, &in);
    VecGetArray(y, &out);
    KN_<PetscScalar> xx(const_cast<PetscScalar*>(in), mat->N);
    KN_<PetscScalar> yy(out, mat->N);
    yy = *mat * xx;
    VecRestoreArray(y, &out);
    VecRestoreArrayRead(x, &in);
    PetscFunctionReturn(PETSC_SUCCESS);
}
void finalizeSLEPc() {
    SlepcFinalize();
}
template<class K, typename std::enable_if<std::is_same<K, double>::value>::type* = nullptr>
void addSLEPc() {
    Global.Add("EPSSolveComplex", "(", new SLEPc::eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, std::complex<double>, EPS>());
    Global.Add("EPSSolveComplex", "(", new SLEPc::eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, std::complex<double>, EPS>(1));
    Global.Add("EPSSolveComplex", "(", new SLEPc::eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, std::complex<double>, EPS>(1, 1));
}
template<class K, typename std::enable_if<!std::is_same<K, double>::value>::type* = nullptr>
void addSLEPc() { }
}

static void Init() {
    //  to load only once
    aType t;
    int r;
#ifdef WITH_slepccomplex
    const char * mmmm= "Petsc Slepc complex";
#else
    const char * mmmm= "Petsc Slepc real";
#endif
    if(!zzzfff->InMotClef(mmmm,t,r))
    {
#ifdef PETScandSLEPc
        Init_PETSc();
#endif
        int argc = pkarg->n;
        char** argv = new char*[argc];
        for(int i = 0; i < argc; ++i)
            argv[i] = const_cast<char*>((*(*pkarg)[i].getap())->c_str());
        PetscBool isInitialized;
        PetscInitialized(&isInitialized);
        if(!isInitialized && mpirank == 0)
            std::cout << "PetscInitialize has not been called, do not forget to load PETSc before loading SLEPc" << std::endl;
        SlepcInitialize(&argc, &argv, nullptr, "");
        delete [] argv;
        ff_atend(SLEPc::finalizeSLEPc);
        SLEPc::addSLEPc<PetscScalar>();
        Global.Add("EPSSolve", "(", new SLEPc::eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, EPS>());
        Global.Add("EPSSolve", "(", new SLEPc::eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, EPS>(1));
        Global.Add("EPSSolve", "(", new SLEPc::eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, EPS>(1, 1));
        Global.Add("SVDSolve", "(", new SLEPc::eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, SVD>(1, 1));
        Global.Add("NEPSolve", "(", new SLEPc::eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, NEP>(1, 1, 1));
        Global.Add("PEPSolve", "(", new SLEPc::eigensolver<PETSc::DistributedCSR<HpSchwarz<PetscScalar>>, PetscScalar, PEP>(1, 1, 1, 1));
        if(verbosity>1)cout << "*** End:: load PETSc & SELPc "<< typeid(PetscScalar).name() <<"\n\n"<<endl;
        zzzfff->Add(mmmm, atype<Dmat*>());
    }
    else {
        if(verbosity>1)cout << "*** reload and skip load PETSc & SELPc "<< typeid(PetscScalar).name() <<"\n\n"<<endl;
    }
}
#else
static void Init() {
     Init_PETSc();
}
#endif

#if defined(__clang__)
  #pragma clang diagnostic pop
#endif
