def main():
    """
    A simple test program to setup a full step hierarchy
    """

    # initialize level parameters
    level_params = dict()
    level_params['restol'] = 1E-10
    level_params['dt'] = 0.1

    # initialize sweeper parameters
    sweeper_params = dict()
    sweeper_params['collocation_class'] = CollGaussRadau_Right
    sweeper_params['num_nodes'] = [5, 3]

    # initialize problem parameters
    problem_params = dict()
    problem_params['nu'] = 0.1  # diffusion coefficient
    problem_params['freq'] = 4  # frequency for the test value
    problem_params['nvars'] = [31, 15, 7
                               ]  # number of degrees of freedom for each level

    # initialize step parameters
    step_params = dict()
    step_params['maxiter'] = 20

    # initialize space transfer parameters
    space_transfer_params = dict()
    space_transfer_params['rorder'] = 2
    space_transfer_params['iorder'] = 2

    # fill description dictionary for easy step instantiation
    description = dict()
    description['problem_class'] = heat1d  # pass problem class
    description['problem_params'] = problem_params  # pass problem parameters
    description['sweeper_class'] = generic_LU  # pass sweeper (see part B)
    description['sweeper_params'] = sweeper_params  # pass sweeper parameters
    description['level_params'] = level_params  # pass level parameters
    description['step_params'] = step_params  # pass step parameters
    description[
        'space_transfer_class'] = mesh_to_mesh  # pass spatial transfer class
    description[
        'space_transfer_params'] = space_transfer_params  # pass paramters for spatial transfer

    # now the description contains more or less everything we need to create a step with multiple levels
    S = step(description=description)

    # print out and check
    f = open('step_4_B_out.txt', 'w')
    for l in range(len(S.levels)):
        L = S.levels[l]
        out = 'Level %2i: nvars = %4i -- nnodes = %2i' % (
            l, L.prob.params.nvars, L.sweep.coll.num_nodes)
        f.write(out + '\n')
        print(out)
        assert L.prob.params.nvars == problem_params['nvars'][min(l, len(problem_params['nvars']) - 1)], \
            "ERROR: number of DOFs is not correct on this level, got %s" % L.prob.params.nvars
        assert L.sweep.coll.num_nodes == sweeper_params['num_nodes'][min(l, len(sweeper_params['num_nodes']) - 1)], \
            "ERROR: number of nodes is not correct on this level, got %s" % L.sweep.coll.num_nodes
    f.close()
Ejemplo n.º 2
0
def main():
    """
    A simple test program to setup a full step instance
    """

    # initialize level parameters
    level_params = dict()
    level_params['restol'] = 1E-10
    level_params['dt'] = 0.1

    # initialize sweeper parameters
    sweeper_params = dict()
    sweeper_params['collocation_class'] = CollGaussRadau_Right
    sweeper_params['num_nodes'] = 3

    # initialize problem parameters
    problem_params = dict()
    problem_params['nu'] = 0.1  # diffusion coefficient
    problem_params['freq'] = 4  # frequency for the test value
    problem_params['nvars'] = 1023  # number of degrees of freedom

    # initialize step parameters
    step_params = dict()
    step_params['maxiter'] = 20

    # fill description dictionary for easy step instantiation
    description = dict()
    description['problem_class'] = heat1d  # pass problem class
    description['problem_params'] = problem_params  # pass problem parameters
    description['dtype_u'] = mesh  # pass data type for u
    description['dtype_f'] = mesh  # pass data type for f
    description['sweeper_class'] = generic_LU  # pass sweeper (see part B)
    description['sweeper_params'] = sweeper_params  # pass sweeper parameters
    description['level_params'] = level_params  # pass level parameters
    description['step_params'] = step_params  # pass step parameters

    # now the description contains more or less everything we need to create a step
    S = step(description=description)

    # we only have a single level, make a shortcut
    L = S.levels[0]

    # one of the integral parts of each level is the problem class, make a shortcut
    P = L.prob

    # now we can do e.g. what we did before with the problem
    err = run_accuracy_check(P)

    f = open('step_2_A_out.txt', 'w')
    out = 'Error of the spatial accuracy test: %8.6e' % err
    f.write(out)
    print(out)
    f.close()

    assert err <= 2E-04, "ERROR: the spatial accuracy is higher than expected, got %s" % err
