########################################################################################################################
# Copyright 2021 the authors (see AUTHORS file for full list).                                                         #
#                                                                                                                      #
# This file is part of OpenCMP.                                                                                        #
#                                                                                                                      #
# OpenCMP is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public  #
# License as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) any     #
# later version.                                                                                                       #
#                                                                                                                      #
# OpenCMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied        #
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more  #
# details.                                                                                                             #
#                                                                                                                      #
# You should have received a copy of the GNU Lesser General Public License along with OpenCMP. If not, see             #
# <https://www.gnu.org/licenses/>.                                                                                     #
########################################################################################################################
import ngsolve as ngs
from ngsolve import GridFunction, BilinearForm, LinearForm, Parameter
from opencmp.models import Model
from typing import List, Tuple
from ngsolve import dx
[docs]def explicit_euler(model: Model, gfu_0: List[GridFunction], dt: List[Parameter])\
        
-> Tuple[List[BilinearForm], List[LinearForm]]:
    """
    Explicit Euler time integration scheme.
    This function constructs the final bilinear and linear forms for the time integration scheme by adding the necessary
    time-dependent terms to the model's stationary terms. The returned bilinear and linear forms have NOT been
    assembled.
    Args:
        model: The model to solve.
        gfu_0: List of the solutions of previous time steps ordered from most recent to oldest.
        dt: List of timestep sizes ordered from most recent to oldest.
    Returns:
        Tuple[BilinearForm, LinearForm]:
            - a: A list of the final bilinear forms (as a BilinearForm but not assembled).
            - L: A list of the final linear forms (as a LinearForm but not assembled).
    """
    U, V = model.get_trial_and_test_functions()
    gfu_lst = _split_gfu(gfu_0)
    # All the terms use the same dt.
    tmp_dt = dt[0]
    # Construct the bilinear forms
    a: List[BilinearForm]   = []
    a_lst                   = model.construct_bilinear_time_coefficient(U, V, tmp_dt, 0)
    for i in range(model.num_weak_forms):
        a_tmp = BilinearForm(model.fes)
        a_tmp += a_lst[i]
        a.append(a_tmp)
    # Construct the linear forms
    L : List[BilinearForm]  = []
    L_ode                   = model.construct_bilinear_time_ODE(gfu_lst[1], V, tmp_dt, 1)
    L_lst                   = model.construct_linear(V, gfu_lst[1], tmp_dt, 1)
    for i in range(model.num_weak_forms):
        L_tmp = LinearForm(model.fes)
        L_tmp += -1.0 * L_ode[i]
        L_tmp += L_lst[i]
        L.append(L_tmp)
    # Add time discretization terms
    _add_dt_terms(a, L, gfu_lst, model, 'explicit euler')
    return a, L 
[docs]def implicit_euler(model: Model, gfu_0: List[GridFunction], dt: List[Parameter], step: int = 0) \
        
-> Tuple[List[BilinearForm], List[LinearForm]]:
    """
    Implicit Euler time integration scheme.
    This function constructs the final bilinear and linear forms for the time integration scheme by adding the necessary
    time-dependent terms to the model's stationary terms. The returned bilinear and linear forms have NOT been
    assembled.
    Args:
        model: The model to solve.
        gfu_0: List of the solutions of previous time steps ordered from most recent to oldest.
        dt: List of timestep sizes ordered from most recent to oldest.
        step: Used for adaptive_three_step to ensure the correct boundary condition and model function values are used
            when the half steps are taken.
    Returns:
        Tuple[BilinearForm, LinearForm]:
            - a: A list of the final bilinear forms (as a BilinearForm but not assembled).
            - L: A list of the final linear forms (as a LinearForm but not assembled).
    """
    U, V = model.get_trial_and_test_functions()
    gfu_lst = _split_gfu(gfu_0)
    # All the terms use the same dt.
    tmp_dt = dt[step]
    # Construct the bilinear form
    a: List[BilinearForm]   = []
    a_lst                   = model.construct_bilinear_time_coefficient(U, V, tmp_dt, step)
    a_ode                   = model.construct_bilinear_time_ODE(U, V, tmp_dt, step)
    for i in range(model.num_weak_forms):
        a_tmp = BilinearForm(model.fes)
        a_tmp += a_lst[i]
        a_tmp += a_ode[i]
        a.append(a_tmp)
    # Construct the linear form
    L : List[BilinearForm]  = []
    L_lst                   = model.construct_linear(V, None, tmp_dt, step)
    for i in range(model.num_weak_forms):
        L_tmp = LinearForm(model.fes)
        L_tmp += L_lst[i]
        L.append(L_tmp)
    # Add time discretization terms
    _add_dt_terms(a, L, gfu_lst, model, 'implicit euler')
    return a, L 
