def discretize_nonlinear_instationary_advection_diffusion_fv(analytical_problem, diameter=None, nt=100, num_flux='lax_friedrichs',
                                                   lxf_lambda=1., domain_discretizer=None, grid=None, boundary_info=None):

    assert isinstance(analytical_problem, InstationaryAdvectionDiffusionProblem)
    assert grid is None or boundary_info is not None
    assert boundary_info is None or grid is not None
    assert grid is None or domain_discretizer is None
    assert num_flux in ('lax_friedrichs', 'engquist_osher')

    if grid is None:
        domain_discretizer = domain_discretizer or discretize_domain_default
        if diameter is None:
            grid, boundary_info = domain_discretizer(analytical_problem.domain)
        else:
            grid, boundary_info = domain_discretizer(analytical_problem.domain, diameter=diameter)

    p = analytical_problem
    
    if num_flux == 'lax_friedrichs':
        L = nonlinear_advection_lax_friedrichs_operator(grid, boundary_info, p.flux_function, dirichlet_data=p.dirichlet_data,
                                            lxf_lambda=lxf_lambda)
    else:
        L = nonlinear_advection_engquist_osher_operator(grid, boundary_info, p.flux_function, p.flux_function_derivative,
                                            dirichlet_data=p.dirichlet_data)

    I = p.initial_data.evaluate(grid.quadrature_points(0, order=2)).squeeze()
    I = np.sum(I * grid.reference_element.quadrature(order=2)[1], axis=1) * (1. / grid.reference_element.volume)
    I = NumpyVectorArray(I)
    inject_sid(I, __name__ + '.discretize_nonlinear_instationary_advection_diffusion_fv.initial_data', p.initial_data, grid)

    D = DiffusionOperator(grid, boundary_info, p.diffusion)
    D = type(D).lincomb(operators=[D], name='diffusion', coefficients_name='diffusion')
    F = None if p.rhs is None else DiffusionRHSOperatorFunctional(grid, boundary_info, p.rhs, 
                                                                  p.neumann_data, p.dirichlet_data, p.diffusion)
    F = type(F).lincomb(operators=[F], name='rhs', coefficients_name='diffusion')
    
    products = {'l2': L2Product(grid, boundary_info)}
    visualizer = PatchVisualizer(grid=grid, bounding_box=grid.domain, codim=0)
    parameter_space = p.parameter_space if hasattr(p, 'parameter_space') else None

    discretization = InstationaryImexDiscretization(explicit_operator=L, implicit_operator=D, rhs=F, 
                                                                  initial_data=I, T=p.T, nt=nt, products=products,
                                                                  parameter_space=parameter_space, visualizer=visualizer,
                                                                  name='{}_FV'.format(p.name))

    return discretization, {'grid': grid, 'boundary_info': boundary_info}