Ejemplo n.º 3
0
    def __init__(self, controller_params, description, comm):
        """
       Initialization routine for PFASST controller

       Args:
           controller_params: parameter set for the controller and the step class
           description: all the parameters to set up the rest (levels, problems, transfer, ...)
           comm: MPI communicator
       """

        # call parent's initialization routine
        super(controller_MPI, self).__init__(controller_params)

        # create single step per processor
        self.S = step(description)

        # pass communicator for future use
        self.comm = comm

        num_procs = self.comm.Get_size()
        rank = self.comm.Get_rank()

        # insert data on time communicator to the steps (helpful here and there)
        self.S.status.time_size = num_procs

        if self.params.dump_setup and rank == 0:
            self.dump_setup(step=self.S,
                            controller_params=controller_params,
                            description=description)

        num_levels = len(self.S.levels)

        # add request handler for status send
        self.req_status = None
        # add request handle container for isend
        self.req_send = [None] * num_levels
        self.req_ibcast = None
        self.req_diff = None

        if num_procs > 1 and num_levels > 1:
            for L in self.S.levels:
                if not L.sweep.coll.right_is_node or L.sweep.params.do_coll_update:
                    raise ControllerError(
                        "For PFASST to work, we assume uend^k = u_M^k")

        if num_levels == 1 and self.params.predict_type is not None:
            self.logger.warning(
                'you have specified a predictor type but only a single level.. '
                'predictor will be ignored')
Ejemplo n.º 4
0
def main():
    """
    A simple test program to run IMEX SDC for a single time step
    """
    # initialize level parameters
    level_params = dict()
    level_params['restol'] = 1E-10
    level_params['dt'] = 0.1

    # initialize sweeper parameters
    sweeper_params = dict()
    sweeper_params['collocation_class'] = CollGaussRadau_Right
    sweeper_params['num_nodes'] = 3

    # initialize problem parameters
    problem_params = dict()
    problem_params['nu'] = 0.1  # diffusion coefficient
    problem_params['freq'] = 4  # frequency for the test value
    problem_params['nvars'] = 1023  # number of degrees of freedom

    # initialize step parameters
    step_params = dict()
    step_params['maxiter'] = 20

    # Fill description dictionary for easy hierarchy creation
    description = dict()
    description['problem_class'] = heat1d_forced
    description['problem_params'] = problem_params
    description['dtype_u'] = mesh
    description['dtype_f'] = rhs_imex_mesh
    description['sweeper_class'] = imex_1st_order
    description['sweeper_params'] = sweeper_params
    description['level_params'] = level_params
    description['step_params'] = step_params

    # instantiate the step we are going to work on
    S = step(description=description)

    # run IMEX SDC test and check error, residual and number of iterations
    err, res, niter = run_imex_sdc(S)
    print('Error and residual: %12.8e -- %12.8e' % (err, res))

    assert err <= 1E-5, "ERROR: IMEX SDC iteration did not reduce the error enough, got %s" % err
    assert res <= level_params[
        'restol'], "ERROR: IMEX SDC iteration did not reduce the residual enough, got %s" % res
    assert niter <= 12, "ERROR: IMEX SDC took too many iterations, got %s" % niter