[docs]def crank_nicolson(model: Model, gfu_0: List[GridFunction], dt: List[Parameter])\
        
-> Tuple[List[BilinearForm], List[LinearForm]]:
    """
    Crank Nicolson (trapezoidal rule) time integration scheme.
    This function constructs the final bilinear and linear forms for the time integration scheme by adding the necessary
    time-dependent terms to the model's stationary terms. The returned bilinear and linear forms have NOT been
    assembled.
    Args:
        model: The model to solve.
        gfu_0: List of the solutions of previous time steps ordered from most recent to oldest.
        dt: List of timestep sizes ordered from most recent to oldest.
    Returns:
        Tuple[BilinearForm, LinearForm]:
            - a: A list of the final bilinear forms (as a BilinearForm but not assembled).
            - L: A list of the final linear forms (as a LinearForm but not assembled).
    """
    U, V = model.get_trial_and_test_functions()
    gfu_lst = _split_gfu(gfu_0)
    # All the terms use the same dt.
    tmp_dt = dt[0]
    # Construct the bilinear form
    a: List[BilinearForm]   = []
    a_lst                   = model.construct_bilinear_time_coefficient(U, V, tmp_dt, 0)
    a_ode                   = model.construct_bilinear_time_ODE(U, V, tmp_dt, 0)
    for i in range(model.num_weak_forms):
        a_tmp = ngs.BilinearForm(model.fes)
        a_tmp += a_lst[i]
        a_tmp += 0.5 * a_ode[i]
        a.append(a_tmp)
    # Construct the linear form
    L: List[BilinearForm]   = []
    L_ode                   = model.construct_bilinear_time_ODE(gfu_lst[1], V, tmp_dt, 1)
    L_lst_0                 = model.construct_linear(V, gfu_lst[0], tmp_dt, 0)
    L_lst_1                 = model.construct_linear(V, gfu_lst[1], tmp_dt, 1)
    for i in range(model.num_weak_forms):
        L_tmp = LinearForm(model.fes)
        L_tmp += -0.5 * L_ode[i]
        L_tmp += 0.5 * L_lst_0[i]
        L_tmp += 0.5 * L_lst_1[i]
        L.append(L_tmp)
    # Add time discretization terms
    _add_dt_terms(a, L, gfu_lst, model, 'crank nicolson')
    return a, L 
[docs]def euler_IMEX(model: Model, gfu_0: List[GridFunction], dt: List[Parameter])\
        
