"""
This module contains the problem definition and solver for iNSE ALE article.
It enables to replicate all results
"""
__version__ = "0.2"
__author__ = "Reidmen Arostica"
__date__ = "18/07/2020"

# Libraries 
import fenics as fn
from dolfin import __version__ as df_version
import numpy as np
import matplotlib.pyplot as plt
import time 
from common import *

# Basic info to user
print("DOLFIN VERSION: {}".format(df_version))
print("SOLVER VERSION: {}".format(__version__))
print("__author__: {}".format(__author__))


def iNSE_Monolithic_Solver(parameters, deformation, stabilization, solver_to_use='LU'):
    """ This solver computes the incompressible Navier-Stokes (iNSE) problem in the ALE formalism.
    To do so, the FEniCS framework is used and the convective term within iNSE is linealized.
    To solve the problem, a monolithic scheme is employed with finite diferences for time 
    and FE for space.
    ...

    Input Parameters
    ----------------
    parameter: dict
        dictionary containing several options for the scheme used.
    deformation: dict
        dictionary containing deformation mapping properties and options.
    stabilization: dict
        dictionary specifying stabilization used.


    Output
    ----------------
    None.
        The solver print the results to console, and returns None, saving solutions and figures.
    """
    # Starting time
    start = time.time()
    # Default parameters
    mu, rho_f = parameters['mu'], parameters['rho_f']
    Nx, Ny = parameters['Nx'], parameters['Ny']
    tau, T = parameters['tau'], parameters['T']

    def def_mesh_tuple(def_params: dict) -> tuple:
        """
        Computes a list (len 2) containing the parametrized mesh velocity.
        ...

        Input Parameters
        ----------------
        def_params: dict
            dictionary with keys 'expression' and 'output_name'.
        
        """
        if 'expression' in def_params.keys():
            if 'output_name' in def_params.keys():
                output_name = def_params['output_name']
            else:
                output_name = ('X', 'Y')
            return def_params['expression'], output_name
        else:
            raise Exception("Key 'expression' was not found in the dictionary!")
        
    def exps_at_bdry(bdry_params: dict, def_params):
        """
        Computes the boundary FEniCS expressions for this particular Dirichlet Homogeneous case.
        """
        u_inlet = fn.Constant((0.0, 0.0))
        u_outlet = fn.Constant((0.0, 0.0))
        u_bottom = fn.Constant((0.0, 0.0))
        u_top = fn.Constant((0.0, 0.0))
        return u_inlet, u_outlet, u_bottom, u_top

    # Define boundary domain classes
    class inlet(fn.SubDomain):
        def inside(self, x, on_boundary):
            return fn.near(x[0], 0) and on_boundary
    class outlet(fn.SubDomain):
        def inside(self, x, on_boundary):
            return fn.near(x[0], 6) and on_boundary
    class top(fn.SubDomain):
        def inside(self, x, on_boundary):
            return fn.near(x[1], 1) and on_boundary
    class bottom(fn.SubDomain):
        def inside(self, x, on_boundary):
            return fn.near(x[1], -1) and on_boundary

    # Define mesh object
    mesh_f = fn.RectangleMesh(fn.Point(0, -1), fn.Point(6, 1), Nx, Ny)
    #h = fn.CellDiameter(mesh_f)

    # Obtain mesh deformation tuple and output name
    def_tuple, def_name = def_mesh_tuple(deformation)

    # Define displacement expressions at previous and actual time stepss
    def_n = fn.Expression(def_tuple, t=0.0, domain=mesh_f, degree=2)
    def_t = fn.Expression(def_tuple, t=tau, domain=mesh_f, degree=2)

     # Functional Spaces (Mixed Element)
    U = fn.VectorElement("CG", mesh_f.ufl_cell(), parameters['deg_U'])
    P = fn.FiniteElement("CG", mesh_f.ufl_cell(), parameters['deg_P'])
    M = fn.FunctionSpace(mesh_f, fn.MixedElement([U, P]))

    # Functional spaces (Separately)
    U_ = fn.VectorFunctionSpace(mesh_f, "CG", parameters['deg_U'])
    P_ = fn.FunctionSpace(mesh_f, "CG", parameters['deg_P'])
    
    # Assigner between spaces
    assigner_to_M = fn.FunctionAssigner(M, [U_, P_])
    assigner_to_UP = fn.FunctionAssigner([U_, P_], M)

     # Boundary expressions in the reference domain
    u_inflow, u_outflow, u_bottom, u_top = exps_at_bdry(parameters, deformation)
    # Define and mark boundaries
    bdry_markers = fn.MeshFunction('size_t', mesh_f, mesh_f.topology().dim()-1)
    inlet().mark(bdry_markers, 1) # Inlet marked with label 1
    outlet().mark(bdry_markers, 2) # Outlet marked with label 2
    bottom().mark(bdry_markers, 3) # Bottom marked with label 3
    top().mark(bdry_markers, 4) # Top marked with label 4

    # Define measures for weak formulation
    dx_f = fn.Measure('dx', domain=mesh_f, subdomain_data=bdry_markers)
    #ds_f = fn.Measure('ds', domain=mesh_f, subdomain_data=bdry_markers)

    # Define boundary conditions with pressure condition to obtain uniqueness (case LU solver!)
    bdries = [fn.DirichletBC(M.sub(0), u_inflow, bdry_markers, 1),
              fn.DirichletBC(M.sub(0), u_outflow, bdry_markers, 2),
              fn.DirichletBC(M.sub(0), u_bottom, bdry_markers, 3),
              fn.DirichletBC(M.sub(0), u_top, bdry_markers, 4),
              fn.DirichletBC(M.sub(1), fn.Constant(0.), "near(x[0], 0) && near(x[1], 0)", 'pointwise')]

    # Trial/Test function definition
    (u, p) = fn.TrialFunctions(M)
    (v, q) = fn.TestFunctions(M)
    aux_n = fn.Function(M, name='MixedSolution')
    # Define initial condition (non-zero)
    if parameters["init_cond"] == True:
        print("Using a non-zero initial condition (2D-Gaussian) for the velocity field!")
        cpp_aux = '0.001*(1 - pow(x[1], 2))*x[0]*(6 - x[0])'
        aux_n.assign(fn.Expression((cpp_aux, '0.', '0.'), domain=mesh_f, degree=2))
        u_n, _ = fn.split(aux_n)
    else:
        u_n, _ = fn.split(aux_n)

    # Define normal for backflow stabilization term
    #N = fn.FacetNormal(mesh_f)
    # Compute its jacobians
    J_n = J_(def_n)
    J = J_(def_t)
    # Define operators wrt to the reference domain
    grad = lambda u: grad_(u, def_t)
    sym_grad = lambda u: sym_grad_(u, def_t)
    #inv_F = inv_F_(def_t)
    div = lambda u: div_(u, def_t)
    # Define domain velocity as a first order finite difference
    w = (def_t - def_n)/tau

    # All formulations contain GCL + Temam's stabilization term
    if parameters['gcl_stab'] == True:
        if parameters['jac_at_n'] == False:
            print("Using time-derivative term approximated with Jacobian at n+1...")
            F =   fn.Constant(rho_f/tau)*J*fn.inner(u - u_n, v)*dx_f\
                + fn.Constant(0.5*rho_f/tau)*(J - J_n)*fn.inner(u, v)*dx_f\
                + fn.Constant(0.5*rho_f)*div(u_n - w)*fn.inner(u, v)*dx_f
        else:
            print("Using time-derivative term approximated with Jacobian at n...")
            F =   fn.Constant(rho_f/tau)*J_n*fn.inner(u - u_n, v)*dx_f\
                + fn.Constant(0.5*rho_f/tau)*(J - J_n)*fn.inner(u, v)*dx_f\
                + fn.Constant(0.5*rho_f)*div(u_n - w)*fn.inner(u, v)*dx_f
    else:
        raise Exception("Solver implemented only for cases with 'gcl_stab': True")

    # Adding remaining term of the weak formulation 
    # The convective term is linearized!
    F +=  fn.Constant(rho_f)*J*fn.dot(fn.dot(grad(u), u_n - w), v)*dx_f\
        + fn.Constant(2*mu)*J*fn.inner(sym_grad(u), sym_grad(v))*dx_f\
        - p*div(v)*dx_f + q*div(u)*dx_f

     # Add stabilization when required
    if stabilization['type'] == None:
        pass
    else:
        raise Exception("Other stabilization not required for this monolithic test model!")

    # Obtain lhs and rhs   
    a, L = fn.lhs(F), fn.rhs(F)
    # Assemble lhs of the formulation
    A = fn.PETScMatrix()
    fn.assemble(a, tensor = A)
    # Define Krylov solver for the problem 
    if solver_to_use == 'LU':
        print("Using LU Solver...")
        solver = fn.LUSolver()
        solver.set_operator(A)
    elif solver_to_use == 'Krylov':
        krylov_method = 'gmres'#'minres', 'gmres', 'bicgstab'
        precond = 'icc'#'ilu', 'icc', 'hypre_amg', 'amg'
        print("Using Krylov Solver --> Method: {}, Precond: {}\n".format(krylov_method, precond))
        solver = fn.PETScKrylovSolver(krylov_method, precond)
        solver.set_operator(A)

        # TODO: Remove the zero-mean space for pressure fields
        # Create normalized vector that spans the pressure null space (P)
        p_ = fn.Function(P_)
        null_p_vec = fn.Vector(p_.vector())
        P_.dofmap().set(null_p_vec, 1.0)
        null_p_vec *= 1.0/null_p_vec.norm("l2")
        p_.vector()[:] = null_p_vec
        # Create zero-vector that spans M by Function assigning it
        null_func = fn.Function(M)
        u_ = fn.Function(U_)
        assigner_to_M.assign(null_func, [u_, p_])
        null_vec = fn.Vector(null_func.vector())
        # Create null space basis object and attach to PETSc matrix
        null_space = fn.VectorSpaceBasis([null_vec])
        fn.as_backend_type(A).set_nullspace(null_space)

        fn.PETScOptions.set("ksp_monitor")
        #solver.parameters['monitor_convergence'] = True
        solver.parameters['maximum_iterations'] = 300
        solver.parameters['relative_tolerance'] = 1E-2
        solver.parameters['absolute_tolerance'] = 1E-4
        
    else:
        raise Exception("Solver type not implemented, available choices: 'LU', 'Krylov'")

    # Define nicely the output file to save!
    base_name = "iNSE_ALE"
    base_name += "_{}_{}".format(def_name[0], def_name[1])
    base_name += "_P{}P{}_GCL_{}".format(parameters['deg_U'], parameters['deg_P'], parameters['gcl_stab'])

    if stabilization['type'] == None:
        filename = base_name + "_NoStab.xdmf"
    else:
        raise Exception("Other stabilizations not implemented!")
    # Create XDMF file (It saves multiple fields)
    file_output = fn.XDMFFile('tests/rectangle/'+filename)
    file_output.parameters['functions_share_mesh'] = True

    # Starts from dt, we take at 0 the initial solution
    print("Starting iteration over time...")
    saved_defs = []
    saved_vels = []
    saved_pres = []
    # Depending on time step tau, output some results to user!
    if tau == 0.1 and T <= 1: nb = 2
    else: nb = 1
    # Saving velocity and deformation fields at time t = 0.
    u_n, _ = aux_n.split(deepcopy=True)
    file_output.write(u_n, 0.0)
    energy_assembled = fn.assemble(J_n*fn.inner(u_n, u_n)*dx_f)*0.5*rho_f/tau
    print("E (Energy) of initial velocity field: {:.5f} at time {:2.3f}\n".format(energy_assembled, 0.0))
    aux_ = fn.Function(M.sub(0).collapse(), name="Deformation")
    aux_.assign(fn.project(def_n, M.sub(0).collapse()))
    file_output.write(aux_, 0.0)
    # Main iteration
    for count, t in enumerate(np.arange(tau, T+tau, tau)):
        # re-Assemble lhs vector
        fn.assemble(a, tensor=A)
        [bc.apply(A) for bc in bdries]
        # Assemble rhs vector (changes over time)
        b = fn.assemble(L)

        # if Krylov solver is used, then ortogonalize also b
        if solver_to_use == 'Krylov':
            null_space.orthogonalize(b)
        
        [bc.apply(b) for bc in bdries]

        # Solve linear system and save solution
        aux = fn.Function(M, name="MixedSolution")
        num_iters = solver.solve(aux.vector(), b)
        # Split and save velocity field
        u, p = aux.split(deepcopy=True)
        file_output.write(u, t)
        # Saving computed 
        aux_ = fn.Function(M.sub(0).collapse(), name="Deformation")
        aux_.assign(fn.project(def_t, M.sub(0).collapse()))
        file_output.write(aux_, t)
        # Print only a couple of solutions to the user!
        if count % nb  == 0:
            J_assembled = fn.assemble(J*dx_f)
            J_n_assembled = fn.assemble(J_n*dx_f)
            def_assembled = fn.assemble(fn.dot(def_t, def_t)*dx_f)
            #w_assembled = fn.assemble(fn.dot(w, w)*dx_f)
            energy_assembled = fn.assemble(J*fn.inner(u, u)*dx_f)*0.5*rho_f/tau
            energy_n_assembled = fn.assemble(J_n*fn.inner(u_n, u_n)*dx_f)*0.5*rho_f/tau
            energy_strain_assembled = fn.assemble(J*fn.inner(sym_grad(u), sym_grad(u))*dx_f)*2*mu
            if parameters['jac_at_n'] == False:
                energy_var_assembled = fn.assemble(J*fn.inner(u - u_n, u - u_n)*dx_f)*0.5*rho_f/tau
            else:
                energy_var_assembled = fn.assemble(J_n*fn.inner(u - u_n, u - u_n)*dx_f)*0.5*rho_f/tau
            delta_value = energy_assembled - energy_n_assembled + energy_strain_assembled + energy_var_assembled
            # Give info to user
            print("J   integrated over the domain: {:.3f} at time: {:2.3f}".format(J_assembled, t))
            print("J_n integrated over the domain: {:.3f} at time: {:2.3f}".format(J_n_assembled, t))
            print("Well-posedness condition 3J - J_n > 0: {} at time {:2.3f}".format(3*J_assembled - J_n_assembled > 0, t))
            print("d  (def. map) integrated over the domain: {:.3f} at time: {:2.3f}".format(def_assembled, t))
            #print("w (vel. map)integrated over the domain: {:.3f} at time: {:2.3f}".format(w_assembled, t))
            print("Strain energy E( eps(|u|^2) ): {:.5f} at time {:2.3f}".format(energy_strain_assembled, t))
            print("Residual of scheme -> 'delta' value: {:.5f} at time {:2.3f}\n".format(delta_value, t))
            # Saving results for postprocessing
            if solver_to_use == 'LU':
                saved_vels.append((u, t, delta_value , energy_strain_assembled, energy_var_assembled))
            else:
                saved_vels.append((u, t, delta_value , energy_strain_assembled, energy_var_assembled, num_iters))

        # Update velocity profile
        aux_n.assign(aux)
        u_n, _ = fn.split(aux_n)
        # Updates deformation mapping
        def_n.t = t
        def_t.t = t+tau    

    # Number of elements used and total time
    print("Number of degrees of freedom: {}".format(M.dim()))
    print("Solution Computed in {:.5} seconds".format(time.time()-start))
    return saved_defs, saved_vels, saved_pres