Ejemplo n.º 5
0
    def __init__(self, controller_params, description, comm):
        """
       Initialization routine for PFASST controller

       Args:
           controller_params: parameter set for the controller and the step class
           description: all the parameters to set up the rest (levels, problems, transfer, ...)
           comm: MPI communicator
       """

        # call parent's initialization routine
        super(allinclusive_classic_MPI, self).__init__(controller_params)

        self.logger.warning(
            'classic controller is about to become deprecated, use multigrid controller instead'
        )

        # create single step per processor
        self.S = step(description)

        # pass communicator for future use
        self.comm = comm
        # add request handle container for isend
        self.req_send = []
        # add request handler for status send
        self.req_status = None

        num_procs = self.comm.Get_size()
        rank = self.comm.Get_rank()

        if self.params.dump_setup and rank == 0:
            self.dump_setup(step=self.S,
                            controller_params=controller_params,
                            description=description)

        num_levels = len(self.S.levels)

        assert not (num_procs > 1
                    and num_levels == 1), "ERROR: classic cannot do MSSDC"

        if num_procs > 1 and num_levels > 1:
            for L in self.S.levels:
                if not L.sweep.coll.right_is_node or L.sweep.params.do_coll_update:
                    raise ControllerError(
                        "For PFASST to work, we assume uend^k = u_M^k in this controller"
                    )
Ejemplo n.º 6
0
def compute_specrad():
    """
    Routine to compute spectral radius and norm  of the error propagation matrix E

    Returns:
        numpy.nparray: list of number of nodes
        numpy.nparray: list of fast lambdas
        numpy.nparray: list of spectral radii
        numpy.nparray: list of norms

    """
    problem_params = dict()
    # SET VALUE FOR lambda_slow AND VALUES FOR lambda_fast ###
    problem_params['lambda_s'] = np.array([1.0 * 1j], dtype='complex')
    problem_params['lambda_f'] = np.array([50.0 * 1j, 100.0 * 1j],
                                          dtype='complex')
    problem_params['u0'] = 1.0

    # initialize sweeper parameters
    sweeper_params = dict()
    # SET TYPE OF QUADRATURE NODES ###
    sweeper_params['collocation_class'] = CollGaussRadau_Right

    # initialize level parameters
    level_params = dict()
    level_params['dt'] = 1.0
    t0 = 0.0

    # fill description dictionary for easy step instantiation
    description = dict()
    description['problem_class'] = swfw_scalar  # pass problem class
    description['problem_params'] = problem_params  # pass problem parameters
    description['sweeper_class'] = imex_1st_order  # pass sweeper (see part B)
    description['level_params'] = level_params  # pass level parameters
    description['step_params'] = dict()  # pass step parameters

    nodes_v = np.arange(2, 10)
    specrad = np.zeros((3, np.size(nodes_v)))
    norm = np.zeros((3, np.size(nodes_v)))

    for i in range(0, np.size(nodes_v)):

        sweeper_params['num_nodes'] = nodes_v[i]
        description[
            'sweeper_params'] = sweeper_params  # pass sweeper parameters

        # now the description contains more or less everything we need to create a step
        S = step(description=description)

        L = S.levels[0]
        P = L.prob

        u0 = S.levels[0].prob.u_exact(t0)
        S.init_step(u0)
        QE = L.sweep.QE[1:, 1:]
        QI = L.sweep.QI[1:, 1:]
        Q = L.sweep.coll.Qmat[1:, 1:]
        nnodes = L.sweep.coll.num_nodes
        dt = L.params.dt

        assert nnodes == nodes_v[
            i], 'Something went wrong during instantiation, nnodes is not correct, got %s' % nnodes

        for j in range(0, 2):
            LHS = np.eye(nnodes) - dt * (P.params.lambda_f[j] * QI +
                                         P.params.lambda_s[0] * QE)
            RHS = dt * (
                (P.params.lambda_f[j] + P.params.lambda_s[0]) * Q -
                (P.params.lambda_f[j] * QI + P.params.lambda_s[0] * QE))
            evals, evecs = np.linalg.eig(np.linalg.inv(LHS).dot(RHS))
            specrad[j + 1, i] = np.linalg.norm(evals, np.inf)
            norm[j + 1,
                 i] = np.linalg.norm(np.linalg.inv(LHS).dot(RHS), np.inf)

        if L.sweep.coll.left_is_node:
            # For Lobatto nodes, first column and row are all zeros, since q_1 = q_0; hence remove them
            QI = QI[1:, 1:]
            Q = Q[1:, 1:]
            # Eigenvalue of error propagation matrix in stiff limit: E = I - inv(QI)*Q
            evals, evecs = np.linalg.eig(
                np.eye(nnodes - 1) - np.linalg.inv(QI).dot(Q))
            norm[0, i] = np.linalg.norm(
                np.eye(nnodes - 1) - np.linalg.inv(QI).dot(Q), np.inf)
        else:
            evals, evecs = np.linalg.eig(
                np.eye(nnodes) - np.linalg.inv(QI).dot(Q))
            norm[0, i] = np.linalg.norm(
                np.eye(nnodes) - np.linalg.inv(QI).dot(Q), np.inf)
        specrad[0, i] = np.linalg.norm(evals, np.inf)

        print("Spectral radius of infinitely fast wave case > 1.0 for M=%2i" %
              nodes_v[np.argmax(specrad[0, :] > 1.0)])
        print("Spectral radius of > 1.0 for M=%2i" %
              nodes_v[np.argmax(specrad[1, :] > 1.0)])

    return nodes_v, problem_params['lambda_f'], specrad, norm