-> Tuple[List[BilinearForm], List[LinearForm]]:
    """
    First order IMEX time integration scheme.
    This function constructs the final bilinear and linear forms for the time integration scheme by adding the necessary
    time-dependent terms to the model's stationary terms. The returned bilinear and linear forms have NOT been
    assembled.
    Args:
        model: The model to solve.
        gfu_0: List of the solutions of previous time steps ordered from most recent to oldest.
        dt: List of timestep sizes ordered from most recent to oldest.
    Returns:
        Tuple[BilinearForm, LinearForm]:
            - a: A list of the final bilinear forms (as a BilinearForm but not assembled).
            - L: A list of the final linear forms (as a LinearForm but not assembled).
    """
    U, V = model.get_trial_and_test_functions()
    gfu_lst = _split_gfu(gfu_0)
    # All the terms use the same dt.
    tmp_dt = dt[0]
    # Construct the bilinear form
    a: List[BilinearForm]   = []
    a_lst                   = model.construct_bilinear_time_coefficient(U, V, tmp_dt, 0)
    a_ode                   = model.construct_bilinear_time_ODE(U, V, tmp_dt, 0)
    for i in range(model.num_weak_forms):
        a_tmp = BilinearForm(model.fes)
        a_tmp += a_lst[i]
        a_tmp += a_ode[i]
        a.append(a_tmp)
    # Construct the linear form
    L : List[BilinearForm]  = []
    L_lst                   = model.construct_linear(V, None, tmp_dt, 0)
    L_imex                  = model.construct_imex_explicit(V, gfu_lst[1], tmp_dt, 0)
    for i in range(model.num_weak_forms):
        L_tmp = ngs.LinearForm(model.fes)
        L_tmp += L_lst[i]
        L_tmp += L_imex[i]
        L.append(L_tmp)
    # Add time discretization terms
    _add_dt_terms(a, L, gfu_lst, model, 'implicit euler')
    return a, L 
[docs]def CNLF(model: Model, gfu_0: List[GridFunction], dt: List[Parameter]) \
        
-> Tuple[List[BilinearForm], List[LinearForm]]:
    """
    Crank Nicolson Leap Frog IMEX time integration scheme.
    This function constructs the final bilinear and linear forms for the time integration scheme by adding the necessary
    time-dependent terms to the model's stationary terms. The returned bilinear and linear forms have NOT been
    assembled.
    Args:
        model: The model to solve.
        gfu_0: List of the solutions of previous time steps ordered from most recent to oldest.
        dt: List of timestep sizes ordered from most recent to oldest.
    Returns:
        Tuple[BilinearForm, LinearForm]:
            - a: A list of the final bilinear forms (as a BilinearForm but not assembled).
            - L: A list of the final linear forms (as a LinearForm but not assembled).
    """
    U, V = model.get_trial_and_test_functions()
    gfu_lst = _split_gfu(gfu_0)
    # All the terms use the same dt.
    tmp_dt = dt[0]
    # Construct the bilinear forms
    a: List[BilinearForm]   = []
    a_lst                   = model.construct_bilinear_time_coefficient(U, V, tmp_dt, 0)
    a_ode                   = model.construct_bilinear_time_ODE(U, V, tmp_dt, 0)
    for i in range(model.num_weak_forms):
        a_tmp = BilinearForm(model.fes)
        a_tmp += 2.0 * a_lst[i]
        a_tmp += a_ode[i]
        a.append(a_tmp)
    # Construct the linear form
    L : List[BilinearForm]  = []
    L_lst                   = model.construct_linear(V, gfu_lst[2], tmp_dt, 2)
    L_ode                   = model.construct_bilinear_time_ODE(gfu_lst[2], V, tmp_dt, 2)
    L_imex                  = model.construct_imex_explicit(V, gfu_lst[1], tmp_dt, 1)
    for i in range(model.num_weak_forms):
        L_tmp = LinearForm(model.fes)
        L_tmp += L_lst[i]
        L_tmp += -1.0 * L_ode[i]
        L_tmp += 2.0 * L_imex[i]
        L.append(L_tmp)
    # Add time discretization terms
    _add_dt_terms(a, L, gfu_lst, model, 'CNLF')
    return a, L 
[docs]def SBDF(model: Model, gfu_0: List[GridFunction], dt: List[Parameter]) \
        
