// Copyright (C) 2011 by the BEM++ Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "../type_template.hpp"
#include "../check_arrays_are_close.hpp"

#include "bempp/common/config_ahmed.hpp"
#include "assembly/blocked_boundary_operator.hpp"
#include "assembly/blocked_operator_structure.hpp"
#include "assembly/context.hpp"
#include "assembly/discrete_boundary_operator.hpp"
#include "assembly/identity_operator.hpp"
#include "assembly/laplace_3d_single_layer_boundary_operator.hpp"
#include "assembly/modified_helmholtz_3d_single_layer_boundary_operator.hpp"
#include "assembly/numerical_quadrature_strategy.hpp"
#include "grid/grid_factory.hpp"
#include "grid/grid.hpp"
#include "space/piecewise_constant_scalar_space.hpp"
#include "space/piecewise_linear_continuous_scalar_space.hpp"

#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/type_traits/is_complex.hpp>

using namespace Bempp;

// Tests

BOOST_AUTO_TEST_SUITE(BlockedBoundaryOperator)

BOOST_AUTO_TEST_CASE_TEMPLATE(blocked_boundary_operator_produces_correct_weak_form_for_1x1_operator,
                              ValueType, result_types)
{
    // space | PL
    // ------+---
    // PC    |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid = GridFactory::importGmshGrid(
        params, "meshes/cube-12-reoriented.msh", false /* verbose */);

    shared_ptr<Space<BFT> > pwiseConstants(
        new PiecewiseConstantScalarSpace<BFT>(grid));
    shared_ptr<Space<BFT> > pwiseLinears(
        new PiecewiseLinearContinuousScalarSpace<BFT>(grid));

    AssemblyOptions assemblyOptions;
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pwiseLinears, pwiseLinears, pwiseConstants);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> nonblockedWeakForm = op00.weakForm()->asMatrix();
    arma::Mat<RT> blockedWeakForm = blockedOp.weakForm()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, blockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(blocked_boundary_operator_produces_correct_weak_form_for_2x1_operator,
                              ValueType, result_types)
{
    // space  | PL0
    // -------+---
    // PC0    |  V
    // PL1    |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-2.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));

    AssemblyOptions assemblyOptions;
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pl0, pc0);
    BoundaryOperator<BFT, RT> op10 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pc1, pl1);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(1, 0, op10);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm = arma::join_cols(mat00, mat10);
    arma::Mat<RT> blockedWeakForm = blockedOp.weakForm()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, blockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(blocked_boundary_operator_produces_correct_weak_form_for_2x3_operator,
                              ValueType, result_types)
{
    // space  | PL0 | PC1 | PL2
    // -------+-----+-----+----
    // PC0    |  V  |  V  |  V
    // PL2    |  V  |  V  |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-2.msh",
                false /* verbose */);
    shared_ptr<Grid> grid2 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-4.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pc2(new PiecewiseConstantScalarSpace<BFT>(grid2));
    shared_ptr<Space<BFT> > pl2(new PiecewiseLinearContinuousScalarSpace<BFT>(grid2));

    AssemblyOptions assemblyOptions;
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pl0, pc0);
    BoundaryOperator<BFT, RT> op01 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pl0, pc0);
    BoundaryOperator<BFT, RT> op02 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pl0, pc0);
    BoundaryOperator<BFT, RT> op10 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pc2, pl2);
    BoundaryOperator<BFT, RT> op11 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pc2, pl2);
    BoundaryOperator<BFT, RT> op12 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pc2, pl2);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(0, 1, op01);
    structure.setBlock(0, 2, op02);
    structure.setBlock(1, 0, op10);
    structure.setBlock(1, 1, op11);
    structure.setBlock(1, 2, op12);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat01 = op01.weakForm()->asMatrix();
    arma::Mat<RT> mat02 = op02.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> mat11 = op11.weakForm()->asMatrix();
    arma::Mat<RT> mat12 = op12.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm =
        arma::join_rows(arma::join_rows(arma::join_cols(mat00, mat10),
                                        arma::join_cols(mat01, mat11)),
                        arma::join_cols(mat02, mat12));
    arma::Mat<RT> blockedWeakForm = blockedOp.weakForm()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, blockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(blocked_boundary_operator_produces_correct_weak_form_for_2x3_operator_with_empty_blocks,
                              ValueType, result_types)
{
    // space  | PL0 | PC1 | PL2
    // -------+-----+-----+----
    // PC0    |  0  |  0  |  V
    // PL2    |  V  |  V  |  0

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-2.msh",
                false /* verbose */);
    shared_ptr<Grid> grid2 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-4.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pc2(new PiecewiseConstantScalarSpace<BFT>(grid2));
    shared_ptr<Space<BFT> > pl2(new PiecewiseLinearContinuousScalarSpace<BFT>(grid2));

    AssemblyOptions assemblyOptions;
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pl0, pc0);
    BoundaryOperator<BFT, RT> op01 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pl0, pc0);
    BoundaryOperator<BFT, RT> op02 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pl0, pc0);
    BoundaryOperator<BFT, RT> op10 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pc2, pl2);
    BoundaryOperator<BFT, RT> op11 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pc2, pl2);
    BoundaryOperator<BFT, RT> op12 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pc2, pl2);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 2, op02);
    structure.setBlock(1, 0, op10);
    structure.setBlock(1, 1, op11);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = 0. * op00.weakForm()->asMatrix();
    arma::Mat<RT> mat01 = 0. * op01.weakForm()->asMatrix();
    arma::Mat<RT> mat02 = op02.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> mat11 = op11.weakForm()->asMatrix();
    arma::Mat<RT> mat12 = 0. * op12.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm =
        arma::join_rows(arma::join_rows(arma::join_cols(mat00, mat10),
                                        arma::join_cols(mat01, mat11)),
                        arma::join_cols(mat02, mat12));
    arma::Mat<RT> blockedWeakForm = blockedOp.weakForm()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, blockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