Ejemplo n.º 7
0
def compute_stability():
    """
    Routine to compute the stability domains of different configurations of fwsw-SDC

    Returns:
        numpy.ndarray: lambda_slow
        numpy.ndarray: lambda_fast
        int: number of collocation nodes
        int: number of iterations
        numpy.ndarray: stability numbers
    """
    N_s = 100
    N_f = 400

    lam_s_max = 5.0
    lam_f_max = 12.0
    lambda_s = 1j * np.linspace(0.0, lam_s_max, N_s)
    lambda_f = 1j * np.linspace(0.0, lam_f_max, N_f)

    problem_params = dict()
    # SET VALUE FOR lambda_slow AND VALUES FOR lambda_fast ###
    problem_params['lambda_s'] = np.array([0.0])
    problem_params['lambda_f'] = np.array([0.0])
    problem_params['u0'] = 1.0

    # initialize sweeper parameters
    sweeper_params = dict()
    # SET TYPE AND NUMBER OF QUADRATURE NODES ###
    sweeper_params['collocation_class'] = CollGaussRadau_Right
    sweeper_params['num_nodes'] = 3
    sweeper_params['do_coll_update'] = True

    # initialize level parameters
    level_params = dict()
    level_params['dt'] = 1.0

    # fill description dictionary for easy step instantiation
    description = dict()
    description['problem_class'] = swfw_scalar  # pass problem class
    description['problem_params'] = problem_params  # pass problem parameters
    description['sweeper_class'] = imex_1st_order  # pass sweeper (see part B)
    description['sweeper_params'] = sweeper_params  # pass sweeper parameters
    description['level_params'] = level_params  # pass level parameters
    description['step_params'] = dict()  # pass step parameters

    # SET NUMBER OF ITERATIONS - SET K=0 FOR COLLOCATION SOLUTION ###
    K = 3

    # now the description contains more or less everything we need to create a step
    S = step(description=description)

    L = S.levels[0]

    Q = L.sweep.coll.Qmat[1:, 1:]
    nnodes = L.sweep.coll.num_nodes
    dt = L.params.dt

    stab = np.zeros((N_f, N_s), dtype='complex')

    for i in range(0, N_s):
        for j in range(0, N_f):
            lambda_fast = lambda_f[j]
            lambda_slow = lambda_s[i]
            if K is not 0:
                lambdas = [lambda_fast, lambda_slow]
                # LHS, RHS = L.sweep.get_scalar_problems_sweeper_mats(lambdas=lambdas)
                Mat_sweep = L.sweep.get_scalar_problems_manysweep_mat(nsweeps=K, lambdas=lambdas)
            else:
                # Compute stability function of collocation solution
                Mat_sweep = np.linalg.inv(np.eye(nnodes) - dt * (lambda_fast + lambda_slow) * Q)
            if L.sweep.params.do_coll_update:
                stab_fh = 1.0 + (lambda_fast + lambda_slow) * L.sweep.coll.weights.dot(
                    Mat_sweep.dot(np.ones(nnodes)))
            else:
                q = np.zeros(nnodes)
                q[nnodes - 1] = 1.0
                stab_fh = q.dot(Mat_sweep.dot(np.ones(nnodes)))
            stab[j, i] = stab_fh

    return lambda_s, lambda_f, sweeper_params['num_nodes'], K, stab