-> Tuple[List[BilinearForm], List[LinearForm]]:
    """
    Third order semi-implicit backwards differencing time integration scheme.
    This function constructs the final bilinear and linear forms for the time integration scheme by adding the necessary
    time-dependent terms to the model's stationary terms. The returned bilinear and linear forms have NOT been
    assembled.
    Args:
        model: The model to solve.
        gfu_0: List of the solutions of previous time steps ordered from most recent to oldest.
        dt: List of timestep sizes ordered from most recent to oldest.
    Returns:
        Tuple[BilinearForm, LinearForm]:
            - a: A list of the final bilinear forms (as a BilinearForm but not assembled).
            - L: A list of the final linear forms (as a LinearForm but not assembled).
    """
    U, V = model.get_trial_and_test_functions()
    gfu_lst = _split_gfu(gfu_0)
    # All the terms use the same dt.
    tmp_dt = dt[0]
    # Construct the bilinear forms
    a: List[BilinearForm]   = []
    a_lst                   = model.construct_bilinear_time_coefficient(U, V, tmp_dt, 0)
    a_ode                   = model.construct_bilinear_time_ODE(U, V, tmp_dt, 0)
    for i in range(model.num_weak_forms):
        a_tmp = ngs.BilinearForm(model.fes)
        a_tmp += a_lst[i]
        a_tmp += a_ode[i]
        a.append(a_tmp)
    # Construct the linear form
    L : List[BilinearForm]  = []
    L_lst                   = model.construct_linear(V, gfu_lst[0], tmp_dt, 0)
    L_imex_1                = model.construct_imex_explicit(V, gfu_lst[1], tmp_dt, 1)
    L_imex_2                = model.construct_imex_explicit(V, gfu_lst[2], tmp_dt, 2)
    L_imex_3                = model.construct_imex_explicit(V, gfu_lst[3], tmp_dt, 3)
    for i in range(model.num_weak_forms):
        L_tmp = ngs.LinearForm(model.fes)
        L_tmp += L_lst[i]
        L_tmp += 3.0 * L_imex_1[i]
        L_tmp += -3.0 * L_imex_2[i]
        L_tmp += L_imex_3[i]
        L.append(L_tmp)
    # Add time discretization terms
    _add_dt_terms(a, L, gfu_lst, model, 'SBDF')
    return a, L 
[docs]def adaptive_IMEX_pred(model: Model, gfu_0: List[GridFunction], dt: List[Parameter]) \
        
-> Tuple[List[BilinearForm], List[LinearForm]]:
    """
    Predictor for the adaptive time-stepping IMEX time integration scheme.
    This function constructs the final bilinear and linear forms for the time integration scheme by adding the necessary
    time-dependent terms to the model's stationary terms. The returned bilinear and linear forms have NOT been
    assembled.
    Args:
        model: The model to solve.
        gfu_0: List of the solutions of previous time steps ordered from most recent to oldest.
        dt: List of timestep sizes ordered from most recent to oldest.
    Returns:
        Tuple[BilinearForm, LinearForm]:
            - a: A list of the final bilinear forms (as a BilinearForm but not assembled).
            - L: A list of the final linear forms (as a LinearForm but not assembled).
    """
    U, V = model.get_trial_and_test_functions()
    gfu_lst = _split_gfu(gfu_0)
    # Create list of the previous time-step's result
    # NOTE: It is assumed that pressure is always the 2nd in the list
    gfu_prev = [gfu_lst[0][i] for i in range(len(gfu_lst[0]))]
    # Zero out pressure
    gfu_prev[1] = 0.0
    # Operators specific to this time integration scheme.
    w = dt[0] / dt[1]
    E = model.construct_gfu().components[0]
    E_expr = (1.0 + w) * gfu_lst[0][0] - w * gfu_lst[1][0]
    E.Set(E_expr)
    # All the terms use the same dt.
    tmp_dt = dt[0]
    # Construct the bilinear forms
    a: List[BilinearForm]   = []
    a_lst                   = model.construct_bilinear_time_coefficient(U, V, tmp_dt, 0)
    a_ode                   = model.construct_bilinear_time_ODE(U, V, tmp_dt, 0)
    for i in range(model.num_weak_forms):
        a_tmp = BilinearForm(model.fes)
        a_tmp += a_lst[i]
        a_tmp += a_ode[i]
        a.append(a_tmp)
    # Construct the linear forms
    L : List[BilinearForm]  = []
    # TODO: Why doesn't using E work? Numerical error?
    L_lst                   = model.construct_linear(V, gfu_lst[0], tmp_dt, 0)
    L_imex                  = model.construct_imex_explicit(V, gfu_prev, tmp_dt, 0)
    for i in range(model.num_weak_forms):
        L_tmp = LinearForm(model.fes)
        L_tmp += L_lst[i]
        L_tmp += L_imex[i]
        L.append(L_tmp)
    # Add time discretization terms
    _add_dt_terms(a, L, gfu_lst, model, 'crank nicolson')
    return a, L 
