Vector *NestedMGIter(int NoLevels, QMatrix *A, Vector *x, Vector *b,
	    Matrix *R, Matrix *P, int Gamma,
            IterProcType SmoothProc, int Nu1, int Nu2, 
	    PrecondProcType PrecondProc, double Omega,
            IterProcType SolvProc, int NuC,
	    PrecondProcType PrecondProcC, double OmegaC)
/* nested multigrid method */
{
    int Level;

    /* solution of system of equations on coarsest grid */
    V_SetAllCmp(&x[0], 0.0);
    MGStep(NoLevels, A, x, b, R, P, 0, Gamma,
           SmoothProc, Nu1, Nu2, PrecondProc, Omega,
           SolvProc, NuC, PrecondProcC, OmegaC);

    for (Level = 1; Level < NoLevels; Level++) {
        /* prolongation of solution to finer grid */
        if (P != NULL)
            Asgn_VV(&x[Level], Mul_MV(&P[Level], &x[Level - 1]));
	else
            Asgn_VV(&x[Level], Mul_MV(Transp_M(&R[Level - 1]), &x[Level - 1]));
        /* solution of system of equations on finer grid with
           multigrid method */
        MGStep(NoLevels, A, x, b, R, P, Level, Gamma,
               SmoothProc, Nu1, Nu2, PrecondProc, Omega,
               SolvProc, NuC, PrecondProcC, OmegaC);
    }

    /* submission of reached accuracy to RTC */
    RTCResult(1, l2Norm_V(Sub_VV(&b[NoLevels - 1],
              Mul_QV(&A[NoLevels - 1], &x[NoLevels - 1]))),
              l2Norm_V(&b[NoLevels - 1]), NestedMGIterId);

    return(&x[NoLevels - 1]);
}
Vector *MGIter(int NoLevels, QMatrix *A, Vector *x, Vector *b,
	    Matrix *R, Matrix *P, int MaxIter, int Gamma,
            IterProcType SmoothProc, int Nu1, int Nu2, 
	    PrecondProcType PrecondProc, double Omega,
            IterProcType SolvProc, int NuC,
	    PrecondProcType PrecondProcC, double OmegaC)
/* multigrid method with residual termination control */
{
    int Iter;
    double bNorm;
    size_t Dim;
    Vector r;

    Dim = Q_GetDim(&A[NoLevels - 1]);
    V_Constr(&r, "r", Dim, Normal, True);

    if (LASResult() == LASOK) {
        bNorm = l2Norm_V(&b[NoLevels - 1]);

        Iter = 0;
        /* r = b - A x(i) at NoLevels - 1 */
        Asgn_VV(&r, Sub_VV(&b[NoLevels - 1], Mul_QV(&A[NoLevels - 1], &x[NoLevels - 1])));
        while (!RTCResult(Iter, l2Norm_V(&r), bNorm, MGIterId)
            && Iter < MaxIter) {
            Iter++;
            /* one multigrid step */
            MGStep(NoLevels, A, x, b, R, P, NoLevels - 1, Gamma,
		   SmoothProc, Nu1, Nu2, PrecondProc, Omega,
                   SolvProc, NuC, PrecondProcC, OmegaC);
            /* r = b - A x(i) at NoLevels - 1 */
            Asgn_VV(&r, Sub_VV(&b[NoLevels - 1], Mul_QV(&A[NoLevels - 1], &x[NoLevels - 1])));
        }
    }

    V_Destr(&r);

    return(&x[NoLevels - 1]);
}
Vector *MGStep(int NoLevels, QMatrix *A, Vector *x, Vector *b,
            Matrix *R, Matrix *P, int Level, int Gamma,
            IterProcType SmoothProc, int Nu1, int Nu2, 
	    PrecondProcType PrecondProc, double Omega,
            IterProcType SolvProc, int NuC,
	    PrecondProcType PrecondProcC, double OmegaC)