Ejemplo n.º 8
0
def compute_stab_vs_k(slow_resolved):
    """
    Routine to compute modulus of the stability function

    Args:
        slow_resolved (bool): switch to compute lambda_slow = 1 or lambda_slow = 4

    Returns:
        numpy.ndarray: number of nodes
        numpy.ndarray: number of iterations
        numpy.ndarray: moduli
    """

    mvals = [2, 3, 4]
    kvals = np.arange(1, 10)
    lambda_fast = 10j

    # PLOT EITHER FOR lambda_slow = 1 (resolved) OR lambda_slow = 4 (unresolved)
    if slow_resolved:
        lambda_slow = 1j
    else:
        lambda_slow = 4j
    stabval = np.zeros((np.size(mvals), np.size(kvals)))

    problem_params = dict()
    # SET VALUE FOR lambda_slow AND VALUES FOR lambda_fast ###
    problem_params['lambda_s'] = np.array([0.0])
    problem_params['lambda_f'] = np.array([0.0])
    problem_params['u0'] = 1.0

    # initialize sweeper parameters
    sweeper_params = dict()
    # SET TYPE AND NUMBER OF QUADRATURE NODES ###
    sweeper_params['collocation_class'] = CollGaussRadau_Right
    sweeper_params['do_coll_update'] = True

    # initialize level parameters
    level_params = dict()
    level_params['dt'] = 1.0

    # fill description dictionary for easy step instantiation
    description = dict()
    description['problem_class'] = swfw_scalar  # pass problem class
    description['problem_params'] = problem_params  # pass problem parameters
    description['sweeper_class'] = imex_1st_order  # pass sweeper (see part B)
    description['level_params'] = level_params  # pass level parameters
    description['step_params'] = dict()  # pass step parameters

    for i in range(0, np.size(mvals)):

        sweeper_params['num_nodes'] = mvals[i]
        description[
            'sweeper_params'] = sweeper_params  # pass sweeper parameters

        # now the description contains more or less everything we need to create a step
        S = step(description=description)

        L = S.levels[0]

        nnodes = L.sweep.coll.num_nodes

        for k in range(0, np.size(kvals)):
            Kmax = kvals[k]
            Mat_sweep = L.sweep.get_scalar_problems_manysweep_mat(
                nsweeps=Kmax, lambdas=[lambda_fast, lambda_slow])
            if L.sweep.params.do_coll_update:
                stab_fh = 1.0 + (lambda_fast +
                                 lambda_slow) * L.sweep.coll.weights.dot(
                                     Mat_sweep.dot(np.ones(nnodes)))
            else:
                q = np.zeros(nnodes)
                q[nnodes - 1] = 1.0
                stab_fh = q.dot(Mat_sweep.dot(np.ones(nnodes)))
            stabval[i, k] = np.absolute(stab_fh)

    return mvals, kvals, stabval