[docs]def RK_222(model: Model, gfu_0: List[GridFunction], dt: List[Parameter], step: int) \
        
-> Tuple[List[BilinearForm], List[LinearForm]]:
    """
    Two-step second-order Runge Kutta IMEX time integration scheme.
    This function constructs the final bilinear and linear forms for the time integration scheme by adding the necessary
    time-dependent terms to the model's stationary terms. The returned bilinear and linear forms have NOT been
    assembled.
    
    Args:
        model: The model to solve.
        gfu_0: List of the solutions of intermediate steps in reverse step order (ie: [step n+1 sol, step 2 sol, step 1
            sol, step n sol]).
        dt: List of current time step size.
        step: Which step of the Runge Kutta scheme should be assembled.
    Returns:
        Tuple[BilinearForm, LinearForm]:
            - a: A list of the final bilinear forms (as a BilinearForm but not assembled).
            - L: A list of the final linear forms (as a LinearForm but not assembled).
    """
    U, V = model.get_trial_and_test_functions()
    gfu_lst = _split_gfu(gfu_0)
    # Define scheme-specific constants. These might eventually become user-specified constants.
    gamma = 0.5 * (2.0 - ngs.sqrt(2.0))
    delta = 1.0 - 1.0 / (2.0 - ngs.sqrt(2.0))
    # Define the variables to hold the linear and bilinear forms
    a: List[BilinearForm] = []
    L: List[BilinearForm] = []
    if step == 1:
        # All the terms use the same dt.
        tmp_dt = dt[1]
        # Construct the bilinear forms
        a_lst = model.construct_bilinear_time_coefficient(U, V, tmp_dt, 1)
        a_ode = model.construct_bilinear_time_ODE(U, V, tmp_dt, 1)
        for i in range(model.num_weak_forms):
            a_tmp = BilinearForm(model.fes)
            a_tmp += a_lst[i]
            a_tmp += a_ode[i]
            a.append(a_tmp)
        # Construct the linear forms
        L_lst   = model.construct_linear(V, None, tmp_dt, 1)
        L_imex  = model.construct_imex_explicit(V, gfu_lst[2], tmp_dt, 2)
        for i in range(model.num_weak_forms):
            L_tmp = LinearForm(model.fes)
            L_tmp += L_lst[i]
            L_tmp += L_imex[i]
            L.append(L_tmp)
    elif step == 2:
        # All the terms use the same dt.
        tmp_dt = dt[0]
        # Construct the bilinear forms
        a_lst   = model.construct_bilinear_time_coefficient(U, V, tmp_dt, 0)
        a_ode   = model.construct_bilinear_time_ODE(U, V, tmp_dt, 0)
        for i in range(model.num_weak_forms):
            a_tmp = BilinearForm(model.fes)
            a_tmp += a_lst[i]
            a_tmp += gamma * a_ode[i]
            a.append(a_tmp)
        # Construct the linear form
        L_lst_0     = model.construct_linear(V, gfu_lst[0], tmp_dt, 0)
        L_lst_1     = model.construct_linear(V, gfu_lst[1], tmp_dt, 1)
        L_imex_1    = model.construct_imex_explicit(V, gfu_lst[1], tmp_dt, 1)
        L_imex_2    = model.construct_imex_explicit(V, gfu_lst[2], tmp_dt, 2)
        L_ode       = model.construct_bilinear_time_ODE(gfu_lst[1], V, tmp_dt, 1)
        for i in range(model.num_weak_forms):
            L_tmp = LinearForm(model.fes)
            L_tmp += gamma          * L_lst_0[i]
            L_tmp += (1.0 - gamma)  * L_lst_1[i]
            L_tmp += (1.0 - delta)  * L_imex_1[i]
            L_tmp += delta          * L_imex_2[i]
            L_tmp += -(1.0 - gamma) * L_ode[i]
            L.append(L_tmp)
    else:
        raise ValueError('RK 222 only has 2 steps. Step \'{}\' is invalid'.format(step))
    _add_dt_terms(a, L, gfu_lst, model, 'RK 222')
    return a, L 