Exemple #2
0
def discretize_nonlinear_instationary_advection_fv(analytical_problem,
                                                   diameter=None,
                                                   nt=100,
                                                   num_flux='lax_friedrichs',
                                                   lxf_lambda=1.,
                                                   eo_gausspoints=5,
                                                   eo_intervals=1,
                                                   num_values=None,
                                                   domain_discretizer=None,
                                                   grid=None,
                                                   boundary_info=None):
    """Discretizes an |InstationaryAdvectionProblem| using the finite volume method.

    Simple explicit Euler time-stepping is used for time-discretization.

    Parameters
    ----------
    analytical_problem
        The |InstationaryAdvectionProblem| to discretize.
    diameter
        If not `None`, `diameter` is passed to the `domain_discretizer`.
    nt
        The number of time-steps.
    num_flux
        The numerical flux to use in the finite volume formulation. Allowed
        values are `'lax_friedrichs'`, `'engquist_osher'`, `'simplified_engquist_osher'`.
        (See :mod:`pymor.operators.fv`.)
    lxf_lambda
        The stabilization parameter for the Lax-Friedrichs numerical flux.
        (Ignored, if different flux is chosen.)
    eo_gausspoints
        Number of Gauss points for the Engquist-Osher numerical flux.
        (Ignored, if different flux is chosen.)
    eo_intervals
        Number of sub-intervals to use for integration when using Engquist-Osher
        numerical flux. (Ignored, if different flux is chosen.)
    num_values
        The number of returned vectors of the solution trajectory. If `None`, each
        intermediate vector that is calculated is returned.
    domain_discretizer
        Discretizer to be used for discretizing the analytical domain. This has
        to be a function `domain_discretizer(domain_description, diameter, ...)`.
        If further arguments should be passed to the discretizer, use
        :func:`functools.partial`. If `None`, |discretize_domain_default| is used.
    grid
        Instead of using a domain discretizer, the |Grid| can also be passed directly
        using this parameter.
    boundary_info
        A |BoundaryInfo| specifying the boundary types of the grid boundary entities.
        Must be provided if `grid` is specified.

    Returns
    -------
    discretization
        The |Discretization| that has been generated.
    data
        Dictionary with the following entries:

            :grid:           The generated |Grid|.
            :boundary_info:  The generated |BoundaryInfo|.


    """

    assert isinstance(analytical_problem, InstationaryAdvectionProblem)
    assert grid is None or boundary_info is not None
    assert boundary_info is None or grid is not None
    assert grid is None or domain_discretizer is None
    assert num_flux in ('lax_friedrichs', 'engquist_osher',
                        'simplified_engquist_osher')

    if grid is None:
        domain_discretizer = domain_discretizer or discretize_domain_default
        if diameter is None:
            grid, boundary_info = domain_discretizer(analytical_problem.domain)
        else:
            grid, boundary_info = domain_discretizer(analytical_problem.domain,
                                                     diameter=diameter)

    p = analytical_problem

    if num_flux == 'lax_friedrichs':
        L = nonlinear_advection_lax_friedrichs_operator(
            grid,
            boundary_info,
            p.flux_function,
            dirichlet_data=p.dirichlet_data,
            lxf_lambda=lxf_lambda)
    elif num_flux == 'engquist_osher':
        L = nonlinear_advection_engquist_osher_operator(
            grid,
            boundary_info,
            p.flux_function,
            p.flux_function_derivative,
            gausspoints=eo_gausspoints,
            intervals=eo_intervals,
            dirichlet_data=p.dirichlet_data)
    else:
        L = nonlinear_advection_simplified_engquist_osher_operator(
            grid,
            boundary_info,
            p.flux_function,
            p.flux_function_derivative,
            dirichlet_data=p.dirichlet_data)
    F = None if p.rhs is None else L2ProductFunctional(grid, p.rhs)

    if p.initial_data.parametric:

        def initial_projection(U, mu):
            I = p.initial_data.evaluate(grid.quadrature_points(0, order=2),
                                        mu).squeeze()
            I = np.sum(I * grid.reference_element.quadrature(order=2)[1],
                       axis=1) * (1. / grid.reference_element.volume)
            I = NumpyVectorArray(I, copy=False)
            return I.lincomb(U).data

        inject_sid(
            initial_projection, __name__ +
            '.discretize_nonlinear_instationary_advection_fv.initial_data',
            p.initial_data, grid)
        I = NumpyGenericOperator(initial_projection,
                                 dim_range=grid.size(0),
                                 linear=True,
                                 parameter_type=p.initial_data.parameter_type)
    else:
        I = p.initial_data.evaluate(grid.quadrature_points(0,
                                                           order=2)).squeeze()
        I = np.sum(I * grid.reference_element.quadrature(order=2)[1],
                   axis=1) * (1. / grid.reference_element.volume)
        I = NumpyVectorArray(I, copy=False)
        inject_sid(
            I, __name__ +
            '.discretize_nonlinear_instationary_advection_fv.initial_data',
            p.initial_data, grid)

    products = {'l2': L2Product(grid, boundary_info)}
    if grid.dim == 2:
        visualizer = PatchVisualizer(grid=grid,
                                     bounding_box=grid.domain,
                                     codim=0)
    elif grid.dim == 1:
        visualizer = Matplotlib1DVisualizer(grid, codim=0)
    else:
        visualizer = None
    parameter_space = p.parameter_space if hasattr(p,
                                                   'parameter_space') else None
    time_stepper = ExplicitEulerTimeStepper(nt=nt)

    discretization = InstationaryDiscretization(
        operator=L,
        rhs=F,
        initial_data=I,
        T=p.T,
        products=products,
        time_stepper=time_stepper,
        parameter_space=parameter_space,
        visualizer=visualizer,
        num_values=num_values,
        name='{}_FV'.format(p.name))

    return discretization, {'grid': grid, 'boundary_info': boundary_info}