def iNSE_CT_Solver(parameters, deformation, stabilization, solver_to_use='LU'):
    """ This solver computes the incompressible Navier-Stokes (iNSE) problem in the ALE formalism.
    To do so, the FEniCS framework is used and the convective term within iNSE is linealized.
    To solve the problem, a Chorin-Temam scheme is employed with finite diferences for time 
    and FE for space. 
    The Chorin-Temam Scheme here is taken in the order: PPS_0 --> FVS_0 --> PPS_1 --> FVS_1...
    ...

    Input Parameters
    ----------------
    parameter: dict
        dictionary containing several options for the scheme used.
    deformation: dict
        dictionary containing deformation mapping properties and options.
    stabilization: dict
        dictionary specifying stabilization used.


    Output
    ----------------
    None.
        The solver print the results to console, and returns None, saving solutions and figures.
    """
    # Starting time
    start = time.time()
    # Default parameters
    mu, rho_f = parameters['mu'], parameters['rho_f']
    Nx, Ny = parameters['Nx'], parameters['Ny']
    tau, T = parameters['tau'], parameters['T']

    def def_mesh_tuple(def_params: dict) -> tuple:
        """
        Computes a list (len 2) containing the parametrized mesh velocity.
        ...

        Input Parameters
        ----------------
        def_params: dict
            dictionary with keys 'expression' and 'output_name'.
        
        """
        if 'expression' in def_params.keys():
            if 'output_name' in def_params.keys():
                output_name = def_params['output_name']
            else:
                output_name = ('X', 'Y')
            return def_params['expression'], output_name
        else:
            raise Exception("Key 'expression' was not found in the dictionary!")
        
    def exps_at_bdry(bdry_params: dict, def_params: dict) -> tuple:
        """
        Computes the boundary FEniCS expressions for this particular Dirichlet Homogeneous case.
        """
        u_inlet = fn.Constant((0.0, 0.0))
        u_outlet = fn.Constant((0.0, 0.0))
        u_bottom = fn.Constant((0.0, 0.0))
        u_top = fn.Constant((0.0, 0.0))
        return u_inlet, u_outlet, u_bottom, u_top

    def corrected_vel(u, ut, p) -> fn.Function:
        """
        Computes the corrected velocity field within CT update scheme.
        """
        grad_p = fn.project(grad(p), U)
        u.vector()[:] = ut.vector()[:] - (tau/rho_f)*grad_p.vector()[:]
        return u

    # Define boundary domain classes
    class inlet(fn.SubDomain):
        def inside(self, x, on_boundary):
            return fn.near(x[0], 0) and on_boundary
    class outlet(fn.SubDomain):
        def inside(self, x, on_boundary):
            return fn.near(x[0], 6) and on_boundary
    class top(fn.SubDomain):
        def inside(self, x, on_boundary):
            return fn.near(x[1], 1) and on_boundary
    class bottom(fn.SubDomain):
        def inside(self, x, on_boundary):
            return fn.near(x[1], -1) and on_boundary

    # Define mesh object
    mesh_f = fn.RectangleMesh(fn.Point(0, -1), fn.Point(6, 1), Nx, Ny)
    #h = fn.CellDiameter(mesh_f)

    # Obtain mesh deformation tuple and output name
    def_tuple, def_name = def_mesh_tuple(deformation)

    # Define displacement expressions at previous and actual time steps
    def_n = fn.Expression(def_tuple, t=0.0, domain=mesh_f, degree=2)
    def_t = fn.Expression(def_tuple, t=tau, domain=mesh_f, degree=2)

     # Functional Spaces
    U = fn.VectorFunctionSpace(mesh_f, "CG", parameters['deg_U'])
    P = fn.FunctionSpace(mesh_f, "CG", parameters['deg_P'])

     # Boundary expressions in the reference domain
    u_inflow, u_outflow, u_bottom, u_top = exps_at_bdry(parameters, deformation)
    # Define and mark boundaries
    bdry_markers = fn.MeshFunction('size_t', mesh_f, mesh_f.topology().dim()-1)
    inlet().mark(bdry_markers, 1) # Inlet marked with label 1
    outlet().mark(bdry_markers, 2) # Outlet marked with label 2
    bottom().mark(bdry_markers, 3) # Bottom marked with label 3
    top().mark(bdry_markers, 4) # Top marked with label 4

    # Define measures
    dx_f = fn.Measure('dx', domain=mesh_f, subdomain_data=bdry_markers)
    #ds_f = fn.Measure('ds', domain=mesh_f, subdomain_data=bdry_markers)

    # Define boundary conditions with pressure condition to obtain uniqueness (case LU solver!)
    bdries_u = [
        fn.DirichletBC(U, u_inflow, bdry_markers, 1),
        fn.DirichletBC(U, u_outflow, bdry_markers, 2),
        fn.DirichletBC(U, u_bottom, bdry_markers, 3),
        fn.DirichletBC(U, u_top, bdry_markers, 4)
        ]
    bdries_p = [
        fn.DirichletBC(P, fn.Constant(0.), "near(x[0], 0) && near(x[1], 0)", 'pointwise')
        ]

    # Trial/Test function definition
    ut, v = fn.TrialFunction(U), fn.TestFunction(U)
    p, q = fn.TrialFunction(P), fn.TestFunction(P)
    ut_n, p_n = fn.Function(U, name='T. Velocity'), fn.Function(P, name='Pressure')
    #u, u_n = fn.Function(U, name='C. Velocity'), fn.Function(U, name='C. Velocity')
    # Define initial condition (non-zero)
    if parameters["init_cond"] == True:
        print("Using a non-zero initial condition (2D-Gaussian) for the velocity field!")
        cpp_aux = '0.001*(1 - pow(x[1], 2))*x[0]*(6 - x[0])'
        ut_n.assign(fn.Expression((cpp_aux, '0.'), domain=mesh_f, degree=2))
    else:
        pass

    # Define normal for backflow stabilization term
    #N = fn.FacetNormal(mesh_f)
    # Compute its jacobians
    J_n = J_(def_n)
    J = J_(def_t)
    # Define operators wrt to the reference domain
    grad = lambda u: grad_(u, def_t)
    grad_n = lambda u: grad_(u, def_n)
    sym_grad = lambda u: sym_grad_(u, def_t)
    div = lambda u: div_(u, def_t)
    div_n = lambda u: div_(u, def_n)
    # Define domain velocity as a first order finite difference
    w = (def_t - def_n)/tau

    # (FVS) Step. Finding tentative velocity field
    # The formulation has GCL+Temam Stabilization term
    print("Defining forms associated to Chorin-Temam scheme...")
    if parameters['gcl_stab'] == True:
        if parameters['jac_at_n'] == False:
            F_fvs = fn.Constant(rho_f/tau)*J*fn.inner(ut - ut_n, v)*dx_f\
                + fn.Constant(0.5*rho_f/tau)*(J - J_n)*fn.inner(ut, v)*dx_f\
                + fn.Constant(0.5*rho_f)*div(ut_n - w)*fn.inner(ut, v)*dx_f
        else:
            F_fvs = fn.Constant(rho_f/tau)*J_n*fn.inner(ut - ut_n, v)*dx_f\
                + fn.Constant(0.5*rho_f/tau)*(J - J_n)*fn.inner(ut, v)*dx_f\
                + fn.Constant(0.5*rho_f)*div(ut_n - w)*fn.inner(ut, v)*dx_f
    else:
        raise Exception("Solver implemented only for cases with 'gcl_stab': True")

    # Adding remaining term of the weak formulation 
    # The convective term is linearized!
    F_fvs += fn.Constant(rho_f)*J*fn.dot(fn.dot(grad(ut), ut_n - w), v)*dx_f\
        + fn.Constant(2*mu)*J*fn.inner(sym_grad(ut), sym_grad(v))*dx_f\
        - p_n*div_n(v)*dx_f
    
     # Add stabilization when required
    if stabilization['type'] == None:
        pass
    else:
        raise Exception("Other stabilization not required for this CT test scheme!")

    # (PPS) Step. Projecting into the div-free solution space
    ut_pps = fn.Function(U) # Define tentative vel. object for pps form
    F_pps = fn.Constant(tau/rho_f)*J_n*fn.inner(grad_n(p), grad_n(q))*dx_f\
        + div_n(ut_pps)*q*dx_f

    # Obtain lhs and rhs   
    a_fvs, L_fvs = fn.lhs(F_fvs), fn.rhs(F_fvs)
    a_pps, L_pps = fn.lhs(F_pps), fn.rhs(F_pps)  
    # Assemble lhs of the formulation
    A_fvs = fn.PETScMatrix()
    A_pps = fn.PETScMatrix()
    fn.assemble(a_fvs, tensor = A_fvs)
    fn.assemble(a_pps, tensor = A_pps)

    # Define Krylov solver for the problem 
    # With 'Krylov' using 'gmres'+'ilu' the solver works smoothly
    if solver_to_use == 'LU':
        print("Using LU Solver...")
        solver_fvs = fn.LUSolver()
        solver_pps = fn.LUSolver()
        [solver.set_operator(A) for solver, A in zip([solver_fvs, solver_pps], [A_fvs, A_pps])]
    elif solver_to_use == 'Krylov':
        krylov_method = 'gmres'#'minres', 'gmres', 'bicgstab'
        precond = 'amg'#'ilu', 'icc', 'hypre_amg', 'petsc_amg'
        print("Using Krylov Solver --> Method: {}, Precond: {}\n".format(krylov_method, precond))
        solver_fvs = fn.PETScKrylovSolver(krylov_method, precond)
        solver_pps = fn.PETScKrylovSolver(krylov_method, precond)
        for solver, A in zip([solver_fvs, solver_pps], [A_fvs, A_pps]):
            solver.set_operator(A)
            solver.parameters['report'] = True
            #solver.parameters['monitor_convergence'] = True
            solver.parameters['maximum_iterations'] = 2000
            solver.parameters['relative_tolerance'] = 1E-4
            solver.parameters['absolute_tolerance'] = 1E-7
            #solver.parameters.str(True)
    else:
        raise Exception("Solver type not implemented, available choices: 'LU', 'Krylov'")
    
    # Define nicely the output file to save!
    base_name = "iNSE_ALE_CT"
    base_name += "_{}_{}".format(def_name[0], def_name[1])
    base_name += "_P{}P{}_GCL_{}".format(parameters['deg_U'], parameters['deg_P'], parameters['gcl_stab'])

    if stabilization['type'] == None:
        filename = base_name + "_NoStab.xdmf"
    else:
        raise Exception("Other stabilizations not implemented!")
    # Create XDMF file (It saves multiple fields)
    file_output = fn.XDMFFile('tests/rectangle/'+filename)
    file_output.parameters['functions_share_mesh'] = True

    # Starts from dt, we take at 0 the initial solution
    print("Starting iteration over time...")
    saved_defs = []
    saved_vels = []
    saved_pres = []
    # Depending on time step tau, save and export some solutions
    if tau == 0.1 and T <= 1: nb = 2
    else: nb = 1

    ##-----Saving deformation field at time t = 0-----##
    aux_ = fn.Function(U, name="Deformation")
    aux_.assign(fn.project(def_n, U))
    file_output.write(aux_, 0.0)
    # Saving velocity (t., div-free) and pressure fields
    # by solving an initial PPS 
    fn.assemble(a_pps, tensor=A_pps)
    [bc.apply(A_pps) for bc in bdries_p]
    # Assemble rhs (changes over time)
    b_pps = fn.assemble(L_pps)
    [bc.apply(b_pps) for bc in bdries_p]
    # Solve linear system 
    p = fn.Function(P, name='Pressure')
    num_iters_pps = solver_pps.solve(p.vector(), b_pps)
    file_output.write(p, 0.0)
    # Compute and save updated (t.) velocity
    corrected_vel(ut_n, ut_n, p)
    file_output.write(ut_n, 0.0)
    #corrected_vel(u_n, ut_n, p_n)
    #file_output.write(u_n, 0.0)
    energy_assembled = fn.assemble(J_n*fn.inner(ut_n, ut_n)*dx_f)*0.5*rho_f/tau
    print("E (Energy) of initial t. velocity: {:.5f} at time {:2.3f}\n".format(energy_assembled, 0.0))
    ## -------------------------------------------- ##
    ## ----------------Main iteration---------------##
    for count, t in enumerate(np.arange(tau, T+tau, tau)):
        # (PPS) Pressure-Projection Step
        # Update t. velocity
        ut_pps.assign(ut_n)
        # Assemble lhs 
        fn.assemble(a_pps, tensor=A_pps)
        [bc.apply(A_pps) for bc in bdries_p]
        # Assemble rhs (changes over time)
        b_pps = fn.assemble(L_pps)
        [bc.apply(b_pps) for bc in bdries_p]
        # Solve linear system 
        p = fn.Function(P, name='Pressure')
        num_iters_pps = solver_pps.solve(p.vector(), b_pps)
        # Save pressure
        file_output.write(p, t)

        # (FVS) Fluid-Viscous Step
        # Update pressure
        p_n.assign(p)
        # re-Assemble lhs vector
        fn.assemble(a_fvs, tensor=A_fvs)
        [bc.apply(A_fvs) for bc in bdries_u]
        # Assemble rhs vector (changes over time)
        b_fvs = fn.assemble(L_fvs)
        [bc.apply(b_fvs) for bc in bdries_u]
        # Solve linear system and save solution
        ut = fn.Function(U, name='T. Velocity')
        num_iters_fvs = solver_fvs.solve(ut.vector(), b_fvs)

        # (CVS) Computation Corrected Velocity (Optional)
        #corrected_vel(u, ut, p)

        # Save deformation (proj. into U)
        aux_ = fn.Function(U, name="Deformation")
        aux_.assign(fn.project(def_t, U))
        file_output.write(aux_, t)
        # Save t. velocity
        file_output.write(ut, t)
        #file_output.write(u, t)

        # Print and save solutions to the user!
        if count % nb  == 0:
            J_assembled = fn.assemble(J*dx_f)
            J_n_assembled = fn.assemble(J_n*dx_f)
            def_assembled = fn.assemble(fn.dot(def_t, def_t)*dx_f)
            #w_assembled = fn.assemble(fn.dot(w, w)*dx_f)
            energy_assembled = fn.assemble(J*fn.inner(ut, ut)*dx_f)*0.5*rho_f/tau
            energy_n_assembled = fn.assemble(J_n*fn.inner(ut_n, ut_n)*dx_f)*0.5*rho_f/tau
            energy_strain_assembled = fn.assemble(J*fn.inner(sym_grad(ut), sym_grad(ut))*dx_f)*2*mu
            pres_assembled = fn.assemble(J_n*fn.inner(grad_n(p), grad_n(p))*dx_f)*0.5*tau/rho_f
            delta_value = energy_assembled - energy_n_assembled + energy_strain_assembled + pres_assembled
            if parameters['jac_at_n'] == False:
                energy_var_assembled = fn.assemble(J*fn.inner(ut - ut_n, ut - ut_n)*dx_f)*0.5*rho_f/tau
            else:
                energy_var_assembled = fn.assemble(J_n*fn.inner(ut - ut_n, ut - ut_n)*dx_f)*0.5*rho_f/tau
            # Providing info to user
            print("J   integrated over the domain: {:.3f} at time: {:2.3f}".format(J_assembled, t))
            print("J_n integrated over the domain: {:.3f} at time: {:2.3f}".format(J_n_assembled, t))
            print("Well-posedness condition 3J - J_n > 0: {} at time {:2.3f}".format(3*J_assembled - J_n_assembled > 0, t))
            print("d  (def. map) integrated over the domain: {:.3f} at time: {:2.3f}".format(def_assembled, t))
            #print("w (vel. map)integrated over the domain: {:.3f} at time: {:2.3f}".format(w_assembled, t))
            print("Strain energy E( eps(|u|^2) ): {:.5f} at time {:2.3f}".format(energy_strain_assembled, t))
            print("Residual of scheme -> 'delta' value: {:.5f} at time {:2.3f}\n".format(delta_value, t))
            # Saving results
            if solver_to_use == 'LU':
                saved_vels.append((ut, t, delta_value, energy_strain_assembled, energy_var_assembled))
            else:
                saved_vels.append((ut, t, delta_value, energy_strain_assembled, energy_var_assembled, num_iters_fvs, num_iters_pps))

        # Update t. velocity
        ut_n.assign(ut)
        #u_n.assign(u)
        # Updates deformation mapping
        def_n.t = t
        def_t.t = t+tau    

    # Number of elements used and total time
    print("DoF for Velocity Space (U): {}".format(U.dim()))
    print("DoF for Pressure Space (P): {}".format(P.dim()))
    print("Solution Computed in {:.5} seconds".format(time.time()-start))
    return saved_defs, saved_vels, saved_pres