[docs]def RK_232(model: Model, gfu_0: List[GridFunction], dt: List[Parameter], step: int)\
        
-> Tuple[List[BilinearForm], List[LinearForm]]:
    """
    Three-step second-order Runge Kutta IMEX time integration scheme.
    This function constructs the final bilinear and linear forms for the time integration scheme by adding the necessary
    time-dependent terms to the model's stationary terms. The returned bilinear and linear forms have NOT been
    assembled.
    Args:
        model: The model to solve.
        gfu_0: List of the solutions of intermediate steps in reverse step order (ie: [step n+1 sol, step 2 sol, step 1
            sol, step n sol]).
        dt: List of current time step size.
        step: Which step of the Runge Kutta scheme should be assembled.
    Returns:
        Tuple[BilinearForm, LinearForm]:
            - a: A list of the final bilinear forms (as a BilinearForm but not assembled).
            - L: A list of the final linear forms (as a LinearForm but not assembled).
    """
    U, V = model.get_trial_and_test_functions()
    gfu_lst = _split_gfu(gfu_0)
    # Define scheme-specific constants. These might eventually become user-specified constants.
    gamma = 0.5 * (2.0 - ngs.sqrt(2.0))
    delta = -2.0 * ngs.sqrt(2.0) / 3.0
    # Define the variables to hold the linear and bilinear forms
    a: List[BilinearForm] = []
    L: List[BilinearForm] = []
    if step == 1:
        # All the terms use the same dt.
        tmp_dt = dt[2]
        # Construct the bilinear form
        a_lst = model.construct_bilinear_time_coefficient(U, V, tmp_dt, 2)
        a_ode = model.construct_bilinear_time_ODE(U, V, tmp_dt, 2)
        for i in range(model.num_weak_forms):
            a_tmp = BilinearForm(model.fes)
            a_tmp += a_lst[i]
            a_tmp += a_ode[i]
            a.append(a_tmp)
        # Construct the linear form
        L_lst   = model.construct_linear(V, None, tmp_dt, 2)
        L_imex  = model.construct_imex_explicit(V, gfu_lst[3], tmp_dt, 3)
        for i in range(model.num_weak_forms):
            L_tmp = LinearForm(model.fes)
            L_tmp += L_lst[i]
            L_tmp += L_imex[i]
            L.append(L_tmp)
    elif step == 2:
        # All the terms use the same dt.
        tmp_dt = dt[1]
        # Construct the bilinear form
        a_lst = model.construct_bilinear_time_coefficient(U, V, tmp_dt, 1)
        a_ode = model.construct_bilinear_time_ODE(U, V, tmp_dt, 1)
        for i in range(model.num_weak_forms):
            a_tmp = BilinearForm(model.fes)
            a_tmp += a_lst[i]
            a_tmp += gamma * a_ode[i]
            a.append(a_tmp)
        # Construct the linear form
        L_lst_1     = model.construct_linear(V, None, tmp_dt, 1)
        L_lst_2     = model.construct_linear(V, gfu_lst[2], tmp_dt, 2)
        L_imex_2    = model.construct_imex_explicit(V, gfu_lst[2], tmp_dt, 2)
        L_imex_3    = model.construct_imex_explicit(V, gfu_lst[3], tmp_dt, 3)
        L_ode       = model.construct_bilinear_time_ODE(gfu_lst[2], V, tmp_dt, 2)
        for i in range(model.num_weak_forms):
            L_tmp = LinearForm(model.fes)
            L_tmp += gamma          * L_lst_1[i]
            L_tmp += (1.0 - gamma)  * L_lst_2[i]
            L_tmp += (1.0 - delta)  * L_imex_2[i]
            L_tmp += delta          * L_imex_3[i]
            L_tmp += -(1.0 - gamma) * L_ode[i]
            L.append(L_tmp)
    elif step == 3:
        # All the terms use the same dt.
        tmp_dt = dt[0]
        # Construct the bilinear form
        a_lst = model.construct_bilinear_time_coefficient(U, V, tmp_dt, 0)
        a_ode = model.construct_bilinear_time_ODE(U, V, tmp_dt, 0)
        for i in range(model.num_weak_forms):
            a_tmp = BilinearForm(model.fes)
            a_tmp += a_lst[i]
            a_tmp += gamma * a_ode[i]
            a.append(a_tmp)
        # Construct the linear form
        L_lst_0     = model.construct_linear(V, gfu_lst[0], tmp_dt, 0)
        L_lst_2     = model.construct_linear(V, gfu_lst[2], tmp_dt, 2)
        L_imex_1    = model.construct_imex_explicit(V, gfu_lst[1], tmp_dt, 1)
        L_imex_2    = model.construct_imex_explicit(V, gfu_lst[2], tmp_dt, 2)
        L_ode       = model.construct_bilinear_time_ODE(gfu_lst[2], V, tmp_dt, 2)
        for i in range(model.num_weak_forms):
            L_tmp = LinearForm(model.fes)
            L_tmp += gamma          * L_lst_0[i]
            L_tmp += (1.0 - gamma)  * L_lst_2[i]
            L_tmp += delta          * L_imex_1[i]
            L_tmp += (1.0 - delta)  * L_imex_2[i]
            L_tmp += -(1.0 - gamma) * L_ode[i]
            L.append(L_tmp)
    else:
        raise ValueError('RK 232 only has 3 steps. Step \'{}\' is invalid'.format(step))
    _add_dt_terms(a, L, gfu_lst, model, 'RK 232')
    return a, L 
