unsigned long CSysSolve::Solve(CSysMatrix & Jacobian, CSysVector & LinSysRes, CSysVector & LinSysSol, CGeometry *geometry, CConfig *config) {
  
  su2double SolverTol = config->GetLinear_Solver_Error(), Residual;
  unsigned long MaxIter = config->GetLinear_Solver_Iter();
  unsigned long IterLinSol = 0;
  CMatrixVectorProduct *mat_vec;

  bool TapeActive = NO;

  if (config->GetDiscrete_Adjoint()){
#ifdef CODI_REVERSE_TYPE

   /*--- Check whether the tape is active, i.e. if it is recording and store the status ---*/

    TapeActive = AD::globalTape.isActive();


    /*--- Stop the recording for the linear solver ---*/

    AD::StopRecording();
#endif
  }

  /*--- Solve the linear system using a Krylov subspace method ---*/
  
  if (config->GetKind_Linear_Solver() == BCGSTAB ||
      config->GetKind_Linear_Solver() == FGMRES ||
      config->GetKind_Linear_Solver() == RESTARTED_FGMRES) {
    
    mat_vec = new CSysMatrixVectorProduct(Jacobian, geometry, config);
    CPreconditioner* precond = NULL;
    
    switch (config->GetKind_Linear_Solver_Prec()) {
      case JACOBI:
        Jacobian.BuildJacobiPreconditioner();
        precond = new CJacobiPreconditioner(Jacobian, geometry, config);
        break;
      case ILU:
        Jacobian.BuildILUPreconditioner();
        precond = new CILUPreconditioner(Jacobian, geometry, config);
        break;
      case LU_SGS:
        precond = new CLU_SGSPreconditioner(Jacobian, geometry, config);
        break;
      case LINELET:
        Jacobian.BuildJacobiPreconditioner();
        precond = new CLineletPreconditioner(Jacobian, geometry, config);
        break;
      default:
        Jacobian.BuildJacobiPreconditioner();
        precond = new CJacobiPreconditioner(Jacobian, geometry, config);
        break;
    }
    
    switch (config->GetKind_Linear_Solver()) {
      case BCGSTAB:
        IterLinSol = BCGSTAB_LinSolver(LinSysRes, LinSysSol, *mat_vec, *precond, SolverTol, MaxIter, &Residual, false);
        break;
      case FGMRES:
        IterLinSol = FGMRES_LinSolver(LinSysRes, LinSysSol, *mat_vec, *precond, SolverTol, MaxIter, &Residual, false);
        break;
      case RESTARTED_FGMRES:
        IterLinSol = 0;
        while (IterLinSol < config->GetLinear_Solver_Iter()) {
          if (IterLinSol + config->GetLinear_Solver_Restart_Frequency() > config->GetLinear_Solver_Iter())
            MaxIter = config->GetLinear_Solver_Iter() - IterLinSol;
          IterLinSol += FGMRES_LinSolver(LinSysRes, LinSysSol, *mat_vec, *precond, SolverTol, MaxIter, &Residual, false);
          if (LinSysRes.norm() < SolverTol) break;
          SolverTol = SolverTol*(1.0/LinSysRes.norm());
        }
        break;
    }
    
    /*--- Dealocate memory of the Krylov subspace method ---*/
    
    delete mat_vec;
    delete precond;
    
  }
  
  /*--- Smooth the linear system. ---*/
  
  else {
    switch (config->GetKind_Linear_Solver()) {
      case SMOOTHER_LUSGS:
        mat_vec = new CSysMatrixVectorProduct(Jacobian, geometry, config);
        IterLinSol = Jacobian.LU_SGS_Smoother(LinSysRes, LinSysSol, *mat_vec, SolverTol, MaxIter, &Residual, false, geometry, config);
        delete mat_vec;
        break;
      case SMOOTHER_JACOBI:
        mat_vec = new CSysMatrixVectorProduct(Jacobian, geometry, config);
        Jacobian.BuildJacobiPreconditioner();
        IterLinSol = Jacobian.Jacobi_Smoother(LinSysRes, LinSysSol, *mat_vec, SolverTol, MaxIter, &Residual, false, geometry, config);
        delete mat_vec;
        break;
      case SMOOTHER_ILU:
        mat_vec = new CSysMatrixVectorProduct(Jacobian, geometry, config);
        Jacobian.BuildILUPreconditioner();
        IterLinSol = Jacobian.ILU0_Smoother(LinSysRes, LinSysSol, *mat_vec, SolverTol, MaxIter, &Residual, false, geometry, config);
        delete mat_vec;
        break;
      case SMOOTHER_LINELET:
        Jacobian.BuildJacobiPreconditioner();
        Jacobian.ComputeLineletPreconditioner(LinSysRes, LinSysSol, geometry, config);
        IterLinSol = 1;
        break;
    }
  }


  if(TapeActive){

    /*--- Prepare the externally differentiated linear solver ---*/

    SetExternalSolve(Jacobian, LinSysRes, LinSysSol, geometry, config);

    /*--- Start recording if it was stopped for the linear solver ---*/

    AD::StartRecording();
  }

  return IterLinSol;
  
}
unsigned long CSysSolve::Solve(CSysMatrix **Jacobian, CSysVector **LinSysRes, CSysVector **LinSysSol, CGeometry **geometry,
                               CConfig *config, unsigned short iMesh) {
  
  double SolverTol = config->GetLinear_Solver_Error();
  unsigned long MaxIter = config->GetLinear_Solver_Iter();
  unsigned long IterLinSol = 0;

  /*--- Solve the linear system using a Krylov subspace method ---*/
  
  if (config->GetKind_Linear_Solver() == BCGSTAB || config->GetKind_Linear_Solver() == FGMRES
      || config->GetKind_Linear_Solver() == RFGMRES) {
    
    CMatrixVectorProduct* mat_vec = new CSysMatrixVectorProduct(*Jacobian[iMesh], geometry[iMesh], config);
    
    CPreconditioner* precond = NULL;
    switch (config->GetKind_Linear_Solver_Prec()) {
      case JACOBI:
        Jacobian[iMesh]->BuildJacobiPreconditioner();
        precond = new CJacobiPreconditioner(*Jacobian[iMesh], geometry[iMesh], config);
        break;
      case ILU:
        Jacobian[iMesh]->BuildILUPreconditioner();
        precond = new CILUPreconditioner(*Jacobian[iMesh], geometry[iMesh], config);
        break;
      case LU_SGS:
        precond = new CLU_SGSPreconditioner(*Jacobian[iMesh], geometry[iMesh], config);
        break;
      case LINELET:
        Jacobian[iMesh]->BuildJacobiPreconditioner();
        precond = new CLineletPreconditioner(*Jacobian[iMesh], geometry[iMesh], config);
        break;
    }
    
    switch (config->GetKind_Linear_Solver()) {
      case BCGSTAB:
        IterLinSol = BCGSTAB_LinSolver(*LinSysRes[iMesh], *LinSysSol[iMesh], *mat_vec, *precond, SolverTol, MaxIter, false);
        break;
      case FGMRES:
        IterLinSol = FGMRES_LinSolver(*LinSysRes[iMesh], *LinSysSol[iMesh], *mat_vec, *precond, SolverTol, MaxIter, false);
        break;
      case RFGMRES:
        IterLinSol = 0;
        while (IterLinSol < config->GetLinear_Solver_Iter()) {
          if (IterLinSol + config->GetLinear_Solver_Restart_Frequency() > config->GetLinear_Solver_Iter())
            MaxIter = config->GetLinear_Solver_Iter() - IterLinSol;
          IterLinSol += FGMRES_LinSolver(*LinSysRes[iMesh], *LinSysSol[iMesh], *mat_vec, *precond, SolverTol, MaxIter, false);
          if (LinSysRes[iMesh]->norm() < SolverTol) break;
          SolverTol = SolverTol*(1.0/LinSysRes[iMesh]->norm());
        }
        break;
    }
    
    /*--- Dealocate memory of the Krylov subspace method ---*/
    
    delete mat_vec;
    delete precond;
    
  }
  
  /*--- Solve the linear system using a linear multigrid ---*/

  else if (config->GetKind_Linear_Solver() == MULTIGRID) {
    
    CMatrixVectorProduct* mat_vec = new CSysMatrixVectorProduct(*Jacobian[iMesh], geometry[iMesh], config);

    MultiGrid_LinSolver(Jacobian, LinSysRes, LinSysSol, *mat_vec, geometry, config, iMesh, config->GetMGCycle(), SolverTol, MaxIter, true);
    
    delete mat_vec;
    
  }

  /*--- Smooth the linear system. ---*/
  
  else if (config->GetKind_Linear_Solver() == SMOOTHER_LUSGS || config->GetKind_Linear_Solver() == SMOOTHER_JACOBI
      || config->GetKind_Linear_Solver() == SMOOTHER_ILU || config->GetKind_Linear_Solver() == SMOOTHER_LINELET) {
    switch (config->GetKind_Linear_Solver()) {
      case SMOOTHER_LUSGS:
        Jacobian[iMesh]->ComputeLU_SGSPreconditioner(*LinSysRes[iMesh], *LinSysSol[iMesh], geometry[iMesh], config);
        break;
      case SMOOTHER_JACOBI:
        Jacobian[iMesh]->BuildJacobiPreconditioner();
        Jacobian[iMesh]->ComputeJacobiPreconditioner(*LinSysRes[iMesh], *LinSysSol[iMesh], geometry[iMesh], config);
        break;
      case SMOOTHER_ILU:
        Jacobian[iMesh]->BuildILUPreconditioner();
        Jacobian[iMesh]->ComputeILUPreconditioner(*LinSysRes[iMesh], *LinSysSol[iMesh], geometry[iMesh], config);
        break;
      case SMOOTHER_LINELET:
        Jacobian[iMesh]->BuildJacobiPreconditioner();
        Jacobian[iMesh]->ComputeLineletPreconditioner(*LinSysRes[iMesh], *LinSysSol[iMesh], geometry[iMesh], config);
        break;
        IterLinSol = 1;
    }
  }
  
  return IterLinSol;
  
}