Exemple #3
0
def discretize_nonlinear_instationary_advection_fv(analytical_problem, diameter=None, nt=100, num_flux='lax_friedrichs',
                                                   lxf_lambda=1., eo_gausspoints=5, eo_intervals=1, num_values=None,
                                                   domain_discretizer=None, grid=None, boundary_info=None):
    """Discretizes an |InstationaryAdvectionProblem| using the finite volume method.

    Simple explicit Euler time-stepping is used for time-discretization.

    Parameters
    ----------
    analytical_problem
        The |InstationaryAdvectionProblem| to discretize.
    diameter
        If not `None`, `diameter` is passed to the `domain_discretizer`.
    nt
        The number of time-steps.
    num_flux
        The numerical flux to use in the finite volume formulation. Allowed
        values are `'lax_friedrichs'`, `'engquist_osher'`, `'simplified_engquist_osher'`.
        (See :mod:`pymor.operators.fv`.)
    lxf_lambda
        The stabilization parameter for the Lax-Friedrichs numerical flux.
        (Ignored, if different flux is chosen.)
    eo_gausspoints
        Number of Gauss points for the Engquist-Osher numerical flux.
        (Ignored, if different flux is chosen.)
    eo_intervals
        Number of sub-intervals to use for integration when using Engquist-Osher
        numerical flux. (Ignored, if different flux is chosen.)
    num_values
        The number of returned vectors of the solution trajectory. If `None`, each
        intermediate vector that is calculated is returned.
    domain_discretizer
        Discretizer to be used for discretizing the analytical domain. This has
        to be a function `domain_discretizer(domain_description, diameter, ...)`.
        If further arguments should be passed to the discretizer, use
        :func:`functools.partial`. If `None`, |discretize_domain_default| is used.
    grid
        Instead of using a domain discretizer, the |Grid| can also be passed directly
        using this parameter.
    boundary_info
        A |BoundaryInfo| specifying the boundary types of the grid boundary entities.
        Must be provided if `grid` is specified.

    Returns
    -------
    discretization
        The |Discretization| that has been generated.
    data
        Dictionary with the following entries:

            :grid:           The generated |Grid|.
            :boundary_info:  The generated |BoundaryInfo|.


    """

    assert isinstance(analytical_problem, InstationaryAdvectionProblem)
    assert grid is None or boundary_info is not None
    assert boundary_info is None or grid is not None
    assert grid is None or domain_discretizer is None
    assert num_flux in ('lax_friedrichs', 'engquist_osher', 'simplified_engquist_osher')

    if grid is None:
        domain_discretizer = domain_discretizer or discretize_domain_default
        if diameter is None:
            grid, boundary_info = domain_discretizer(analytical_problem.domain)
        else:
            grid, boundary_info = domain_discretizer(analytical_problem.domain, diameter=diameter)

    p = analytical_problem

    if num_flux == 'lax_friedrichs':
        L = nonlinear_advection_lax_friedrichs_operator(grid, boundary_info, p.flux_function,
                                                        dirichlet_data=p.dirichlet_data, lxf_lambda=lxf_lambda)
    elif num_flux == 'engquist_osher':
        L = nonlinear_advection_engquist_osher_operator(grid, boundary_info, p.flux_function,
                                                        p.flux_function_derivative,
                                                        gausspoints=eo_gausspoints, intervals=eo_intervals,
                                                        dirichlet_data=p.dirichlet_data)
    else:
        L = nonlinear_advection_simplified_engquist_osher_operator(grid, boundary_info, p.flux_function,
                                                                   p.flux_function_derivative,
                                                                   dirichlet_data=p.dirichlet_data)
    F = None if p.rhs is None else L2ProductFunctional(grid, p.rhs)

    if p.initial_data.parametric:
        def initial_projection(U, mu):
            I = p.initial_data.evaluate(grid.quadrature_points(0, order=2), mu).squeeze()
            I = np.sum(I * grid.reference_element.quadrature(order=2)[1], axis=1) * (1. / grid.reference_element.volume)
            I = NumpyVectorArray(I, copy=False)
            return I.lincomb(U).data
        I = NumpyGenericOperator(initial_projection, dim_range=grid.size(0), linear=True,
                                 parameter_type=p.initial_data.parameter_type)
    else:
        I = p.initial_data.evaluate(grid.quadrature_points(0, order=2)).squeeze()
        I = np.sum(I * grid.reference_element.quadrature(order=2)[1], axis=1) * (1. / grid.reference_element.volume)
        I = NumpyVectorArray(I, copy=False)

    products = {'l2': L2Product(grid, boundary_info)}
    if grid.dim == 2:
        visualizer = PatchVisualizer(grid=grid, bounding_box=grid.bounding_box(), codim=0)
    elif grid.dim == 1:
        visualizer = Matplotlib1DVisualizer(grid, codim=0)
    else:
        visualizer = None
    parameter_space = p.parameter_space if hasattr(p, 'parameter_space') else None
    time_stepper = ExplicitEulerTimeStepper(nt=nt)

    discretization = InstationaryDiscretization(operator=L, rhs=F, initial_data=I, T=p.T, products=products,
                                                time_stepper=time_stepper,
                                                parameter_space=parameter_space, visualizer=visualizer,
                                                num_values=num_values, name='{}_FV'.format(p.name))

    return discretization, {'grid': grid, 'boundary_info': boundary_info}