def testing_comparative_plots(dict_fields_mt: dict, dict_fields_ct: dict, *args) -> None:
    """
    Computes error figures between the schemes with Jacobian evaluation at times {n, n+1}, i.e.
    with explicit and implicit treatment of the time-derivative term.
    ...
    
    Input Parameters
    ----------------
    dict_fields_mt : tuple -> (dict, dict, dict)
        tuple containing dictionaries of fields (def, vel, pre) from the iNSE ALE Monolithic solver.
    
    dict_fields_ct : tupel -> (dict, dict, dict)
        tuple containing dictionaries of fields (def, vel, pre) from the iNSE ALE CT solver. 
    """
    print("\nComputing error figures for both schemes...\n")
    # Creates dictionaries for "expanding" and "contracting" functions
    # Time dictionary
    time_list = {
        'MT': {jac_name: [] for jac_name in dict_fields_mt.keys()},
        'CT': {jac_name: [] for jac_name in dict_fields_ct.keys()}
    }
    # Numerical dissipation dictionary --> int (| J^{n+1} u^{n+1} - J^{n} u^{n} |)
    dis_value_exp = {
        'MT': {jac_name: [] for jac_name in dict_fields_mt.keys()},
        'CT': {jac_name: [] for jac_name in dict_fields_ct.keys()}
    }
    dis_value_con = {
        'MT': {jac_name: [] for jac_name in dict_fields_mt.keys()},
        'CT': {jac_name: [] for jac_name in dict_fields_ct.keys()}
    }
    # Physical dissipation dictionary --> int ( 2*mu* | eps(u) |^2 )
    eps_value_exp = {
        'MT': {jac_name: [] for jac_name in dict_fields_mt.keys()},
        'CT': {jac_name: [] for jac_name in dict_fields_ct.keys()}
    }
    eps_value_con = {
        'MT': {jac_name: [] for jac_name in dict_fields_mt.keys()},
        'CT': {jac_name: [] for jac_name in dict_fields_ct.keys()}
    }
    # 'delta' values dictionary
    delta_value_exp = {
        'MT': {jac_name: [] for jac_name in dict_fields_mt.keys()},
        'CT': {jac_name: [] for jac_name in dict_fields_ct.keys()}
    }
    delta_value_con = {
        'MT': {jac_name: [] for jac_name in dict_fields_mt.keys()},
        'CT': {jac_name: [] for jac_name in dict_fields_ct.keys()}
    }
    # Normalized 'delta' values dictionary (w.r.t physical dissipation)
    delta_hat_value_exp = {
        'MT': {jac_name: [] for jac_name in dict_fields_mt.keys()},
        'CT': {jac_name: [] for jac_name in dict_fields_ct.keys()}
    }
    delta_hat_value_con = {
        'MT': {jac_name: [] for jac_name in dict_fields_mt.keys()},
        'CT': {jac_name: [] for jac_name in dict_fields_ct.keys()}
    }
    # Normalized 'delta' values dictionary (w.r.t numerical dissipation)
    delta_hat_dis_exp = {
        'MT': {jac_name: [] for jac_name in dict_fields_mt.keys()},
        'CT': {jac_name: [] for jac_name in dict_fields_ct.keys()}
    }
    delta_hat_dis_con = {
        'MT': {jac_name: [] for jac_name in dict_fields_mt.keys()},
        'CT': {jac_name: [] for jac_name in dict_fields_ct.keys()}
    }
    # Iterate over the different cases in MT (Monolithic) and CT (Chorin-Temam) schemes
    for scheme in ['MT', 'CT']: # 'MT', 'CT'
        if scheme == 'MT':
            dict_fields = dict_fields_mt
        else:
            dict_fields = dict_fields_ct
        # Iterate over different cases 'j_implicit', 'j_explicit'
        for jac_name in dict_fields.keys():
            dict_vel = dict_fields[jac_name][1]
            for i in range(len(dict_vel['High_Osc_exp'])):
                # Saving time steps
                time_list[scheme][jac_name].append(dict_vel['High_Osc_exp'][i][1])
                # Add 'delta' values
                delta_value_exp[scheme][jac_name].append(dict_vel['High_Osc_exp'][i][2])
                delta_value_con[scheme][jac_name].append(dict_vel['High_Osc_con'][i][2])
                # Add physical dissipation
                eps_value_exp[scheme][jac_name].append(dict_vel['High_Osc_exp'][i][3])
                eps_value_con[scheme][jac_name].append(dict_vel['High_Osc_con'][i][3])
                # Add numerical dissipation
                dis_value_exp[scheme][jac_name].append(dict_vel['High_Osc_exp'][i][4])
                dis_value_con[scheme][jac_name].append(dict_vel['High_Osc_con'][i][4])
                # Compute normalized 'delta' values w.r.t the physical dissipation
                delta_hat_value_exp[scheme][jac_name].append(dict_vel['High_Osc_exp'][i][2]/dict_vel['High_Osc_exp'][i][3])
                delta_hat_value_con[scheme][jac_name].append(dict_vel['High_Osc_con'][i][2]/dict_vel['High_Osc_con'][i][3])
                # Compute normalized 'delta' values w.r.t the numerical dissipation
                delta_hat_dis_exp[scheme][jac_name].append(dict_vel['High_Osc_exp'][i][2]/dict_vel['High_Osc_exp'][i][4])
                delta_hat_dis_con[scheme][jac_name].append(dict_vel['High_Osc_con'][i][2]/dict_vel['High_Osc_con'][i][4])

    # Auxiliary indexes for plotting
    # TODO: Test this line and automatize the "100"!
    init = 0
    out = 101 #len(time_list['CT']['j_implicit']) # at tau = 0.01, T = 2 --> 200 time steps

    # ---------------------- #
    # DELTA VALUE CASE
    # ---------------------- #
    # CASE CT
    plt.subplots(figsize=(10, 4))
    # Top 
    lf_ax = plt.subplot(1, 2, 2)
    lf_j_exp, = lf_ax.plot(time_list['CT']['j_explicit'][init:out], delta_value_exp['CT']['j_explicit'][init:out], color='blue', ls='--')
    lf_j_imp, = lf_ax.plot(time_list['CT']['j_implicit'][init:out], delta_value_exp['CT']['j_implicit'][init:out], color='blue')
    lf_ax.set_xlabel(r"Simulation time [t]")
    lf_ax.set_ylabel(r"$\delta_{CT}$ def. I", color='blue')
    lf_ax.set_title(r"Residual Energy $\delta_{CT}$")
    lf_ax.tick_params(axis='y', labelcolor='blue')
    lf_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    rh_ax = lf_ax.twinx()
    rh_j_exp, = rh_ax.plot(time_list['CT']['j_explicit'][init:out], delta_value_con['CT']['j_explicit'][init:out], color='red', ls='--')
    rh_j_imp, = rh_ax.plot(time_list['CT']['j_implicit'][init:out], delta_value_con['CT']['j_implicit'][init:out], color='red')
    rh_ax.set_ylabel(r"$\delta_{CT}$ def. II", color='red')
    rh_ax.tick_params(axis='y', labelcolor='red')
    rh_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    plt.legend((lf_j_exp, lf_j_imp, rh_j_exp, rh_j_imp), \
        (r'CT $\star\star = n$', r'CT $\star\star = n+1$', r'CT $\star\star = n$', r'CT $\star\star = n+1$'), loc='center right')
    plt.grid(True)
    # CASE M
    # Bottom
    lf_ax = plt.subplot(1, 2, 1)
    lf_j_exp, = lf_ax.plot(time_list['MT']['j_explicit'][init:out], delta_value_exp['MT']['j_explicit'][init:out], color='blue', ls='--')
    lf_j_imp, = lf_ax.plot(time_list['MT']['j_implicit'][init:out], delta_value_exp['MT']['j_implicit'][init:out], color='blue')
    lf_ax.set_xlabel(r"Simulation time [t]")
    lf_ax.set_ylabel(r"$\delta_{M}$ def. I", color='blue')
    lf_ax.set_title(r"Residual Energy $\delta_{M}$")
    lf_ax.tick_params(axis='y', labelcolor='blue')
    lf_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    rh_ax = lf_ax.twinx()
    rh_j_exp, = rh_ax.plot(time_list['MT']['j_explicit'][init:out], delta_value_con['MT']['j_explicit'][init:out], color='red', ls='--')
    rh_j_imp, = rh_ax.plot(time_list['MT']['j_implicit'][init:out], delta_value_con['MT']['j_implicit'][init:out], color='red')
    rh_ax.set_ylabel(r"$\delta_{M}$ def. II", color='red')
    rh_ax.tick_params(axis='y', labelcolor='red')
    rh_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    plt.legend((lf_j_exp, lf_j_imp, rh_j_exp, rh_j_imp), \
        (r'M $\star\star = n$', r'M $\star\star = n+1$', r'M $\star\star = n$', r'M $\star\star = n+1$'), loc='center right')
    plt.grid(True)
    plt.subplots_adjust(hspace=0.8)
    plt.tight_layout()
    plt.savefig('Comparison_Delta_Value_GCL_{}_Solver_{}.png'.format(args[0], args[1]), dpi=300)
    plt.close()

    # ---------------------- #
    # NORMALIZED DELTA CASE (w.r.t physical dissipation)
    # ---------------------- #
    plt.subplots(figsize=(10, 4))
    # CASE CT
    # Top
    lf_ax = plt.subplot(1, 2, 2)
    lf_j_exp, = lf_ax.plot(time_list['CT']['j_explicit'][init:out], delta_hat_value_exp['CT']['j_explicit'][init:out], color='blue', ls='--')
    lf_j_imp, = lf_ax.plot(time_list['CT']['j_implicit'][init:out], delta_hat_value_exp['CT']['j_implicit'][init:out], color='blue')
    lf_ax.set_xlabel(r"Simulation time [t]")
    lf_ax.set_ylabel(r"$\hat{\delta}_{CT}$ def. I", color='blue')
    lf_ax.set_title(r"Normalized value $\hat{\delta}_{CT}$")
    lf_ax.tick_params(axis='y', labelcolor='blue')
    lf_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    rh_ax = lf_ax.twinx()
    rh_j_exp, = rh_ax.plot(time_list['CT']['j_explicit'][init:out], delta_hat_value_con['CT']['j_explicit'][init:out], color='red', ls='--')
    rh_j_imp, = rh_ax.plot(time_list['CT']['j_implicit'][init:out], delta_hat_value_con['CT']['j_implicit'][init:out], color='red')
    rh_ax.set_ylabel(r"$\hat{\delta}_{CT}$ def. II", color='red')
    rh_ax.tick_params(axis='y', labelcolor='red')
    rh_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    plt.legend((lf_j_exp, lf_j_imp, rh_j_exp, rh_j_imp), \
        (r'CT $\star\star = n$', r'CT $\star\star = n+1$', r'CT $\star\star = n$', r'CT $\star\star = n+1$'), loc='lower right')
    plt.grid(True)
    # CASE M
    # Bottom
    lf_ax = plt.subplot(1, 2, 1)
    lf_j_exp, = lf_ax.plot(time_list['MT']['j_explicit'][init:out], delta_hat_value_exp['MT']['j_explicit'][init:out], color='blue', ls='--')
    lf_j_imp, = lf_ax.plot(time_list['MT']['j_implicit'][init:out], delta_hat_value_exp['MT']['j_implicit'][init:out], color='blue')
    lf_ax.set_xlabel(r"Simulation time [t]")
    lf_ax.set_ylabel(r"$\hat{\delta}_{M}$ def. I", color='blue')
    lf_ax.set_title(r"Normalized value $\hat{\delta}_{M}$")
    lf_ax.tick_params(axis='y', labelcolor='blue')
    lf_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    rh_ax = lf_ax.twinx()
    rh_j_exp, = rh_ax.plot(time_list['MT']['j_explicit'][init:out], delta_hat_value_con['MT']['j_explicit'][init:out], color='red', ls='--')
    rh_j_imp, = rh_ax.plot(time_list['MT']['j_implicit'][init:out], delta_hat_value_con['MT']['j_implicit'][init:out], color='red')
    rh_ax.set_ylabel(r"$\hat{\delta}_{M}$ def. II", color='red')
    rh_ax.tick_params(axis='y', labelcolor='red')
    rh_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    plt.legend((lf_j_exp, lf_j_imp, rh_j_exp, rh_j_imp), \
        (r'M $\star\star = n$', r'M $\star\star = n+1$', r'M $\star\star = n$', r'M $\star\star = n+1$'), loc='upper right')
    plt.grid(True)
    plt.subplots_adjust(hspace=0.8)
    plt.tight_layout()
    plt.savefig('Comparison_Delta_Hat_Value_GCL_{}_solver_{}.png'.format(args[0], args[1]), dpi=300)
    plt.close()

    # ---------------------- #
    # NORMALIZED DELTA CASE (w.r.t numerical dissipation)
    # ---------------------- #
    plt.subplots(figsize=(10, 4))
    # CASE CT
    # Top
    lf_ax = plt.subplot(1, 2, 2)
    lf_j_exp, = lf_ax.plot(time_list['CT']['j_explicit'][init:out], delta_hat_dis_exp['CT']['j_explicit'][init:out], color='blue', ls='--')
    lf_j_imp, = lf_ax.plot(time_list['CT']['j_implicit'][init:out], delta_hat_dis_exp['CT']['j_implicit'][init:out], color='blue')
    lf_ax.set_xlabel(r"Simulation time [t]")
    lf_ax.set_ylabel(r"$\hat{\delta}_{CT}$ dis. def. I", color='blue')
    lf_ax.set_title(r"Normalized value $\hat{\delta}_{CT}$ (dis)")
    lf_ax.tick_params(axis='y', labelcolor='blue')
    lf_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    rh_ax = lf_ax.twinx()
    rh_j_exp, = rh_ax.plot(time_list['CT']['j_explicit'][init:out], delta_hat_dis_con['CT']['j_explicit'][init:out], color='red', ls='--')
    rh_j_imp, = rh_ax.plot(time_list['CT']['j_implicit'][init:out], delta_hat_dis_con['CT']['j_implicit'][init:out], color='red')
    rh_ax.set_ylabel(r"$\hat{\delta}_{CT}$ def. II", color='red')
    rh_ax.tick_params(axis='y', labelcolor='red')
    rh_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    plt.legend((lf_j_exp, lf_j_imp, rh_j_exp, rh_j_imp), \
        (r'CT $\star\star = n$', r'CT $\star\star = n+1$', r'CT $\star\star = n$', r'CT $\star\star = n+1$'), loc='lower right')
    plt.grid(True)
    # CASE M
    # Bottom
    lf_ax = plt.subplot(1, 2, 1)
    lf_j_exp, = lf_ax.plot(time_list['MT']['j_explicit'][init:out], delta_hat_dis_exp['MT']['j_explicit'][init:out], color='blue', ls='--')
    lf_j_imp, = lf_ax.plot(time_list['MT']['j_implicit'][init:out], delta_hat_dis_exp['MT']['j_implicit'][init:out], color='blue')
    lf_ax.set_xlabel(r"Simulation time [t]")
    lf_ax.set_ylabel(r"$\hat{\delta}_{M}$ dis. def. I", color='blue')
    lf_ax.set_title(r"Normalized value $\hat{\delta}_{M}$ (dis)")
    lf_ax.tick_params(axis='y', labelcolor='blue')
    lf_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    rh_ax = lf_ax.twinx()
    rh_j_exp, = rh_ax.plot(time_list['MT']['j_explicit'][init:out], delta_hat_dis_con['MT']['j_explicit'][init:out], color='red', ls='--')
    rh_j_imp, = rh_ax.plot(time_list['MT']['j_implicit'][init:out], delta_hat_dis_con['MT']['j_implicit'][init:out], color='red')
    rh_ax.set_ylabel(r"$\hat{\delta}_{M}$ def. II", color='red')
    rh_ax.tick_params(axis='y', labelcolor='red')
    rh_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    plt.legend((lf_j_exp, lf_j_imp, rh_j_exp, rh_j_imp), \
        (r'M $\star\star = n$', r'M $\star\star = n+1$', r'M $\star\star = n$', r'M $\star\star = n+1$'), loc='lower right')
    plt.grid(True)
    plt.subplots_adjust(hspace=0.8)
    plt.tight_layout()
    plt.savefig('Comparison_Delta_Hat_Dis_Value_GCL_{}_solver_{}.png'.format(args[0], args[1]), dpi=300)
    plt.close()

    # ---------------------- #
    # PHYSICAL DISSIPATION PLOT
    # ---------------------- #
    plt.subplots(figsize=(10, 4))
    # CASE CT
    # Top
    lf_ax = plt.subplot(1, 2, 2)
    lf_j_exp, = lf_ax.plot(time_list['CT']['j_explicit'][init:out], eps_value_exp['CT']['j_explicit'][init:out], color='blue', ls='--')
    lf_j_imp, = lf_ax.plot(time_list['CT']['j_implicit'][init:out], eps_value_exp['CT']['j_implicit'][init:out], color='blue')
    lf_ax.set_xlabel(r"Simulation time [t]")
    lf_ax.set_ylabel(r"On def. I", color='blue')
    lf_ax.set_title(r"Physical Dissipation (CT)")
    lf_ax.tick_params(axis='y', labelcolor='blue')
    lf_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    rh_ax = lf_ax.twinx()
    rh_j_exp, = rh_ax.plot(time_list['CT']['j_explicit'][init:out], eps_value_con['CT']['j_explicit'][init:out], color='red', ls='--')
    rh_j_imp, = rh_ax.plot(time_list['CT']['j_implicit'][init:out], eps_value_con['CT']['j_implicit'][init:out], color='red')
    rh_ax.set_ylabel(r"On def. II", color='red')
    rh_ax.tick_params(axis='y', labelcolor='red')
    rh_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    plt.legend((lf_j_exp, lf_j_imp, rh_j_exp, rh_j_imp), \
        (r'CT $\star\star = n$', r'CT $\star\star = n+1$', r'CT $\star\star = n$', r'CT $\star\star = n+1$'), loc='lower right')
    plt.grid(True)
    # CASE M
    # Bottom
    lf_ax = plt.subplot(1, 2, 1)
    lf_j_exp, = lf_ax.plot(time_list['MT']['j_explicit'][init:out], eps_value_exp['MT']['j_explicit'][init:out], color='blue', ls='--')
    lf_j_imp, = lf_ax.plot(time_list['MT']['j_implicit'][init:out], eps_value_exp['MT']['j_implicit'][init:out], color='blue')
    lf_ax.set_xlabel(r"Simulation time [t]")
    lf_ax.set_ylabel(r"On dis. def. I", color='blue')
    lf_ax.set_title(r"Physical Dissipation (M)")
    lf_ax.tick_params(axis='y', labelcolor='blue')
    lf_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    rh_ax = lf_ax.twinx()
    rh_j_exp, = rh_ax.plot(time_list['MT']['j_explicit'][init:out], eps_value_con['MT']['j_explicit'][init:out], color='red', ls='--')
    rh_j_imp, = rh_ax.plot(time_list['MT']['j_implicit'][init:out], eps_value_con['MT']['j_implicit'][init:out], color='red')
    rh_ax.set_ylabel(r"On def. II", color='red')
    rh_ax.tick_params(axis='y', labelcolor='red')
    rh_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    plt.legend((lf_j_exp, lf_j_imp, rh_j_exp, rh_j_imp), \
        (r'M $\star\star = n$', r'M $\star\star = n+1$', r'M $\star\star = n$', r'M $\star\star = n+1$'), loc='lower right')
    plt.grid(True)
    plt.subplots_adjust(hspace=0.8)
    plt.tight_layout()
    plt.savefig('Comparison_Eps_Value_GCL_{}_solver_{}.png'.format(args[0], args[1]), dpi=300)
    plt.close()

    # ---------------------- #
    # NUMERICAL DISSIPATION PLOT
    # ---------------------- #
    plt.subplots(figsize=(10, 4))
    # CASE CT
    # Top
    lf_ax = plt.subplot(1, 2, 2)
    lf_j_exp, = lf_ax.plot(time_list['CT']['j_explicit'][init:out], dis_value_exp['CT']['j_explicit'][init:out], color='blue', ls='--')
    lf_j_imp, = lf_ax.plot(time_list['CT']['j_implicit'][init:out], dis_value_exp['CT']['j_implicit'][init:out], color='blue')
    lf_ax.set_xlabel(r"Simulation time [t]")
    lf_ax.set_ylabel(r"On def. I", color='blue')
    lf_ax.set_title(r"Numerical Dissipation (CT)")
    lf_ax.tick_params(axis='y', labelcolor='blue')
    lf_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    rh_ax = lf_ax.twinx()
    rh_j_exp, = rh_ax.plot(time_list['CT']['j_explicit'][init:out], dis_value_con['CT']['j_explicit'][init:out], color='red', ls='--')
    rh_j_imp, = rh_ax.plot(time_list['CT']['j_implicit'][init:out], dis_value_con['CT']['j_implicit'][init:out], color='red')
    rh_ax.set_ylabel(r"On def. II", color='red')
    rh_ax.tick_params(axis='y', labelcolor='red')
    rh_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    plt.legend((lf_j_exp, lf_j_imp, rh_j_exp, rh_j_imp), \
        (r'CT $\star\star = n$', r'CT $\star\star = n+1$', r'CT $\star\star = n$', r'CT $\star\star = n+1$'), loc='lower right')
    plt.grid(True)
    # CASE M
    # Bottom
    lf_ax = plt.subplot(1, 2, 1)
    lf_j_exp, = lf_ax.plot(time_list['MT']['j_explicit'][init:out], dis_value_exp['MT']['j_explicit'][init:out], color='blue', ls='--')
    lf_j_imp, = lf_ax.plot(time_list['MT']['j_implicit'][init:out], dis_value_exp['MT']['j_implicit'][init:out], color='blue')
    lf_ax.set_xlabel(r"Simulation time [t]")
    lf_ax.set_ylabel(r"On def. I", color='blue')
    lf_ax.set_title(r"Numerical Dissipation (M)")
    lf_ax.tick_params(axis='y', labelcolor='blue')
    lf_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    rh_ax = lf_ax.twinx()
    rh_j_exp, = rh_ax.plot(time_list['MT']['j_explicit'][init:out], dis_value_con['MT']['j_explicit'][init:out], color='red', ls='--')
    rh_j_imp, = rh_ax.plot(time_list['MT']['j_implicit'][init:out], dis_value_con['MT']['j_implicit'][init:out], color='red')
    rh_ax.set_ylabel(r"On def. II", color='red')
    rh_ax.tick_params(axis='y', labelcolor='red')
    rh_ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    plt.legend((lf_j_exp, lf_j_imp, rh_j_exp, rh_j_imp), \
        (r'M $\star\star = n$', r'M $\star\star = n+1$', r'M $\star\star = n$', r'M $\star\star = n+1$'), loc='lower right')
    plt.grid(True)
    plt.subplots_adjust(hspace=0.8)
    plt.tight_layout()
    plt.savefig('Comparison_Num_Dis_Value_GCL_{}_solver_{}.png'.format(args[0], args[1]), dpi=300)
    plt.close()
    print("\nAll computations done and figures saved...\n")
    print("\n------------------------------------------\n")
    return None