#ifdef WITH_AHMED

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_1x1_operator,
                              ValueType, result_types)
{
    // space | PL
    // ------+---
    // PC    |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid = GridFactory::importGmshGrid(
        params, "meshes/cube-12-reoriented.msh", false /* verbose */);

    shared_ptr<Space<BFT> > pwiseConstants(
        new PiecewiseConstantScalarSpace<BFT>(grid));
    shared_ptr<Space<BFT> > pwiseLinears(
        new PiecewiseLinearContinuousScalarSpace<BFT>(grid));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pwiseLinears, pwiseLinears, pwiseConstants);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> nonblockedWeakForm = op00.weakForm()->asMatrix();
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x1_operator,
                              ValueType, result_types)
{
    // space  | PL0
    // -------+---
    // PC0    |  V
    // PL1    |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-2.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pl0, pc0);
    BoundaryOperator<BFT, RT> op10 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pc1, pl1);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(1, 0, op10);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm = arma::join_cols(mat00, mat10);
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x3_operator,
                              ValueType, result_types)
{
    // space  | PL0 | PC1 | PL2
    // -------+-----+-----+----
    // PC0    |  V  |  V  |  V
    // PL2    |  V  |  V  |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-2.msh",
                false /* verbose */);
    shared_ptr<Grid> grid2 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-4.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pc2(new PiecewiseConstantScalarSpace<BFT>(grid2));
    shared_ptr<Space<BFT> > pl2(new PiecewiseLinearContinuousScalarSpace<BFT>(grid2));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pl0, pc0);
    BoundaryOperator<BFT, RT> op01 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pl0, pc0);
    BoundaryOperator<BFT, RT> op02 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pl0, pc0);
    BoundaryOperator<BFT, RT> op10 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pc2, pl2);
    BoundaryOperator<BFT, RT> op11 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pc2, pl2);
    BoundaryOperator<BFT, RT> op12 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pc2, pl2);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(0, 1, op01);
    structure.setBlock(0, 2, op02);
    structure.setBlock(1, 0, op10);
    structure.setBlock(1, 1, op11);
    structure.setBlock(1, 2, op12);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat01 = op01.weakForm()->asMatrix();
    arma::Mat<RT> mat02 = op02.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> mat11 = op11.weakForm()->asMatrix();
    arma::Mat<RT> mat12 = op12.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm =
        arma::join_rows(arma::join_rows(arma::join_cols(mat00, mat10),
                                        arma::join_cols(mat01, mat11)),
                        arma::join_cols(mat02, mat12));
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x3_operator_with_empty_blocks,
                              ValueType, result_types)
{
    // space  | PL0 | PC1 | PL2
    // -------+-----+-----+----
    // PC0    |  0  |  0  |  V
    // PL2    |  V  |  V  |  0

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-2.msh",
                false /* verbose */);
    shared_ptr<Grid> grid2 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-4.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pc2(new PiecewiseConstantScalarSpace<BFT>(grid2));
    shared_ptr<Space<BFT> > pl2(new PiecewiseLinearContinuousScalarSpace<BFT>(grid2));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pl0, pc0);
    BoundaryOperator<BFT, RT> op01 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pl0, pc0);
    BoundaryOperator<BFT, RT> op02 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pl0, pc0);
    BoundaryOperator<BFT, RT> op10 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pc2, pl2);
    BoundaryOperator<BFT, RT> op11 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pc2, pl2);
    BoundaryOperator<BFT, RT> op12 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pc2, pl2);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 2, op02);
    structure.setBlock(1, 0, op10);
    structure.setBlock(1, 1, op11);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = 0. * op00.weakForm()->asMatrix();
    arma::Mat<RT> mat01 = 0. * op01.weakForm()->asMatrix();
    arma::Mat<RT> mat02 = op02.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> mat11 = op11.weakForm()->asMatrix();
    arma::Mat<RT> mat12 = 0. * op12.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm =
        arma::join_rows(arma::join_rows(arma::join_cols(mat00, mat10),
                                        arma::join_cols(mat01, mat11)),
                        arma::join_cols(mat02, mat12));
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x1_operator_containing_sparse_block,
                              ValueType, result_types)
{
    // space  | PL0
    // -------+---
    // PC0    |  V
    // PL1    |  I

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pl0, pc0);
    BoundaryOperator<BFT, RT> op10 = identityOperator<BFT, RT>(
        context, pl0, pc0, pl0);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(1, 0, op10);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm = arma::join_cols(mat00, mat10);
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_1x1_operator_with_interleaving,
                              ValueType, result_types)
{
    // space | PL
    // ------+---
    // PC    |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid = GridFactory::importGmshGrid(
        params, "meshes/cube-12-reoriented.msh", false /* verbose */);

    shared_ptr<Space<BFT> > pwiseConstants(
        new PiecewiseConstantScalarSpace<BFT>(grid));
    shared_ptr<Space<BFT> > pwiseLinears(
        new PiecewiseLinearContinuousScalarSpace<BFT>(grid));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pwiseLinears, pwiseLinears, pwiseConstants);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> nonblockedWeakForm = op00.weakForm()->asMatrix();
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x1_operator_with_interleaving,
                              ValueType, result_types)
{
    // space  | PL0
    // -------+---
    // PC0    |  V
    // PL1    |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-2.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pl0, pc0);
    BoundaryOperator<BFT, RT> op10 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pc1, pl1);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(1, 0, op10);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm = arma::join_cols(mat00, mat10);
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x3_operator_with_interleaving,
                              ValueType, result_types)
{
    // space  | PL0 | PC1 | PL2
    // -------+-----+-----+----
    // PC0    |  V  |  V  |  V
    // PL2    |  V  |  V  |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-2.msh",
                false /* verbose */);
    shared_ptr<Grid> grid2 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-4.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pc2(new PiecewiseConstantScalarSpace<BFT>(grid2));
    shared_ptr<Space<BFT> > pl2(new PiecewiseLinearContinuousScalarSpace<BFT>(grid2));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pl0, pc0);
    BoundaryOperator<BFT, RT> op01 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pl0, pc0);
    BoundaryOperator<BFT, RT> op02 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pl0, pc0);
    BoundaryOperator<BFT, RT> op10 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pc2, pl2);
    BoundaryOperator<BFT, RT> op11 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pc2, pl2);
    BoundaryOperator<BFT, RT> op12 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pc2, pl2);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(0, 1, op01);
    structure.setBlock(0, 2, op02);
    structure.setBlock(1, 0, op10);
    structure.setBlock(1, 1, op11);
    structure.setBlock(1, 2, op12);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat01 = op01.weakForm()->asMatrix();
    arma::Mat<RT> mat02 = op02.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> mat11 = op11.weakForm()->asMatrix();
    arma::Mat<RT> mat12 = op12.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm =
        arma::join_rows(arma::join_rows(arma::join_cols(mat00, mat10),
                                        arma::join_cols(mat01, mat11)),
                        arma::join_cols(mat02, mat12));
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x3_operator_with_empty_blocks_with_interleaving,
                              ValueType, result_types)
{
    // space  | PL0 | PC1 | PL2
    // -------+-----+-----+----
    // PC0    |  0  |  0  |  V
    // PL2    |  V  |  V  |  0

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-2.msh",
                false /* verbose */);
    shared_ptr<Grid> grid2 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented-shifted-on-x-by-4.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pc2(new PiecewiseConstantScalarSpace<BFT>(grid2));
    shared_ptr<Space<BFT> > pl2(new PiecewiseLinearContinuousScalarSpace<BFT>(grid2));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pl0, pc0);
    BoundaryOperator<BFT, RT> op01 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pl0, pc0);
    BoundaryOperator<BFT, RT> op02 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pl0, pc0);
    BoundaryOperator<BFT, RT> op10 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pc2, pl2);
    BoundaryOperator<BFT, RT> op11 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pc1, pc2, pl2);
    BoundaryOperator<BFT, RT> op12 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl2, pc2, pl2);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 2, op02);
    structure.setBlock(1, 0, op10);
    structure.setBlock(1, 1, op11);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = 0. * op00.weakForm()->asMatrix();
    arma::Mat<RT> mat01 = 0. * op01.weakForm()->asMatrix();
    arma::Mat<RT> mat02 = op02.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> mat11 = op11.weakForm()->asMatrix();
    arma::Mat<RT> mat12 = 0. * op12.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm =
        arma::join_rows(arma::join_rows(arma::join_cols(mat00, mat10),
                                        arma::join_cols(mat01, mat11)),
                        arma::join_cols(mat02, mat12));
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x1_operator_containing_sparse_block_with_interleaving,
                              ValueType, result_types)
{
    // space  | PL0
    // -------+---
    // PC0    |  V
    // PL1    |  I

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/cube-12-reoriented.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pl0, pl0, pc0);
    BoundaryOperator<BFT, RT> op10 = identityOperator<BFT, RT>(
        context, pl0, pc0, pl0);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(1, 0, op10);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm = arma::join_cols(mat00, mat10);
    arma::Mat<RT> acaBlockedWeakForm =
        blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
            -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_1x1_operator_with_small_blocks_and_interleaving,
                              ValueType, result_types)
{
    // space | PL
    // ------+---
    // PC    |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid = GridFactory::importGmshGrid(
        params, "meshes/sphere-ico-1.msh", false /* verbose */);

    shared_ptr<Space<BFT> > pwiseConstants(
        new PiecewiseConstantScalarSpace<BFT>(grid));
    shared_ptr<Space<BFT> > pwiseLinears(
        new PiecewiseLinearContinuousScalarSpace<BFT>(grid));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.maximumBlockSize = 4;
    acaOptions.minimumBlockSize = 2;
    acaOptions.outputPostscript = true;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pwiseLinears, pwiseLinears, pwiseConstants);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> nonblockedWeakForm = op00.weakForm()->asMatrix();
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x1_operator_with_with_small_blocks_and_interleaving,
                              ValueType, result_types)
{
    // space  | PL0
    // -------+---
    // PC0    |  V
    // PL1    |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-1.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-1-shifted-by-3.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.maximumBlockSize = 4;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pl0, pc0, 0.1);
    BoundaryOperator<BFT, RT> op10 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pc1, pl1, 0.2);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(1, 0, op10);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm = arma::join_cols(mat00, mat10);
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x3_operator_with_with_small_blocks_and_interleaving,
                              ValueType, result_types)
{
    // space  | PL0 | PC1 | PL2
    // -------+-----+-----+----
    // PC0    |  V  |  V  |  V
    // PL2    |  V  |  V  |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-1.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-1-shifted-by-3.msh",
                false /* verbose */);
    shared_ptr<Grid> grid2 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-1-shifted-by-6.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pc2(new PiecewiseConstantScalarSpace<BFT>(grid2));
    shared_ptr<Space<BFT> > pl2(new PiecewiseLinearContinuousScalarSpace<BFT>(grid2));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.maximumBlockSize = 4;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pl0, pc0, 0.1);
    BoundaryOperator<BFT, RT> op01 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pc1, pl0, pc0, 0.2);
    BoundaryOperator<BFT, RT> op02 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl2, pl0, pc0, 0.3);
    BoundaryOperator<BFT, RT> op10 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pc2, pl2, 0.4);
    BoundaryOperator<BFT, RT> op11 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pc1, pc2, pl2, 0.5);
    BoundaryOperator<BFT, RT> op12 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl2, pc2, pl2, 0.6);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(0, 1, op01);
    structure.setBlock(0, 2, op02);
    structure.setBlock(1, 0, op10);
    structure.setBlock(1, 1, op11);
    structure.setBlock(1, 2, op12);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat01 = op01.weakForm()->asMatrix();
    arma::Mat<RT> mat02 = op02.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> mat11 = op11.weakForm()->asMatrix();
    arma::Mat<RT> mat12 = op12.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm =
        arma::join_rows(arma::join_rows(arma::join_cols(mat00, mat10),
                                        arma::join_cols(mat01, mat11)),
                        arma::join_cols(mat02, mat12));
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x3_operator_with_empty_blocks_with_with_small_blocks_and_interleaving,
                              ValueType, result_types)
{
    // space  | PL0 | PC1 | PL2
    // -------+-----+-----+----
    // PC0    |  0  |  0  |  V
    // PL2    |  V  |  V  |  0

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-1.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-1-shifted-by-3.msh",
                false /* verbose */);
    shared_ptr<Grid> grid2 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-1-shifted-by-6.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pc2(new PiecewiseConstantScalarSpace<BFT>(grid2));
    shared_ptr<Space<BFT> > pl2(new PiecewiseLinearContinuousScalarSpace<BFT>(grid2));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.maximumBlockSize = 4;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pl0, pc0, 0.1);
    BoundaryOperator<BFT, RT> op01 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pc1, pl0, pc0, 0.2);
    BoundaryOperator<BFT, RT> op02 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl2, pl0, pc0, 0.3);
    BoundaryOperator<BFT, RT> op10 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pc2, pl2, 0.4);
    BoundaryOperator<BFT, RT> op11 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pc1, pc2, pl2, 0.5);
    BoundaryOperator<BFT, RT> op12 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl2, pc2, pl2, 0.6);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 2, op02);
    structure.setBlock(1, 0, op10);
    structure.setBlock(1, 1, op11);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = 0. * op00.weakForm()->asMatrix();
    arma::Mat<RT> mat01 = 0. * op01.weakForm()->asMatrix();
    arma::Mat<RT> mat02 = op02.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> mat11 = op11.weakForm()->asMatrix();
    arma::Mat<RT> mat12 = 0. * op12.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm =
        arma::join_rows(arma::join_rows(arma::join_cols(mat00, mat10),
                                        arma::join_cols(mat01, mat11)),
                        arma::join_cols(mat02, mat12));
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_1x1_operator_with_low_rank_blocks_and_interleaving,
                              ValueType, result_types)
{
    // space | PL
    // ------+---
    // PC    |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid = GridFactory::importGmshGrid(
        params, "meshes/sphere-ico-2.msh", false /* verbose */);

    shared_ptr<Space<BFT> > pwiseConstants(
        new PiecewiseConstantScalarSpace<BFT>(grid));
    shared_ptr<Space<BFT> > pwiseLinears(
        new PiecewiseLinearContinuousScalarSpace<BFT>(grid));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    acaOptions.outputPostscript = true;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = laplace3dSingleLayerBoundaryOperator<BFT, RT>(
        context, pwiseLinears, pwiseLinears, pwiseConstants);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> nonblockedWeakForm = op00.weakForm()->asMatrix();
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x1_operator_with_low_rank_blocks_and_interleaving,
                              ValueType, result_types)
{
    // space  | PL0
    // -------+---
    // PC0    |  V
    // PL1    |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-2.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-2-shifted-by-3.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pl0, pc0, 0.1);
    BoundaryOperator<BFT, RT> op10 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pc1, pl1, 0.2);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(1, 0, op10);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm = arma::join_cols(mat00, mat10);
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x3_operator_with_low_rank_blocks_and_interleaving,
                              ValueType, result_types)
{
    // space  | PL0 | PC1 | PL2
    // -------+-----+-----+----
    // PC0    |  V  |  V  |  V
    // PL2    |  V  |  V  |  V

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-2.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-2-shifted-by-3.msh",
                false /* verbose */);
    shared_ptr<Grid> grid2 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-2-shifted-by-6.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pc2(new PiecewiseConstantScalarSpace<BFT>(grid2));
    shared_ptr<Space<BFT> > pl2(new PiecewiseLinearContinuousScalarSpace<BFT>(grid2));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pl0, pc0, 0.1);
    BoundaryOperator<BFT, RT> op01 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pc1, pl0, pc0, 0.2);
    BoundaryOperator<BFT, RT> op02 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl2, pl0, pc0, 0.3);
    BoundaryOperator<BFT, RT> op10 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pc2, pl2, 0.4);
    BoundaryOperator<BFT, RT> op11 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pc1, pc2, pl2, 0.5);
    BoundaryOperator<BFT, RT> op12 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl2, pc2, pl2, 0.6);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 0, op00);
    structure.setBlock(0, 1, op01);
    structure.setBlock(0, 2, op02);
    structure.setBlock(1, 0, op10);
    structure.setBlock(1, 1, op11);
    structure.setBlock(1, 2, op12);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = op00.weakForm()->asMatrix();
    arma::Mat<RT> mat01 = op01.weakForm()->asMatrix();
    arma::Mat<RT> mat02 = op02.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> mat11 = op11.weakForm()->asMatrix();
    arma::Mat<RT> mat12 = op12.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm =
        arma::join_rows(arma::join_rows(arma::join_cols(mat00, mat10),
                                        arma::join_cols(mat01, mat11)),
                        arma::join_cols(mat02, mat12));
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(asDiscreteAcaBoundaryOperator_produces_correct_weak_form_for_2x3_operator_with_low_rank_blocks_with_small_blocks_and_interleaving,
                              ValueType, result_types)
{
    // space  | PL0 | PC1 | PL2
    // -------+-----+-----+----
    // PC0    |  0  |  0  |  V
    // PL2    |  V  |  V  |  0

    typedef ValueType RT;
    typedef typename ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid0 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-2.msh",
                false /* verbose */);
    shared_ptr<Grid> grid1 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-2-shifted-by-3.msh",
                false /* verbose */);
    shared_ptr<Grid> grid2 = GridFactory::importGmshGrid(
                params, "meshes/sphere-ico-2-shifted-by-6.msh",
                false /* verbose */);

    shared_ptr<Space<BFT> > pc0(new PiecewiseConstantScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pl0(new PiecewiseLinearContinuousScalarSpace<BFT>(grid0));
    shared_ptr<Space<BFT> > pc1(new PiecewiseConstantScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pl1(new PiecewiseLinearContinuousScalarSpace<BFT>(grid1));
    shared_ptr<Space<BFT> > pc2(new PiecewiseConstantScalarSpace<BFT>(grid2));
    shared_ptr<Space<BFT> > pl2(new PiecewiseLinearContinuousScalarSpace<BFT>(grid2));

    AssemblyOptions assemblyOptions;
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 2;
    assemblyOptions.switchToAcaMode(acaOptions);
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
        new NumericalQuadratureStrategy<BFT, RT>);
    shared_ptr<Context<BFT, RT> > context(
        new Context<BFT, RT>(quadStrategy, assemblyOptions));

    BoundaryOperator<BFT, RT> op00 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pl0, pc0, 0.1);
    BoundaryOperator<BFT, RT> op01 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pc1, pl0, pc0, 0.2);
    BoundaryOperator<BFT, RT> op02 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl2, pl0, pc0, 0.3);
    BoundaryOperator<BFT, RT> op10 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl0, pc2, pl2, 0.4);
    BoundaryOperator<BFT, RT> op11 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pc1, pc2, pl2, 0.5);
    BoundaryOperator<BFT, RT> op12 = modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, BFT, RT>(
        context, pl2, pc2, pl2, 0.6);

    BlockedOperatorStructure<BFT, RT> structure;
    structure.setBlock(0, 2, op02);
    structure.setBlock(1, 0, op10);
    structure.setBlock(1, 1, op11);
    Bempp::BlockedBoundaryOperator<BFT, RT> blockedOp(structure);

    arma::Mat<RT> mat00 = 0. * op00.weakForm()->asMatrix();
    arma::Mat<RT> mat01 = 0. * op01.weakForm()->asMatrix();
    arma::Mat<RT> mat02 = op02.weakForm()->asMatrix();
    arma::Mat<RT> mat10 = op10.weakForm()->asMatrix();
    arma::Mat<RT> mat11 = op11.weakForm()->asMatrix();
    arma::Mat<RT> mat12 = 0. * op12.weakForm()->asMatrix();
    arma::Mat<RT> nonblockedWeakForm =
        arma::join_rows(arma::join_rows(arma::join_cols(mat00, mat10),
                                        arma::join_cols(mat01, mat11)),
                        arma::join_cols(mat02, mat12));
    arma::Mat<RT> acaBlockedWeakForm =
            blockedOp.weakForm()->asDiscreteAcaBoundaryOperator(
                -1, -1, true /*interleave*/)->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    nonblockedWeakForm, acaBlockedWeakForm,
                    10. * std::numeric_limits<RealType>::epsilon()));
}