Exemple #4
0
def discretize_stationary_fv(analytical_problem,
                             diameter=None,
                             domain_discretizer=None,
                             grid_type=None,
                             num_flux='lax_friedrichs',
                             lxf_lambda=1.,
                             eo_gausspoints=5,
                             eo_intervals=1,
                             grid=None,
                             boundary_info=None,
                             preassemble=True):
    """Discretizes an |StationaryProblem| using the finite volume method.

    Parameters
    ----------
    analytical_problem
        The |StationaryProblem| to discretize.
    diameter
        If not `None`, `diameter` is passed as an argument to the `domain_discretizer`.
    domain_discretizer
        Discretizer to be used for discretizing the analytical domain. This has
        to be a function `domain_discretizer(domain_description, diameter, ...)`.
        If `None`, |discretize_domain_default| is used.
    grid_type
        If not `None`, this parameter is forwarded to `domain_discretizer` to specify
        the type of the generated |Grid|.
    num_flux
        The numerical flux to use in the finite volume formulation. Allowed
        values are `'lax_friedrichs'`, `'engquist_osher'`, `'simplified_engquist_osher'`
        (see :mod:`pymor.operators.fv`).
    lxf_lambda
        The stabilization parameter for the Lax-Friedrichs numerical flux
        (ignored, if different flux is chosen).
    eo_gausspoints
        Number of Gauss points for the Engquist-Osher numerical flux
        (ignored, if different flux is chosen).
    eo_intervals
        Number of sub-intervals to use for integration when using Engquist-Osher
        numerical flux (ignored, if different flux is chosen).
    grid
        Instead of using a domain discretizer, the |Grid| can also be passed directly
        using this parameter.
    boundary_info
        A |BoundaryInfo| specifying the boundary types of the grid boundary entities.
        Must be provided if `grid` is specified.
    preassemble
        If `True`, preassemble all operators in the resulting |Discretization|.

    Returns
    -------
    discretization
        The |Discretization| that has been generated.
    data
        Dictionary with the following entries:

            :grid:           The generated |Grid|.
            :boundary_info:  The generated |BoundaryInfo|.
    """

    assert isinstance(analytical_problem, StationaryProblem)
    assert grid is None or boundary_info is not None
    assert boundary_info is None or grid is not None
    assert grid is None or domain_discretizer is None
    assert grid_type is None or grid is None

    p = analytical_problem

    if analytical_problem.robin_data is not None:
        raise NotImplementedError

    if grid is None:
        domain_discretizer = domain_discretizer or discretize_domain_default
        if grid_type:
            domain_discretizer = partial(domain_discretizer,
                                         grid_type=grid_type)
        if diameter is None:
            grid, boundary_info = domain_discretizer(analytical_problem.domain)
        else:
            grid, boundary_info = domain_discretizer(analytical_problem.domain,
                                                     diameter=diameter)

    L, L_coefficients = [], []
    F, F_coefficients = [], []

    if p.rhs is not None or p.neumann_data is not None:
        F += [
            L2ProductFunctional(grid,
                                p.rhs,
                                boundary_info=boundary_info,
                                neumann_data=p.neumann_data)
        ]
        F_coefficients += [1.]

    # diffusion part
    if isinstance(p.diffusion, LincombFunction):
        L += [
            DiffusionOperator(grid,
                              boundary_info,
                              diffusion_function=df,
                              name='diffusion_{}'.format(i))
            for i, df in enumerate(p.diffusion.functions)
        ]
        L_coefficients += p.diffusion.coefficients
        if p.dirichlet_data is not None:
            F += [
                L2ProductFunctional(grid,
                                    None,
                                    boundary_info=boundary_info,
                                    dirichlet_data=p.dirichlet_data,
                                    diffusion_function=df,
                                    name='dirichlet_{}'.format(i))
                for i, df in enumerate(p.diffusion.functions)
            ]
            F_coefficients += p.diffusion.coefficients

    elif p.diffusion is not None:
        L += [
            DiffusionOperator(grid,
                              boundary_info,
                              diffusion_function=p.diffusion,
                              name='diffusion')
        ]
        L_coefficients += [1.]
        if p.dirichlet_data is not None:
            F += [
                L2ProductFunctional(grid,
                                    None,
                                    boundary_info=boundary_info,
                                    dirichlet_data=p.dirichlet_data,
                                    diffusion_function=p.diffusion,
                                    name='dirichlet')
            ]
            F_coefficients += [1.]

    # advection part
    if isinstance(p.advection, LincombFunction):
        L += [
            LinearAdvectionLaxFriedrichs(grid,
                                         boundary_info,
                                         af,
                                         name='advection_{}'.format(i))
            for i, af in enumerate(p.advection.functions)
        ]
        L_coefficients += list(p.advection.coefficients)
    elif p.advection is not None:
        L += [
            LinearAdvectionLaxFriedrichs(grid,
                                         boundary_info,
                                         p.advection,
                                         name='advection')
        ]
        L_coefficients.append(1.)

    # nonlinear advection part
    if p.nonlinear_advection is not None:
        if num_flux == 'lax_friedrichs':
            L += [
                nonlinear_advection_lax_friedrichs_operator(
                    grid,
                    boundary_info,
                    p.nonlinear_advection,
                    dirichlet_data=p.dirichlet_data,
                    lxf_lambda=lxf_lambda)
            ]
        elif num_flux == 'upwind':
            L += [
                nonlinear_advection_upwind_operator(
                    grid,
                    boundary_info,
                    p.nonlinear_advection,
                    p.nonlinear_advection_derivative,
                    dirichlet_data=p.dirichlet_data)
            ]
        elif num_flux == 'engquist_osher':
            L += [
                nonlinear_advection_engquist_osher_operator(
                    grid,
                    boundary_info,
                    p.nonlinear_advection,
                    p.nonlinear_advection_derivative,
                    gausspoints=eo_gausspoints,
                    intervals=eo_intervals,
                    dirichlet_data=p.dirichlet_data)
            ]
        elif num_flux == 'simplified_engquist_osher':
            L += [
                nonlinear_advection_simplified_engquist_osher_operator(
                    grid,
                    boundary_info,
                    p.nonlinear_advection,
                    p.nonlinear_advection_derivative,
                    dirichlet_data=p.dirichlet_data)
            ]
        else:
            raise NotImplementedError
        L_coefficients.append(1.)

    # reaction part
    if isinstance(p.reaction, LincombFunction):
        raise NotImplementedError
    elif p.reaction is not None:
        L += [ReactionOperator(grid, p.reaction, name='reaction')]
        L_coefficients += [1.]

    # nonlinear reaction part
    if p.nonlinear_reaction is not None:
        L += [
            NonlinearReactionOperator(grid, p.nonlinear_reaction,
                                      p.nonlinear_reaction_derivative)
        ]
        L_coefficients += [1.]

    # system operator
    if len(L_coefficients) == 1 and L_coefficients[0] == 1.:
        L = L[0]
    else:
        L = LincombOperator(operators=L,
                            coefficients=L_coefficients,
                            name='elliptic_operator')

    # rhs
    if len(F_coefficients) == 0:
        F = ZeroOperator(L.range, NumpyVectorSpace(1))
    elif len(F_coefficients) == 1 and F_coefficients[0] == 1.:
        F = F[0]
    else:
        F = LincombOperator(operators=F,
                            coefficients=F_coefficients,
                            name='rhs')

    if grid.reference_element in (triangle, square):
        visualizer = PatchVisualizer(grid=grid,
                                     bounding_box=grid.bounding_box(),
                                     codim=0)
    elif grid.reference_element is line:
        visualizer = OnedVisualizer(grid=grid, codim=0)
    else:
        visualizer = None

    l2_product = L2Product(grid, name='l2')
    products = {'l2': l2_product}

    parameter_space = p.parameter_space if hasattr(p,
                                                   'parameter_space') else None

    discretization = StationaryDiscretization(L,
                                              F,
                                              products=products,
                                              visualizer=visualizer,
                                              parameter_space=parameter_space,
                                              name='{}_FV'.format(p.name))

    data = {'grid': grid, 'boundary_info': boundary_info}

    if preassemble:
        data['unassembled_discretization'] = discretization
        discretization = preassemble_(discretization)

    return discretization, data