Ejemplo n.º 9
0
def compute_and_plot_dispersion():
    problem_params = dict()
    # SET VALUE FOR lambda_slow AND VALUES FOR lambda_fast ###
    problem_params['lambda_s'] = np.array([0.0])
    problem_params['lambda_f'] = np.array([0.0])
    problem_params['u0'] = 1.0

    # initialize sweeper parameters
    sweeper_params = dict()
    # SET TYPE AND NUMBER OF QUADRATURE NODES ###
    sweeper_params['collocation_class'] = CollGaussRadau_Right
    sweeper_params['do_coll_update'] = True
    sweeper_params['num_nodes'] = 3

    # initialize level parameters
    level_params = dict()
    level_params['dt'] = 1.0

    # fill description dictionary for easy step instantiation
    description = dict()
    description['problem_class'] = swfw_scalar  # pass problem class
    description['problem_params'] = problem_params  # pass problem parameters
    description['dtype_u'] = mesh  # pass data type for u
    description['dtype_f'] = rhs_imex_mesh  # pass data type for f
    description['sweeper_class'] = imex_1st_order  # pass sweeper
    description['sweeper_params'] = sweeper_params  # pass sweeper parameters
    description['level_params'] = level_params  # pass level parameters
    description['step_params'] = dict()  # pass step parameters

    # SET NUMBER OF ITERATIONS ###
    K = 3

    # ORDER OF DIRK/IMEX IS EQUAL TO NUMBER OF ITERATIONS AND THUS ORDER OF SDC ###
    dirk_order = K

    c_speed = 1.0
    U_speed = 0.05

    # now the description contains more or less everything we need to create a step
    S = step(description=description)

    L = S.levels[0]

    # u0 = S.levels[0].prob.u_exact(t0)
    # S.init_step(u0)
    QE = L.sweep.QE[1:, 1:]
    QI = L.sweep.QI[1:, 1:]
    Q = L.sweep.coll.Qmat[1:, 1:]
    nnodes = L.sweep.coll.num_nodes
    dt = L.params.dt

    Nsamples = 15
    k_vec = np.linspace(0, np.pi, Nsamples + 1, endpoint=False)
    k_vec = k_vec[1:]
    phase = np.zeros((3, Nsamples))
    amp_factor = np.zeros((3, Nsamples))

    for i in range(0, np.size(k_vec)):

        Cs = -1j * k_vec[i] * np.array([[0.0, c_speed], [c_speed, 0.0]],
                                       dtype='complex')
        Uadv = -1j * k_vec[i] * np.array([[U_speed, 0.0], [0.0, U_speed]],
                                         dtype='complex')

        LHS = np.eye(2 * nnodes) - dt * (np.kron(QI, Cs) + np.kron(QE, Uadv))
        RHS = dt * (np.kron(Q, Uadv + Cs) - np.kron(QI, Cs) -
                    np.kron(QE, Uadv))

        LHSinv = np.linalg.inv(LHS)
        Mat_sweep = np.linalg.matrix_power(LHSinv.dot(RHS), K)
        for k in range(0, K):
            Mat_sweep = Mat_sweep + np.linalg.matrix_power(LHSinv.dot(RHS),
                                                           k).dot(LHSinv)
        ##
        # ---> The update formula for this case need verification!!
        update = dt * np.kron(L.sweep.coll.weights, Uadv + Cs)

        y1 = np.array([1, 0], dtype='complex')
        y2 = np.array([0, 1], dtype='complex')
        e1 = np.kron(np.ones(nnodes), y1)
        stab_fh_1 = y1 + update.dot(Mat_sweep.dot(e1))
        e2 = np.kron(np.ones(nnodes), y2)
        stab_fh_2 = y2 + update.dot(Mat_sweep.dot(e2))
        stab_sdc = np.column_stack((stab_fh_1, stab_fh_2))

        # Stability function of backward Euler is 1/(1-z); system is y' = (Cs+Uadv)*y
        # stab_ie = np.linalg.inv( np.eye(2) - step.status.dt*(Cs+Uadv) )

        # For testing, insert exact stability function exp(-dt*i*k*(Cs+Uadv)
        # stab_fh = la.expm(Cs+Uadv)

        dirkts = dirk(Cs + Uadv, dirk_order)
        stab_fh1 = dirkts.timestep(y1, 1.0)
        stab_fh2 = dirkts.timestep(y2, 1.0)
        stab_dirk = np.column_stack((stab_fh1, stab_fh2))

        rkimex = rk_imex(M_fast=Cs, M_slow=Uadv, order=K)
        stab_fh1 = rkimex.timestep(y1, 1.0)
        stab_fh2 = rkimex.timestep(y2, 1.0)
        stab_rk_imex = np.column_stack((stab_fh1, stab_fh2))

        sol_sdc = findomega(stab_sdc)
        sol_dirk = findomega(stab_dirk)
        sol_rk_imex = findomega(stab_rk_imex)

        # Now solve for discrete phase
        phase[0, i] = sol_sdc.real / k_vec[i]
        amp_factor[0, i] = np.exp(sol_sdc.imag)
        phase[1, i] = sol_dirk.real / k_vec[i]
        amp_factor[1, i] = np.exp(sol_dirk.imag)
        phase[2, i] = sol_rk_imex.real / k_vec[i]
        amp_factor[2, i] = np.exp(sol_rk_imex.imag)

    rcParams['figure.figsize'] = 1.5, 1.5
    fs = 8
    fig = plt.figure()
    plt.plot(k_vec, (U_speed + c_speed) + np.zeros(np.size(k_vec)),
             '--',
             color='k',
             linewidth=1.5,
             label='Exact')
    plt.plot(k_vec,
             phase[1, :],
             '-',
             color='g',
             linewidth=1.5,
             label='DIRK(' + str(dirkts.order) + ')')
    plt.plot(k_vec,
             phase[2, :],
             '-+',
             color='r',
             linewidth=1.5,
             label='IMEX(' + str(rkimex.order) + ')',
             markevery=(2, 3),
             mew=1.0)
    plt.plot(k_vec,
             phase[0, :],
             '-o',
             color='b',
             linewidth=1.5,
             label='SDC(' + str(K) + ')',
             markevery=(1, 3),
             markersize=fs / 2)
    plt.xlabel('Wave number', fontsize=fs, labelpad=0.25)
    plt.ylabel('Phase speed', fontsize=fs, labelpad=0.5)
    plt.xlim([k_vec[0], k_vec[-1:]])
    plt.ylim([0.0, 1.1 * (U_speed + c_speed)])
    fig.gca().tick_params(axis='both', labelsize=fs)
    plt.legend(loc='lower left', fontsize=fs, prop={'size': fs - 2})
    plt.xticks([0, 1, 2, 3], fontsize=fs)
    filename = 'data/phase-K' + str(K) + '-M' + str(
        sweeper_params['num_nodes']) + '.png'
    plt.gcf().savefig(filename, bbox_inches='tight')

    fig = plt.figure()
    plt.plot(k_vec,
             1.0 + np.zeros(np.size(k_vec)),
             '--',
             color='k',
             linewidth=1.5,
             label='Exact')
    plt.plot(k_vec,
             amp_factor[1, :],
             '-',
             color='g',
             linewidth=1.5,
             label='DIRK(' + str(dirkts.order) + ')')
    plt.plot(k_vec,
             amp_factor[2, :],
             '-+',
             color='r',
             linewidth=1.5,
             label='IMEX(' + str(rkimex.order) + ')',
             markevery=(2, 3),
             mew=1.0)
    plt.plot(k_vec,
             amp_factor[0, :],
             '-o',
             color='b',
             linewidth=1.5,
             label='SDC(' + str(K) + ')',
             markevery=(1, 3),
             markersize=fs / 2)
    plt.xlabel('Wave number', fontsize=fs, labelpad=0.25)
    plt.ylabel('Amplification factor', fontsize=fs, labelpad=0.5)
    fig.gca().tick_params(axis='both', labelsize=fs)
    plt.xlim([k_vec[0], k_vec[-1:]])
    plt.ylim([k_vec[0], k_vec[-1:]])
    plt.legend(loc='lower left', fontsize=fs, prop={'size': fs - 2})
    plt.gca().set_ylim([0.0, 1.1])
    plt.xticks([0, 1, 2, 3], fontsize=fs)
    filename = 'data/ampfactor-K' + str(K) + '-M' + str(
        sweeper_params['num_nodes']) + '.png'
    plt.gcf().savefig(filename, bbox_inches='tight')