#endif // WITH_AHMED
BOOST_AUTO_TEST_SUITE_END()


#pragma once
#include "utils/math_utils.h"
#include "libs/sources_handling/source_data.h"
#include "libs/sources_handling/input_data.h"
#include <boost/range/algorithm/copy.hpp>
#include <boost/iterator/function_input_iterator.hpp>
#include <boost/range/algorithm/remove_if.hpp>
#include <vector>
#include <iostream>

struct reverse_generator
{
    using result_type = input_data;

    reverse_generator(const double* s, const double* t) :
                start(s), end(t), index(0)
    {
    }

    result_type operator () ()
    {
        if (start != end) {
            return result_type{*start++, index++};
        } else {
            return result_type{0, 0};
        }
    }

    const double* start = nullptr;
    const double* end = nullptr;
    unsigned int index = 0;
};

inline source_data
from_double_array(const double* from, const double* to,
        const std::string& name)
{
    reverse_generator gen{from, to};
    return source_data{name,
                    boost::make_function_input_iterator(gen, 0u),
                    boost::make_function_input_iterator(gen, (unsigned int)(to-from))
            };

}


/*
 * Copyright Nick Thompson, 2020
 * Use, modification and distribution are subject to the
 * Boost Software License, Version 1.0. (See accompanying file
 * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_MATH_INTERPOLATORS_QUINTIC_HERMITE_HPP
#define BOOST_MATH_INTERPOLATORS_QUINTIC_HERMITE_HPP
#include <algorithm>
#include <stdexcept>
#include <memory>
#include <boost/math/interpolators/detail/quintic_hermite_detail.hpp>

namespace boost {
namespace math {
namespace interpolators {

template<class RandomAccessContainer>
class quintic_hermite {
public:
    using Real = typename RandomAccessContainer::value_type;
    quintic_hermite(RandomAccessContainer && x, RandomAccessContainer && y, RandomAccessContainer && dydx, RandomAccessContainer && d2ydx2)
     : impl_(std::make_shared<detail::quintic_hermite_detail<RandomAccessContainer>>(std::move(x), std::move(y), std::move(dydx), std::move(d2ydx2)))
    {}

    Real operator()(Real x) const
    {
        return impl_->operator()(x);
    }

    Real prime(Real x) const
    {
        return impl_->prime(x);
    }

    Real double_prime(Real x) const
    {
        return impl_->double_prime(x);
    }

    friend std::ostream& operator<<(std::ostream & os, const quintic_hermite & m)
    {
        os << *m.impl_;
        return os;
    }

    void push_back(Real x, Real y, Real dydx, Real d2ydx2)
    {
        impl_->push_back(x, y, dydx, d2ydx2);
    }

    int64_t bytes() const
    {
        return impl_->bytes() + sizeof(impl_);
    }

    std::pair<Real, Real> domain() const
    {
        return impl_->domain();
    }

private:
    std::shared_ptr<detail::quintic_hermite_detail<RandomAccessContainer>> impl_;
};

template<class RandomAccessContainer>
class cardinal_quintic_hermite {
public:
    using Real = typename RandomAccessContainer::value_type;
    cardinal_quintic_hermite(RandomAccessContainer && y, RandomAccessContainer && dydx, RandomAccessContainer && d2ydx2, Real x0, Real dx)
     : impl_(std::make_shared<detail::cardinal_quintic_hermite_detail<RandomAccessContainer>>(std::move(y), std::move(dydx), std::move(d2ydx2), x0, dx))
    {}

    inline Real operator()(Real x) const {
        return impl_->operator()(x);
    }

    inline Real prime(Real x) const {
        return impl_->prime(x);
    }

    inline Real double_prime(Real x) const
    {
        return impl_->double_prime(x);
    }

    int64_t bytes() const
    {
        return impl_->bytes() + sizeof(impl_);
    }

    std::pair<Real, Real> domain() const
    {
        return impl_->domain();
    }

private:
    std::shared_ptr<detail::cardinal_quintic_hermite_detail<RandomAccessContainer>> impl_;
};

template<class RandomAccessContainer>
class cardinal_quintic_hermite_aos {
public:
    using Point = typename RandomAccessContainer::value_type;
    using Real = typename Point::value_type;
    cardinal_quintic_hermite_aos(RandomAccessContainer && data, Real x0, Real dx)
     : impl_(std::make_shared<detail::cardinal_quintic_hermite_detail_aos<RandomAccessContainer>>(std::move(data), x0, dx))
    {}

    inline Real operator()(Real x) const
    {
        return impl_->operator()(x);
    }

    inline Real prime(Real x) const
    {
        return impl_->prime(x);
    }

    inline Real double_prime(Real x) const
    {
        return impl_->double_prime(x);
    }

    int64_t bytes() const
    {
        return impl_->bytes() + sizeof(impl_);
    }

    std::pair<Real, Real> domain() const
    {
        return impl_->domain();
    }
private:
    std::shared_ptr<detail::cardinal_quintic_hermite_detail_aos<RandomAccessContainer>> impl_;
};

}
}
}
#endif