Exemple #5
0
def discretize_stationary_fv(analytical_problem, diameter=None, domain_discretizer=None,
                             num_flux='lax_friedrichs', lxf_lambda=1., eo_gausspoints=5, eo_intervals=1,
                             grid=None, boundary_info=None):
    """Discretizes an |StationaryProblem| using the finite volume method.

    Parameters
    ----------
    analytical_problem
        The |StationaryProblem| to discretize.
    diameter
        If not `None`, `diameter` is passed as an argument to the `domain_discretizer`.
    domain_discretizer
        Discretizer to be used for discretizing the analytical domain. This has
        to be a function `domain_discretizer(domain_description, diameter, ...)`.
        If `None`, |discretize_domain_default| is used.
    num_flux
        The numerical flux to use in the finite volume formulation. Allowed
        values are `'lax_friedrichs'`, `'engquist_osher'`, `'simplified_engquist_osher'`
        (see :mod:`pymor.operators.fv`).
    lxf_lambda
        The stabilization parameter for the Lax-Friedrichs numerical flux
        (ignored, if different flux is chosen).
    eo_gausspoints
        Number of Gauss points for the Engquist-Osher numerical flux
        (ignored, if different flux is chosen).
    eo_intervals
        Number of sub-intervals to use for integration when using Engquist-Osher
        numerical flux (ignored, if different flux is chosen).
    grid
        Instead of using a domain discretizer, the |Grid| can also be passed directly
        using this parameter.
    boundary_info
        A |BoundaryInfo| specifying the boundary types of the grid boundary entities.
        Must be provided if `grid` is specified.

    Returns
    -------
    discretization
        The |Discretization| that has been generated.
    data
        Dictionary with the following entries:

            :grid:           The generated |Grid|.
            :boundary_info:  The generated |BoundaryInfo|.
    """

    assert isinstance(analytical_problem, StationaryProblem)
    assert grid is None or boundary_info is not None
    assert boundary_info is None or grid is not None
    assert grid is None or domain_discretizer is None

    p = analytical_problem

    if analytical_problem.robin_data is not None:
        raise NotImplementedError

    if grid is None:
        domain_discretizer = domain_discretizer or discretize_domain_default
        if diameter is None:
            grid, boundary_info = domain_discretizer(analytical_problem.domain)
        else:
            grid, boundary_info = domain_discretizer(analytical_problem.domain, diameter=diameter)

    L, L_coefficients = [], []
    F, F_coefficients = [], []

    if p.rhs is not None or p.neumann_data is not None:
        F += [L2ProductFunctional(grid, p.rhs, boundary_info=boundary_info, neumann_data=p.neumann_data)]
        F_coefficients += [1.]

    # diffusion part
    if isinstance(p.diffusion, LincombFunction):
        L += [DiffusionOperator(grid, boundary_info, diffusion_function=df, name='diffusion_{}'.format(i))
              for i, df in enumerate(p.diffusion.functions)]
        L_coefficients += p.diffusion.coefficients
        if p.dirichlet_data is not None:
            F += [L2ProductFunctional(grid, None, boundary_info=boundary_info, dirichlet_data=p.dirichlet_data,
                                      diffusion_function=df, name='dirichlet_{}'.format(i))
                  for i, df in enumerate(p.diffusion.functions)]
            F_coefficients += p.diffusion.coefficients

    elif p.diffusion is not None:
        L += [DiffusionOperator(grid, boundary_info, diffusion_function=p.diffusion, name='diffusion')]
        L_coefficients += [1.]
        if p.dirichlet_data is not None:
            F += [L2ProductFunctional(grid, None, boundary_info=boundary_info, dirichlet_data=p.dirichlet_data,
                                      diffusion_function=p.diffusion, name='dirichlet')]
            F_coefficients += [1.]

    # advection part
    if isinstance(p.advection, LincombFunction):
        L += [LinearAdvectionLaxFriedrichs(grid, boundary_info, af, name='advection_{}'.format(i))
              for i, af in enumerate(p.advection.functions)]
        L_coefficients += list(p.advection.coefficients)
    elif p.advection is not None:
        L += [LinearAdvectionLaxFriedrichs(grid, boundary_info, p.advection, name='advection')]
        L_coefficients.append(1.)

    # nonlinear advection part
    if p.nonlinear_advection is not None:
        if num_flux == 'lax_friedrichs':
            L += [nonlinear_advection_lax_friedrichs_operator(grid, boundary_info, p.nonlinear_advection,
                                                              dirichlet_data=p.dirichlet_data, lxf_lambda=lxf_lambda)]
        elif num_flux == 'upwind':
            L += [nonlinear_advection_upwind_operator(grid, boundary_info, p.nonlinear_advection,
                                                      p.nonlinear_advection_derivative,
                                                      dirichlet_data=p.dirichlet_data)]
        elif num_flux == 'engquist_osher':
            L += [nonlinear_advection_engquist_osher_operator(grid, boundary_info, p.nonlinear_advection,
                                                              p.nonlinear_advection_derivative,
                                                              gausspoints=eo_gausspoints, intervals=eo_intervals,
                                                              dirichlet_data=p.dirichlet_data)]
        elif num_flux == 'simplified_engquist_osher':
            L += [nonlinear_advection_simplified_engquist_osher_operator(grid, boundary_info, p.nonlinear_advection,
                                                                         p.nonlinear_advection_derivative,
                                                                         dirichlet_data=p.dirichlet_data)]
        else:
            raise NotImplementedError
        L_coefficients.append(1.)

    # reaction part
    if isinstance(p.reaction, LincombFunction):
        raise NotImplementedError
    elif p.reaction is not None:
        L += [ReactionOperator(grid, p.reaction, name='reaction')]
        L_coefficients += [1.]

    # nonlinear reaction part
    if p.nonlinear_reaction is not None:
        L += [NonlinearReactionOperator(grid, p.nonlinear_reaction, p.nonlinear_reaction_derivative)]
        L_coefficients += [1.]

    # system operator
    if len(L_coefficients) == 1 and L_coefficients[0] == 1.:
        L = L[0]
    else:
        L = LincombOperator(operators=L, coefficients=L_coefficients, name='elliptic_operator')

    # rhs
    if len(F_coefficients) == 0:
        F = ZeroOperator(L.range, NumpyVectorSpace(1))
    elif len(F_coefficients) == 1 and F_coefficients[0] == 1.:
        F = F[0]
    else:
        F = LincombOperator(operators=F, coefficients=F_coefficients, name='rhs')

    if grid.reference_element in (triangle, square):
        visualizer = PatchVisualizer(grid=grid, bounding_box=grid.bounding_box(), codim=0)
    elif grid.reference_element is line:
        visualizer = Matplotlib1DVisualizer(grid=grid, codim=0)
    else:
        visualizer = None

    l2_product = L2Product(grid, name='l2')
    products = {'l2': l2_product}

    parameter_space = p.parameter_space if hasattr(p, 'parameter_space') else None

    discretization = StationaryDiscretization(L, F, products=products, visualizer=visualizer,
                                              parameter_space=parameter_space, name='{}_FV'.format(p.name))

    return discretization, {'grid': grid, 'boundary_info': boundary_info}