/* one multigrid iteration */
{
    int CoarseMGIter; /* multi grid iteration counter for coarser grid */

    if (Level == 0) {
        /* solving of system of equations for the residual on the coarsest grid */
        (*SolvProc)(&A[Level], &x[Level], &b[Level], NuC, PrecondProcC, OmegaC);
    } else {
        /* pre-smoothing - Nu1 iterations */
        (*SmoothProc)(&A[Level], &x[Level], &b[Level], Nu1, PrecondProc, Omega);
        /* restiction of the residual to the coarser grid */
        Asgn_VV(&b[Level - 1], Mul_MV(&R[Level - 1],
	    Sub_VV(&b[Level], Mul_QV(&A[Level], &x[Level]))));
        /* initialisation of vector of unknowns on the coarser grid */
        V_SetAllCmp(&x[Level - 1], 0.0);
        /* solving of system of equations for the residual on the coarser grid */
        for (CoarseMGIter = 1; CoarseMGIter <= Gamma; CoarseMGIter++)
            MGStep(NoLevels, A, x, b, R, P, Level - 1, Gamma,
		   SmoothProc, Nu1, Nu2, PrecondProc, Omega,
                   SolvProc, NuC, PrecondProcC, OmegaC);
        /* interpolation of the solution from the coarser grid */
	if (P != NULL)
            AddAsgn_VV(&x[Level], Mul_MV(&P[Level], &x[Level - 1]));
	else
            AddAsgn_VV(&x[Level], Mul_MV(Transp_M(&R[Level - 1]), &x[Level - 1]));
        /* post-smoothing - Nu2 iterations */
        (*SmoothProc)(&A[Level], &x[Level], &b[Level], Nu2, PrecondProc, Omega);
    }

    return(&x[Level]);
}
double LinearImplicitSystem::MGStep(int Level,            // Level
                       double Eps1,          // Tolerance
                       int MaxIter,          // n iterations - number of mg cycles
                       const uint Gamma,     // Control V W cycle
                       const uint Nc_pre,    // n pre-smoothing smoother iterations
                       const uint Nc_coarse, // n coarse smoother iterations
                       const uint Nc_post    // n post-smoothing smoother iterations
                      ) {


    std::pair<uint,double> rest;

    if (Level == 0) {
///  std::cout << "************ REACHED THE BOTTOM *****************"<< std::endl;

#ifdef DEFAULT_PRINT_CONV
        _LinSolver[Level]->_EPS->close();
        double xNorm0=_LinSolver[Level]->_EPS->linfty_norm();
        _LinSolver[Level]->_RESC->close();
        double bNorm0=_LinSolver[Level]->_RESC->linfty_norm();
        _LinSolver[Level]->_KK->close();
        double ANorm0=_LinSolver[Level]->_KK->l1_norm();
        std::cout << "Level " << Level << " ANorm l1 " << ANorm0 << " bNorm linfty " << bNorm0  << " xNormINITIAL linfty " << xNorm0 << std::endl;
#endif

        rest = _LinSolver[Level]->solve(*_LinSolver[Level]->_KK,*_LinSolver[Level]->_KK,*_LinSolver[Level]->_EPS,*_LinSolver[Level]->_RESC,DEFAULT_EPS_LSOLV_C,Nc_coarse);  //****** smooth on the coarsest level

#ifdef DEFAULT_PRINT_CONV
        std::cout << " Coarse sol : res-norm: " << rest.second << " n-its: " << rest.first << std::endl;
        _LinSolver[Level]->_EPS->close();
        std::cout << " Norm of x after the coarse solution " << _LinSolver[Level]->_EPS->linfty_norm() << std::endl;
#endif

        _LinSolver[Level]->_RES->resid(*_LinSolver[Level]->_RESC,*_LinSolver[Level]->_EPS,*_LinSolver[Level]->_KK);      //************ compute the coarse residual

        _LinSolver[Level]->_RES->close();
        std::cout << "COARSE Level " << Level << " res linfty " << _LinSolver[Level]->_RES->linfty_norm() << " res l2 " << _LinSolver[Level]->_RES->l2_norm() << std::endl;

    }

    else {

///  std::cout << "************ BEGIN ONE PRE-SMOOTHING *****************"<< std::endl;
#ifdef DEFAULT_PRINT_TIME
        std::clock_t start_time=std::clock();
#endif
#ifdef DEFAULT_PRINT_CONV
        _LinSolver[Level]->_EPS->close();
        double xNormpre=_LinSolver[Level]->_EPS->linfty_norm();
        _LinSolver[Level]->_RESC->close();
        double bNormpre=_LinSolver[Level]->_RESC->linfty_norm();
        _LinSolver[Level]->_KK->close();
        double ANormpre=_LinSolver[Level]->_KK->l1_norm();
        std::cout << "Level " << Level << " ANorm l1 " << ANormpre << " bNorm linfty " << bNormpre  << " xNormINITIAL linfty " << xNormpre << std::endl;
#endif

        rest = _LinSolver[Level]->solve(*_LinSolver[Level]->_KK,*_LinSolver[Level]->_KK,*_LinSolver[Level]->_EPS,*_LinSolver[Level]->_RESC,DEFAULT_EPS_PREPOST, Nc_pre); //****** smooth on the finer level

#ifdef DEFAULT_PRINT_CONV
        std::cout << " Pre Lev: " << Level << ", res-norm: " << rest.second << " n-its: " << rest.first << std::endl;
#endif
#ifdef DEFAULT_PRINT_TIME
        std::clock_t end_time=std::clock();
        std::cout << " time ="<< double(end_time- start_time) / CLOCKS_PER_SEC << std::endl;
#endif

        _LinSolver[Level]->_RES->resid(*_LinSolver[Level]->_RESC,*_LinSolver[Level]->_EPS,*_LinSolver[Level]->_KK);//********** compute the residual

///    std::cout << "************ END ONE PRE-SMOOTHING *****************"<< std::endl;

///    std::cout << ">>>>>>>> BEGIN ONE DESCENT >>>>>>>>>>"<< std::endl;

        _LinSolver[Level-1]->_RESC->matrix_mult(*_LinSolver[Level]->_RES,*_RR[Level-1]);//****** restrict the residual from the finer grid ( new rhs )
        _LinSolver[Level-1]->_EPS->close();                                  //initial value of x for the presmoothing iterations
        _LinSolver[Level-1]->_EPS->zero();                                  //initial value of x for the presmoothing iterations

        for (uint g=1; g <= Gamma; g++)
            MGStep(Level-1,Eps1,MaxIter,Gamma,Nc_pre,Nc_coarse,Nc_post); //***** call MGStep for another possible descent

//at this point you have certainly reached the COARSE level

///      std::cout << ">>>>>>>> BEGIN ONE ASCENT >>>>>>>>"<< std::endl;
#ifdef DEFAULT_PRINT_CONV
        _LinSolver[Level-1]->_RES->close();
        std::cout << "BEFORE PROL Level " << Level << " res linfty " << _LinSolver[Level-1]->_RES->linfty_norm() << " res l2 " << _LinSolver[Level-1]->_RES->l2_norm() << std::endl;
#endif

        _LinSolver[Level]->_RES->matrix_mult(*_LinSolver[Level-1]->_EPS,*_PP[Level]);//******** project the dx from the coarser grid
#ifdef DEFAULT_PRINT_CONV
        _LinSolver[Level]->_RES->close();
        //here, the _res contains the prolongation of dx, so the new x is x_old + P dx
        std::cout << "AFTER PROL Level " << Level << " res linfty " << _LinSolver[Level]->_RES->linfty_norm() << " res l2 " << _LinSolver[Level]->_RES->l2_norm() << std::endl;
#endif
        _LinSolver[Level]->_EPS->add(*_LinSolver[Level]->_RES);// adding the coarser residual to x
        //initial value of x for the post-smoothing iterations
        //_b is the same as before
///   std::cout << "************ BEGIN ONE POST-SMOOTHING *****************"<< std::endl;
        // postsmooting (Nc_post)
#ifdef DEFAULT_PRINT_TIME
        start_time=std::clock();
#endif
#ifdef DEFAULT_PRINT_CONV
        _LinSolver[Level]->_EPS->close();
        double xNormpost=_LinSolver[Level]->_EPS->linfty_norm();
        _LinSolver[Level]->_RESC->close();
        double bNormpost=_LinSolver[Level]->_RESC->linfty_norm();
        _LinSolver[Level]->_KK->close();
        double ANormpost=_LinSolver[Level]->_KK->l1_norm();
        std::cout << "Level " << Level << " ANorm l1 " << ANormpost << " bNorm linfty " << bNormpost << " xNormINITIAL linfty " << xNormpost << std::endl;
#endif

        rest = _LinSolver[Level]->solve(*_LinSolver[Level]->_KK,*_LinSolver[Level]->_KK,*_LinSolver[Level]->_EPS,*_LinSolver[Level]->_RESC,DEFAULT_EPS_PREPOST,Nc_post);  //***** smooth on the coarser level

#ifdef DEFAULT_PRINT_CONV
        std::cout<<" Post Lev: " << Level << ", res-norm: " << rest.second << " n-its: " << rest.first << std::endl;
#endif
#ifdef DEFAULT_PRINT_TIME
        end_time=std::clock();
        std::cout<< " time ="<< double(end_time- start_time) / CLOCKS_PER_SEC << std::endl;
#endif

        _LinSolver[Level]->_RES->resid(*_LinSolver[Level]->_RESC,*_LinSolver[Level]->_EPS,*_LinSolver[Level]->_KK);   //*******  compute the residual

///    std::cout << "************ END ONE POST-SMOOTHING *****************"<< std::endl;

    }

    _LinSolver[Level]->_RES->close();

    return  rest.second;  //it returns the residual norm of whatever level you are in
    // if this is the l2_norm then also the nonlinear solver is computed in the l2 norm
    //WHAT NORM is THIS?!? l2, but PRECONDITIONED!!!

}
//if the residual norm is small enough,exit the cycle, and so also the MGSolve
//this is also the check for the single level solver
//what is the meaning of having multiple cycles for single-grid solver?
//they are all like just a single linear solver loop, where convergence has already been reached,
// and you do another check after the previous one in the linear solver loop
//the big question is:
///@todo why dont you do "res_fine < Eps1"
// instead of  "res_fine < Eps1*(1.+ bNorm_fine)" ???
//because one is for the absolute error and another one is for the relative error
/// This function solves the discrete problem with multigrid solver
void LinearImplicitSystem::MGSolve(double Eps1,          // tolerance for the linear solver
                      int MaxIter,           // n iterations
                      const uint Gamma,     // Control V W cycle
                      const uint Nc_pre,    // n pre-smoothing cycles
                      const uint Nc_coarse, // n coarse cycles
                      const uint Nc_post    // n post-smoothing cycles
                     ) {

#ifdef DEFAULT_PRINT_INFO
    std::cout << "######### BEGIN MG SOLVE ########" << std::endl;
#endif
    double res_fine;

    _LinSolver[GetGridn()-1]->_RESC->close();
    double bNorm_fine =     _LinSolver[GetGridn()-1]->_RESC->l2_norm();
    _LinSolver[GetGridn()-1]->_EPSC->close();
    double x_old_fine = _LinSolver[GetGridn()-1]->_EPSC->l2_norm();

#ifdef DEFAULT_PRINT_INFO
    std::cout << " bNorm_fine l2 "     <<  bNorm_fine                     << std::endl;
    std::cout << " bNorm_fine linfty " << _LinSolver[GetGridn()-1]->_RESC->linfty_norm()  << std::endl;
    std::cout << " xold_fine l2 "      <<  x_old_fine                     << std::endl;
#endif

    // FAS Multigrid (Nested) ---------
    bool NestedMG=false;
    if (NestedMG) {
        _LinSolver[0]->_EPS->zero();
        MGStep(0,1.e-20,MaxIter,Gamma,Nc_pre,Nc_coarse,Nc_post);

        //smooth on the coarse level WITH PHYSICAL b !
        //and compute the residual

        for (uint Level = 1; Level < GetGridn(); Level++) {

            _LinSolver[Level]->_EPS->matrix_mult(*_LinSolver[Level-1]->_EPS,*_PP[Level]);  //**** project the solution

            res_fine = MGStep(Level,Eps1,MaxIter,Gamma,Nc_pre,Nc_coarse,Nc_post);

        }
    } // NestedMG

    // V or W cycle
    int cycle = 0;
    bool exit_mg = false;

    while (!exit_mg && cycle<MaxIter) {

///std::cout << "@@@@@@@@@@ BEGIN MG CYCLE @@@@@@@@"<< std::endl;

///std::cout << "@@@@@@@@@@ start on the finest level @@@@@@@@"<< std::endl;

        res_fine = MGStep(GetGridn()-1,Eps1,MaxIter,Gamma,Nc_pre,Nc_coarse,Nc_post);

///std::cout << "@@@@@@@@@@ back to the finest level @@@@@@@@"<< std::endl;

///std::cout << "@@@@@@@@@@ END MG CYCLE @@@@@@@@"<< std::endl;

        std::cout << "@@@@@@@@@@ CHECK THE RESIDUAL NORM OF THE FINEST LEVEL @@@@@@@@"<< std::endl;

        std::cout << "res_fine: " << res_fine << std::endl;
        std::cout << "bNorm_fine: " << bNorm_fine << std::endl;

        if (res_fine < Eps1*(1. + bNorm_fine)) exit_mg = true;

        cycle++;

#ifdef DEFAULT_PRINT_INFO
        std::cout << " cycle= " << cycle   << " residual= " << res_fine << " \n";
#endif

    }

#ifdef DEFAULT_PRINT_INFO
    std::cout << "######### END MG SOLVE #######"<< std::endl;
#endif
    return;
}
Vector *MGPCGIter(int NoLevels, QMatrix *A, Vector *z, Vector *r,
		   Matrix *R, Matrix *P, int MaxIter, int NoMGIter, int Gamma,
                   IterProcType SmoothProc, int Nu1, int Nu2, 
		   PrecondProcType PrecondProc, double Omega,
                   IterProcType SolvProc, int NuC,
		   PrecondProcType PrecondProcC, double OmegaC)/* multigrid preconditioned CG method */
{
    int Iter, MGIter;
    double Alpha, Beta, Rho, RhoOld = 0.0;
    double bNorm;
    size_t Dim;
    Vector x, p, q, b;

    Dim = Q_GetDim(&A[NoLevels - 1]);
    V_Constr(&x, "x", Dim, Normal, True);
    V_Constr(&p, "p", Dim, Normal, True);
    V_Constr(&q, "q", Dim, Normal, True);
    V_Constr(&b, "b", Dim, Normal, True);

    if (LASResult() == LASOK) {
        /* copy solution and right hand side stored in parameters z and r */
        Asgn_VV(&x, &z[NoLevels - 1]);
        Asgn_VV(&b, &r[NoLevels - 1]);
        
        bNorm = l2Norm_V(&b);
        
        Iter = 0;
        Asgn_VV(&r[NoLevels - 1], Sub_VV(&b, Mul_QV(&A[NoLevels - 1], &x)));
        while (!RTCResult(Iter, l2Norm_V(&r[NoLevels - 1]), bNorm, MGPCGIterId)
            && Iter < MaxIter) {
            Iter++;
            /* multigrid preconditioner */
            V_SetAllCmp(&z[NoLevels - 1], 0.0);
            for (MGIter = 1; MGIter <= NoMGIter; MGIter++)
                MGStep(NoLevels, A, z, r, R, P, NoLevels - 1, Gamma,
                   SmoothProc, Nu1, Nu2, PrecondProc, Omega,
                   SolvProc, NuC, PrecondProcC, OmegaC);
            Rho = Mul_VV(&r[NoLevels - 1], &z[NoLevels - 1]);
            if (Iter == 1) {
                Asgn_VV(&p, &z[NoLevels - 1]);
            } else {
                Beta = Rho / RhoOld;
                Asgn_VV(&p, Add_VV(&z[NoLevels - 1], Mul_SV(Beta, &p)));
            }
            Asgn_VV(&q, Mul_QV(&A[NoLevels - 1], &p));
            Alpha = Rho / Mul_VV(&p, &q);
            AddAsgn_VV(&x, Mul_SV(Alpha, &p));
            SubAsgn_VV(&r[NoLevels - 1], Mul_SV(Alpha, &q));
            RhoOld = Rho;
        }
        
	/* put solution and right hand side vectors back */
        Asgn_VV(&z[NoLevels - 1], &x);
        Asgn_VV(&r[NoLevels - 1], &b);
    }
    
    V_Destr(&x);
    V_Destr(&p);
    V_Destr(&q);
    V_Destr(&b);

    return(&z[NoLevels - 1]);
}