[docs]def _add_dt_terms(a: List[BilinearForm], L: List[BilinearForm], gfu_lst: List[List[GridFunction]],
                  model: Model, time_discretization_scheme: str) -> None:
    """
    Function to handle the adding of the time discretization terms to the linear and bilinear forms.
    Args:
        a: List of all of the bilinear forms for the model
        L: List of all of the linear forms for the model
        gfu_lst: List of the solutions of previous time steps in reverse chronological order.
        model:
        time_discretization_scheme: The name of the time integration discretization being used.
    """
    # Construct the time discretization terms
    a_dt, L_dt = model.time_derivative_terms(gfu_lst, time_discretization_scheme)
    # When adding the time discretization term, multiply by the phase field if using the diffuse interface method.
    if model.DIM:
        for i in range(len(a_dt)):
            a_dt[i] *= model.DIM_solver.phi_gfu
            L_dt[i] *= model.DIM_solver.phi_gfu
    for i in range(model.num_weak_forms):
        a[i] += a_dt[i] * dx
        L[i] += L_dt[i] * dx 
[docs]def _split_gfu(gfu: List[GridFunction]) -> List[List[GridFunction]]:
    """
    Function to separate out the various components of each gridfunction solution to a previous timestep.
    The first entry in gfu_lst is None so that its indexing matches the indexing for dt and the step argument in
    the functions for constructing the weak form (ie: gfu_lst[1], dt[1] and step=1 all refer to values at time n).
    The final result should be gfu_lst = [None, [component 0, component 1...] at t^n, [component 0, component 1...] 
    at t^n-1, ...].
    Args:
        gfu: List of gridfunctions to split up
    Returns:
        [[component 0, component 1...] at t^n, [component 0, component 1...] at t^n-1, ...]
    """
    # NOTE: First entry is supposed to be None, see function docstring
    gfu_lst: List[List[GridFunction]] = [None]
    for i in range(len(gfu)):
        if len(gfu[i].components) > 0:
            gfu_lst.append([gfu[i].components[j] for j in range(len(gfu[i].components))])
        else:
            gfu_lst.append([gfu[i]])
    return gfu_lst