def discretize_nonlinear_instationary_advection_diffusion_fv(
    analytical_problem,
    diameter=None,
    nt=100,
    num_flux="lax_friedrichs",
    lxf_lambda=1.0,
    domain_discretizer=None,
    grid=None,
    boundary_info=None,
):

    assert isinstance(analytical_problem, InstationaryAdvectionDiffusionProblem)
    assert grid is None or boundary_info is not None
    assert boundary_info is None or grid is not None
    assert grid is None or domain_discretizer is None
    assert num_flux in ("lax_friedrichs", "engquist_osher")

    if grid is None:
        domain_discretizer = domain_discretizer or discretize_domain_default
        if diameter is None:
            grid, boundary_info = domain_discretizer(analytical_problem.domain)
        else:
            grid, boundary_info = domain_discretizer(analytical_problem.domain, diameter=diameter)

    p = analytical_problem

    if num_flux == "lax_friedrichs":
        L = nonlinear_advection_lax_friedrichs_operator(
            grid, boundary_info, p.flux_function, dirichlet_data=p.dirichlet_data, lxf_lambda=lxf_lambda
        )
    else:
        L = nonlinear_advection_engquist_osher_operator(
            grid, boundary_info, p.flux_function, p.flux_function_derivative, dirichlet_data=p.dirichlet_data
        )

    I = p.initial_data.evaluate(grid.quadrature_points(0, order=2)).squeeze()
    I = np.sum(I * grid.reference_element.quadrature(order=2)[1], axis=1) * (1.0 / grid.reference_element.volume)
    I = NumpyVectorArray(I, copy=False)

    D = DiffusionOperator(grid, boundary_info, p.diffusion)
    diffusion_functional = GenericParameterFunctional(
        lambda mu: mu["diffusion"], parameter_type={"diffusion": 0}, name="diffusion"
    )
    D = LincombOperator(operators=[D], coefficients=[diffusion_functional], name="diffusion")

    F = None if p.rhs is None else L2ProductFunctional(grid, p.rhs, diffusion_function=p.diffusion)
    F = LincombOperator(operators=[F], coefficients=[diffusion_functional], name="rhs")

    products = {"l2": L2Product(grid, boundary_info)}
    visualizer = PatchVisualizer(grid=grid, bounding_box=grid.domain, codim=0)
    parameter_space = p.parameter_space if hasattr(p, "parameter_space") else None

    discretization = InstationaryImexDiscretization(
        explicit_operator=L,
        implicit_operator=D,
        rhs=F,
        initial_data=I,
        T=p.T,
        nt=nt,
        products=products,
        parameter_space=parameter_space,
        visualizer=visualizer,
        name="{}_FV".format(p.name),
    )

    return discretization, {"grid": grid, "boundary_info": boundary_info}