BOOST_AUTO_TEST_CASE_TEMPLATE(symmetric_matches_nonsymmetric_in_aca_mode,
                              ValueType, result_types)
{
    typedef ValueType RT;
    typedef typename Fiber::ScalarTraits<ValueType>::RealType RealType;
    typedef RealType BFT;

    if (boost::is_same<RT, std::complex<float> >::value) {
        // The AHMED support for single-precision complex symmetric matrices
        // is broken
        BOOST_CHECK(true);
        return;
    }

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

    PiecewiseLinearContinuousScalarSpace<BFT> pwiseLinears(grid);
    PiecewiseConstantScalarSpace<BFT> pwiseConstants(grid);

    AssemblyOptions assemblyOptions;
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    AcaOptions acaOptions;
    acaOptions.minimumBlockSize = 4;
    assemblyOptions.switchToAcaMode(acaOptions);
    AccuracyOptions accuracyOptions;
    accuracyOptions.doubleRegular.setRelativeQuadratureOrder(4);
    accuracyOptions.doubleSingular.setRelativeQuadratureOrder(2);
    NumericalQuadratureStrategy<BFT, RT> quadStrategy(accuracyOptions);

    Context<BFT, RT> context(make_shared_from_ref(quadStrategy), assemblyOptions);

    const RT waveNumber = initWaveNumber<RT>();

    BoundaryOperator<BFT, RT> opNonsymmetric =
        modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, RT, RT>(
            make_shared_from_ref(context),
            make_shared_from_ref(pwiseLinears),
            make_shared_from_ref(pwiseConstants),
            make_shared_from_ref(pwiseLinears),
            waveNumber,
            "", NO_SYMMETRY);
    BoundaryOperator<BFT, RT> opSymmetric =
        modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, RT, RT>(
            make_shared_from_ref(context),
            make_shared_from_ref(pwiseLinears),
            make_shared_from_ref(pwiseConstants),
            make_shared_from_ref(pwiseLinears),
            waveNumber,
            "", SYMMETRIC);

    arma::Mat<RT> matNonsymmetric = opNonsymmetric.weakForm()->asMatrix();
    arma::Mat<RT> matSymmetric = opSymmetric.weakForm()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<RT>(
                    matNonsymmetric, matSymmetric, 2 * acaOptions.eps));
}
BOOST_AUTO_TEST_CASE_TEMPLATE(aca_of_synthetic_maxwell_single_layer_operator_agrees_with_dense_assembly_in_asymmetric_case,
                              ValueType, complex_result_types)
{
    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-h-0.4.msh", false /* verbose */);

    RT waveNumber = initWaveNumber<RT>();

    shared_ptr<Space<BFT> > vectorPwiseLinears(
        new RaviartThomas0VectorSpace<BFT>(grid));
    shared_ptr<Space<BFT> > vectorPwiseLinears2(
        new RaviartThomas0VectorSpace<BFT>(grid));

    AccuracyOptions accuracyOptions;
    accuracyOptions.doubleRegular.setRelativeQuadratureOrder(2);
    accuracyOptions.singleRegular.setRelativeQuadratureOrder(2);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
                new NumericalQuadratureStrategy<BFT, RT>(accuracyOptions));

    AssemblyOptions assemblyOptionsDense;
    assemblyOptionsDense.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<Context<BFT, RT> > contextDense(
        new Context<BFT, RT>(quadStrategy, assemblyOptionsDense));

    BoundaryOperator<BFT, RT> opDense =
            maxwell3dSingleLayerBoundaryOperator<BFT>(
                contextDense,
                vectorPwiseLinears, vectorPwiseLinears, vectorPwiseLinears,
                waveNumber);
    arma::Mat<RT> weakFormDense = opDense.weakForm()->asMatrix();

    AssemblyOptions assemblyOptionsAca;
    assemblyOptionsAca.setVerbosityLevel(VerbosityLevel::LOW);
    AcaOptions acaOptions;
    acaOptions.mode = AcaOptions::LOCAL_ASSEMBLY;
    assemblyOptionsAca.switchToAcaMode(acaOptions);
    shared_ptr<Context<BFT, RT> > contextAca(
        new Context<BFT, RT>(quadStrategy, assemblyOptionsAca));

    // Internal domain different from dualToRange
    BoundaryOperator<BFT, RT> opAca =
            maxwell3dSingleLayerBoundaryOperator<BFT>(
                contextAca,
                vectorPwiseLinears, vectorPwiseLinears, vectorPwiseLinears2,
                waveNumber);
    arma::Mat<RT> weakFormAca = opAca.weakForm()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    weakFormDense, weakFormAca, 2. * acaOptions.eps));
}
BOOST_AUTO_TEST_CASE_TEMPLATE(interpolated_matches_noniterpolated,
                              BasisFunctionType, basis_function_types)
{
    typedef BasisFunctionType BFT;
    typedef typename Fiber::ScalarTraits<BFT>::ComplexType RT;
    typedef typename Fiber::ScalarTraits<BFT>::ComplexType KT;
    typedef typename Fiber::ScalarTraits<BFT>::RealType CT;

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

    PiecewiseLinearContinuousScalarSpace<BFT> pwiseLinears(grid);
    PiecewiseConstantScalarSpace<BFT> pwiseConstants(grid);

    AssemblyOptions assemblyOptions;
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    AccuracyOptions accuracyOptions;
    accuracyOptions.doubleRegular.setAbsoluteQuadratureOrder(5);
    accuracyOptions.doubleSingular.setAbsoluteQuadratureOrder(5);
    NumericalQuadratureStrategy<BFT, RT> quadStrategy(accuracyOptions);

    Context<BFT, RT> context(make_shared_from_ref(quadStrategy), assemblyOptions);

    const KT waveNumber(3.23, 0.31);

    BoundaryOperator<BFT, RT> opNoninterpolated =
            modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, KT, RT>(
                make_shared_from_ref(context),
                make_shared_from_ref(pwiseLinears),
                make_shared_from_ref(pwiseLinears),
                make_shared_from_ref(pwiseLinears),
                waveNumber,
                "", NO_SYMMETRY,
                false);
    BoundaryOperator<BFT, RT> opInterpolated =
            modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, KT, RT>(
                make_shared_from_ref(context),
                make_shared_from_ref(pwiseLinears),
                make_shared_from_ref(pwiseLinears),
                make_shared_from_ref(pwiseLinears),
                waveNumber,
                "", NO_SYMMETRY,
                true);

    arma::Mat<RT> matNoninterpolated = opNoninterpolated.weakForm()->asMatrix();
    arma::Mat<RT> matInterpolated = opInterpolated.weakForm()->asMatrix();

    const CT eps = std::numeric_limits<CT>::epsilon();
    BOOST_CHECK(check_arrays_are_close<RT>(
                    matNoninterpolated, matInterpolated, 100 * eps));
}
BOOST_AUTO_TEST_CASE_TEMPLATE(aca_of_synthetic_modified_helmholtz_hypersingular_operator_agrees_with_dense_assembly_in_symmetric_case,
                              ValueType, result_types)
{
    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-h-0.4.msh", false /* verbose */);

    RT waveNumber = initWaveNumber<RT>();

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

    AccuracyOptions accuracyOptions;
    accuracyOptions.doubleRegular.setRelativeQuadratureOrder(2);
    accuracyOptions.singleRegular.setRelativeQuadratureOrder(2);
    shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
                new NumericalQuadratureStrategy<BFT, RT>(accuracyOptions));

    AssemblyOptions assemblyOptionsDense;
    assemblyOptionsDense.setVerbosityLevel(VerbosityLevel::LOW);
    shared_ptr<Context<BFT, RT> > contextDense(
        new Context<BFT, RT>(quadStrategy, assemblyOptionsDense));

    BoundaryOperator<BFT, RT> opDense =
            modifiedHelmholtz3dHypersingularBoundaryOperator<BFT, RT, RT>(
                contextDense, pwiseLinears, pwiseConstants, pwiseLinears,
                waveNumber);
    arma::Mat<RT> weakFormDense = opDense.weakForm()->asMatrix();

    AssemblyOptions assemblyOptionsAca;
    assemblyOptionsAca.setVerbosityLevel(VerbosityLevel::LOW);
    AcaOptions acaOptions;
    acaOptions.mode = AcaOptions::LOCAL_ASSEMBLY;
    assemblyOptionsAca.switchToAcaMode(acaOptions);
    shared_ptr<Context<BFT, RT> > contextAca(
        new Context<BFT, RT>(quadStrategy, assemblyOptionsAca));

    BoundaryOperator<BFT, RT> opAca =
            modifiedHelmholtz3dHypersingularBoundaryOperator<BFT, RT, RT>(
                contextAca, pwiseLinears, pwiseConstants, pwiseLinears,
                waveNumber);
    arma::Mat<RT> weakFormAca = opAca.weakForm()->asMatrix();

    BOOST_CHECK(check_arrays_are_close<ValueType>(
                    weakFormDense, weakFormAca, 2. * acaOptions.eps));
}
void BlockedOperatorStructure<BasisFunctionType, ResultType>::setBlock(
    size_t row, size_t column,
    const BoundaryOperator<BasisFunctionType, ResultType> &op) {
  if (!op.isInitialized())
    resetBlock(row, column);
  else
    m_blocks[Key(row, column)] = op;
}
BoundaryOperator<BasisFunctionType, ResultType>
pseudoinverse(const BoundaryOperator<BasisFunctionType, ResultType>& boundaryOp)
{
    typedef AbstractBoundaryOperatorPseudoinverse<BasisFunctionType, ResultType>
            Pinv;
    return BoundaryOperator<BasisFunctionType, ResultType>(
                boundaryOp.context(),
                boost::make_shared<Pinv>(boundaryOp));
}
AbstractBoundaryOperatorPseudoinverse<BasisFunctionType, ResultType>::
AbstractBoundaryOperatorPseudoinverse(
        // TODO: add a solver argument specifying how to calculate the pseudoinv.
        const BoundaryOperator<BasisFunctionType, ResultType>& operatorToInvert,
        const shared_ptr<const Space<BasisFunctionType> >& dualToRange) :
    Base(operatorToInvert.range(), operatorToInvert.domain(),dualToRange,
         "pinv(" + operatorToInvert.label() + ")",
         throwIfUninitialized(operatorToInvert,
                              "AbstractBoundaryOperatorPseudoinverse::"
                              "AbstractBoundaryOperatorPseudoinverse(): "
                              "the boundary operator to be inverted must be "
                              "initialized"
                              ).abstractOperator()->symmetry()),
    m_operator(operatorToInvert),
    m_id(boost::make_shared<AbstractBoundaryOperatorPseudoinverseId<
         BasisFunctionType, ResultType> >(
             operatorToInvert))
{
}
BoundaryOperator<BasisFunctionType, ResultType>
pseudoinverse(const BoundaryOperator<BasisFunctionType, ResultType>& boundaryOp,
              const shared_ptr<const Space<BasisFunctionType> >& dualToRange)
{
    typedef AbstractBoundaryOperatorPseudoinverse<BasisFunctionType, ResultType>
            Pinv;
    return BoundaryOperator<BasisFunctionType, ResultType>(
                boundaryOp.context(),
                boost::make_shared<Pinv>(boundaryOp,dualToRange));
}
AdjointAbstractBoundaryOperator<BasisFunctionType, ResultType>::
AdjointAbstractBoundaryOperator(
        const BoundaryOperator<BasisFunctionType, ResultType>& boundaryOp,
        int symmetry) :
    Base(boundaryOp.dualToRange(),
         boundaryOp.domain() == boundaryOp.range() ?
             boundaryOp.dualToRange() :
             // assume that domain == dualToRange, we'll verify it
             // in the body of the constructor
             boundaryOp.range(),
         boundaryOp.domain(),
         "adj(" + boundaryOp.label() + ")",
         symmetry & AUTO_SYMMETRY ?
             boundaryOp.abstractOperator()->symmetry() :
             symmetry),
    m_operator(boundaryOp)
{
    if (boost::is_complex<BasisFunctionType>())
        throw std::logic_error(
            "AdjointAbstractBoundaryOperator(): Taking the adjoint of operators "
            "acting on complex-valued basis functions is not supported");
    if (boundaryOp.domain() != boundaryOp.range() &&
            boundaryOp.domain() != boundaryOp.dualToRange())
        throw std::runtime_error(
                "AdjointAbstractBoundaryOperator::"
                "AdjointAbstractBoundaryOperator(): "
                "Dual to the domain of the operator to invert cannot be determined "
                "since the domain is different from both "
                "the range and the space dual to its range");
}
AbstractBoundaryOperatorPseudoinverse<BasisFunctionType, ResultType>::
AbstractBoundaryOperatorPseudoinverse(
        // TODO: add a solver argument specifying how to calculate the pseudoinv.
        const BoundaryOperator<BasisFunctionType, ResultType>& operatorToInvert) :
    Base(operatorToInvert.range(), operatorToInvert.domain(),
         operatorToInvert.domain() == operatorToInvert.range() ?
             operatorToInvert.dualToRange() :
             // assume that domain == dualToRange, we'll verify it
             // in the body of the constructor
             operatorToInvert.range(),
         "pinv(" + operatorToInvert.label() + ")",
         throwIfUninitialized(operatorToInvert,
                              "AbstractBoundaryOperatorPseudoinverse::"
                              "AbstractBoundaryOperatorPseudoinverse(): "
                              "the boundary operator to be inverted must be "
                              "initialized"
                              ).abstractOperator()->symmetry()),
    m_operator(operatorToInvert),
    m_id(boost::make_shared<AbstractBoundaryOperatorPseudoinverseId<
         BasisFunctionType, ResultType> >(
             operatorToInvert))
{
    if (operatorToInvert.domain() != operatorToInvert.range() &&
            operatorToInvert.domain() != operatorToInvert.dualToRange())
        throw std::runtime_error(
                "AbstractBoundaryOperatorPseudoinverse::"
                "AbstractBoundaryOperatorPseudoinverse(): "
                "Dual to the domain of the operator to invert cannot be determined "
                "since the domain is different from both "
                "the range and the space dual to its range");
}
AbstractBoundaryOperatorPseudoinverseId<BasisFunctionType, ResultType>::
AbstractBoundaryOperatorPseudoinverseId(
        const BoundaryOperator<BasisFunctionType, ResultType>& operatorToInvert) :
    m_operatorToInvertId(operatorToInvert.abstractOperator()->id())
{
}
BOOST_AUTO_TEST_CASE_TEMPLATE(works, BasisFunctionType, basis_function_types)
{
    typedef BasisFunctionType BFT;
    typedef typename Fiber::ScalarTraits<BFT>::ComplexType RT;
    typedef typename Fiber::ScalarTraits<BFT>::RealType CT;
    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid = GridFactory::importGmshGrid(
                params, "meshes/two_disjoint_triangles.msh",
                false /* verbose */);

    PiecewiseLinearContinuousScalarSpace<BFT> pwiseLinears(grid);
    PiecewiseConstantScalarSpace<BFT> pwiseConstants(grid);

    AssemblyOptions assemblyOptions;
    assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
    AccuracyOptions accuracyOptions;
    accuracyOptions.doubleRegular.setAbsoluteQuadratureOrder(5);
    accuracyOptions.doubleSingular.setAbsoluteQuadratureOrder(5);
    NumericalQuadratureStrategy<BFT, RT> quadStrategy(accuracyOptions);

    Context<BFT, RT> context(make_shared_from_ref(quadStrategy), assemblyOptions);

    const RT waveNumber(1.23, 0.31);

    BoundaryOperator<BFT, RT> slpOpConstants = Bempp::helmholtz3dSingleLayerBoundaryOperator<BFT>(
                make_shared_from_ref(context),
                make_shared_from_ref(pwiseConstants),
                make_shared_from_ref(pwiseConstants),
                make_shared_from_ref(pwiseConstants),
                waveNumber);
    BoundaryOperator<BFT, RT> slpOpLinears = helmholtz3dSingleLayerBoundaryOperator<BFT>(
                make_shared_from_ref(context),
                make_shared_from_ref(pwiseLinears),
                make_shared_from_ref(pwiseLinears),
                make_shared_from_ref(pwiseLinears),
                waveNumber);
    BoundaryOperator<BFT, RT> hypOp = helmholtz3dHypersingularBoundaryOperator<BFT>(
                make_shared_from_ref(context),
                make_shared_from_ref(pwiseLinears),
                make_shared_from_ref(pwiseLinears),
                make_shared_from_ref(pwiseLinears),
                waveNumber);

    // Get the matrix repr. of the hypersingular operator
    arma::Mat<RT> hypMat = hypOp.weakForm()->asMatrix();

    // Construct the expected hypersingular operator matrix. For this, we need:

    // * the surface curls of all basis functions (which are constant)
    typedef Fiber::SurfaceCurl3dElementaryFunctor<CT> ElementaryFunctor;
    typedef Fiber::ElementaryBasisTransformationFunctorWrapper<ElementaryFunctor> Functor;
    Functor functor;
    size_t basisDeps = 0, geomDeps = 0;
    functor.addDependencies(basisDeps, geomDeps);

    arma::Mat<CT> points(2, 1);
    points.fill(0.);

    typedef Fiber::PiecewiseLinearContinuousScalarBasis<3, BFT> Basis;
    Basis basis;
    Fiber::BasisData<BFT> basisData;
    basis.evaluate(basisDeps, points, Fiber::ALL_DOFS, basisData);

    Fiber::GeometricalData<CT> geomData[2];
    std::unique_ptr<GridView> view = grid->leafView();
    std::unique_ptr<EntityIterator<0> > it = view->entityIterator<0>();
    it->entity().geometry().getData(geomDeps, points, geomData[0]);
    it->next();
    it->entity().geometry().getData(geomDeps, points, geomData[1]);

    Fiber::DefaultCollectionOfBasisTransformations<Functor> transformations(functor);

    Fiber::CollectionOf3dArrays<BFT> surfaceCurl[2];
    transformations.evaluate(basisData, geomData[0], surfaceCurl[0]);
    transformations.evaluate(basisData, geomData[1], surfaceCurl[1]);

    // * the single-layer potential matrix for constant basis functions
    arma::Mat<RT> slpMatConstants = slpOpConstants.weakForm()->asMatrix();
    // * the single-layer potential matrix for linear basis functions
    arma::Mat<RT> slpMatLinears = slpOpLinears.weakForm()->asMatrix();

    arma::Mat<RT> expectedHypMat(6, 6);
    for (size_t testElement = 0; testElement < 2; ++testElement)
        for (size_t trialElement = 0; trialElement < 2; ++trialElement)
            for (size_t r = 0; r < 3; ++r)
                for (size_t c = 0; c < 3; ++c) {
                    RT curlMultiplier = 0.;
                    for (size_t dim = 0; dim < 3; ++dim)
                        curlMultiplier += surfaceCurl[testElement][0](dim, r, 0) *
                                surfaceCurl[trialElement][0](dim, c, 0);
                    RT valueMultiplier = 0.;
                    for (size_t dim = 0; dim < 3; ++dim)
                        valueMultiplier += geomData[testElement].normals(dim, 0) *
                            geomData[trialElement].normals(dim, 0);
                    expectedHypMat(3 * testElement + r, 3 * trialElement + c) =
                            curlMultiplier * slpMatConstants(testElement, trialElement) -
                            waveNumber * waveNumber * valueMultiplier *
                            slpMatLinears(3 * testElement + r, 3 * trialElement + c);
                }

    BOOST_CHECK(check_arrays_are_close<RT>(expectedHypMat, hypMat, 1e-6));
}
Beispiel #13
0
int main(int argc, char* argv[])
{
    // Process command-line args

    if (argc < 7 || argc % 2 != 1) {
        std::cout << "Solve a Maxwell Dirichlet problem in an exterior domain.\n"
                  << "Usage: " << argv[0]
                  << " <mesh_file> <n_threads> <aca_eps> <solver_tol>"
                  << " <singular_order_increment>"
                  << " [<regular_order_increment_1> <min_relative_distance_1>]"
                  << " [<regular_order_increment_2> <min_relative_distance_2>]"
                  << " [...] <regular_order_increment_n>"
                  << std::endl;
        return 1;
    }
    int maxThreadCount = atoi(argv[2]);
    double acaEps = atof(argv[3]);
    double solverTol = atof(argv[4]);
    int singOrderIncrement = atoi(argv[5]);
    if (acaEps > 1. || acaEps < 0.) {
        std::cout << "Invalid aca_eps: " << acaEps << std::endl;
        return 1;
    }
    if (solverTol > 1. || solverTol < 0.) {
        std::cout << "Invalid solver_tol: " << solverTol << std::endl;
        return 1;
    }

    AccuracyOptionsEx accuracyOptions;
    std::vector<double> maxNormDists;
    std::vector<int> orderIncrements;
    for (int i = 6; i < argc - 1; i += 2) {
        orderIncrements.push_back(atoi(argv[i]));
        maxNormDists.push_back(atof(argv[i + 1]));
    }
    orderIncrements.push_back(atoi(argv[argc - 1]));
    accuracyOptions.setDoubleRegular(maxNormDists, orderIncrements);
    accuracyOptions.setDoubleSingular(singOrderIncrement);
    accuracyOptions.setSingleRegular(2);

    // Load mesh

    GridParameters params;
    params.topology = GridParameters::TRIANGULAR;
    shared_ptr<Grid> grid = GridFactory::importGmshGrid(params, argv[1]);

    // Initialize the space

    RaviartThomas0VectorSpace<BFT> HdivSpace(grid);

    // Set assembly mode and options

    AssemblyOptions assemblyOptions;
    assemblyOptions.setMaxThreadCount(maxThreadCount);
    if (acaEps > 0.) {
        AcaOptions acaOptions;
        acaOptions.eps = acaEps;
        assemblyOptions.switchToAcaMode(acaOptions);
    }

    NumericalQuadratureStrategy<BFT, RT> quadStrategy(accuracyOptions);
    Context<BFT, RT> context(make_shared_from_ref(quadStrategy), assemblyOptions);

    // Construct operators

    BoundaryOperator<BFT, RT> slpOp = maxwell3dSingleLayerBoundaryOperator<BFT>(
                make_shared_from_ref(context),
                make_shared_from_ref(HdivSpace),
                make_shared_from_ref(HdivSpace),
                make_shared_from_ref(HdivSpace),
                k,
                "SLP");
    BoundaryOperator<BFT, RT> dlpOp = maxwell3dDoubleLayerBoundaryOperator<BFT>(
                make_shared_from_ref(context),
                make_shared_from_ref(HdivSpace),
                make_shared_from_ref(HdivSpace),
                make_shared_from_ref(HdivSpace),
                k,
                "DLP");
    BoundaryOperator<BFT, RT> idOp = maxwell3dIdentityOperator<BFT, RT>(
                make_shared_from_ref(context),
                make_shared_from_ref(HdivSpace),
                make_shared_from_ref(HdivSpace),
                make_shared_from_ref(HdivSpace),
                "Id");

    // Construct a grid function representing the Dirichlet data

    GridFunction<BFT, RT> dirichletData(
                make_shared_from_ref(context),
                make_shared_from_ref(HdivSpace),
                make_shared_from_ref(HdivSpace),
                surfaceNormalDependentFunction(DirichletData()));

    dirichletData.exportToVtk(VtkWriter::CELL_DATA, "Dirichlet_data",
                              "input_dirichlet_data_cell");
    dirichletData.exportToVtk(VtkWriter::VERTEX_DATA, "Dirichlet_data",
                              "input_dirichlet_data_vertex");

    // Construct a grid function representing the right-hand side

    GridFunction<BFT, RT> rhs = -((0.5 * idOp + dlpOp) * dirichletData);

    // Solve the equation

    Solution<BFT, RT> solution;
#ifdef WITH_AHMED
    if (solverTol > 0.) {
        DefaultIterativeSolver<BFT, RT> solver(slpOp);

        AcaApproximateLuInverse<RT> slpOpLu(
                     DiscreteAcaBoundaryOperator<RT>::castToAca(
                         *slpOp.weakForm()),
                         /* LU factorisation accuracy */ 0.01);
        Preconditioner<RT> prec =
                     discreteOperatorToPreconditioner<RT>(
                         make_shared_from_ref(slpOpLu));

        solver.initializeSolver(defaultGmresParameterList(solverTol, 10000), prec);
        solution = solver.solve(rhs);
    } else {
        DefaultDirectSolver<BFT, RT> solver(slpOp);
        solution = solver.solve(rhs);
    }
#else // WITH_AHMED
    DefaultDirectSolver<BFT, RT> solver(slpOp);
    solution = solver.solve(rhs);
#endif
    std::cout << solution.solverMessage() << std::endl;

    // Extract the solution

    const GridFunction<BFT, RT>& neumannData = solution.gridFunction();

    neumannData.exportToVtk(VtkWriter::CELL_DATA, "Neumann_data",
                        "calculated_neumann_data_cell");
    neumannData.exportToVtk(VtkWriter::VERTEX_DATA, "Neumann_data",
                        "calculated_neumann_data_vertex");

    // Compare the solution against the analytical result
    GridFunction<BFT, RT> exactNeumannData(
                make_shared_from_ref(context),
                make_shared_from_ref(HdivSpace),
                make_shared_from_ref(HdivSpace),
                surfaceNormalDependentFunction(ExactNeumannData()));
    exactNeumannData.exportToVtk(VtkWriter::CELL_DATA, "Neumann_data",
                                 "exact_neumann_data_cell");
    exactNeumannData.exportToVtk(VtkWriter::VERTEX_DATA, "Neumann_data",
                                 "exact_neumann_data_vertex");
    EvaluationOptions evaluationOptions;
    CT absoluteError = L2NormOfDifference(
                neumannData, surfaceNormalDependentFunction(ExactNeumannData()),
                quadStrategy, evaluationOptions);
    CT exactSolNorm = L2NormOfDifference(
                0. * neumannData, surfaceNormalDependentFunction(ExactNeumannData()),
                quadStrategy, evaluationOptions);
    std::cout << "Relative L^2 error: " << absoluteError / exactSolNorm
              << "\nAbsolute L^2 error: " << absoluteError << std::endl;

    // Evaluate the solution at a few points

    Maxwell3dSingleLayerPotentialOperator<BFT> slpPotOp(k);
    Maxwell3dDoubleLayerPotentialOperator<BFT> dlpPotOp(k);

    const int dimWorld = 3;
    const int pointCount = 3;
    arma::Mat<CT> points(dimWorld, pointCount * pointCount);
    for (int i = 0; i < pointCount; ++i)
        for (int j = 0; j < pointCount; ++j) {
            points(0, i * pointCount + j) = 3. + i / CT(pointCount - 1);
            points(1, i * pointCount + j) = 2. + j / CT(pointCount - 1);
            points(2, i * pointCount + j) = 0.5;
        }

    arma::Mat<RT> slpContrib = slpPotOp.evaluateAtPoints(
        neumannData, points, quadStrategy, evaluationOptions);
    arma::Mat<RT> dlpContrib = dlpPotOp.evaluateAtPoints(
        dirichletData, points, quadStrategy, evaluationOptions);
    arma::Mat<RT> values = -slpContrib - dlpContrib;

    // Evaluate the analytical solution at the same points

    ExactSolution exactSolution;
    arma::Mat<RT> exactValues(dimWorld, pointCount * pointCount);
    for (int i = 0; i < pointCount * pointCount; ++i) {
        arma::Col<RT> activeResultColumn = exactValues.unsafe_col(i);
        exactSolution.evaluate(points.unsafe_col(i), activeResultColumn);
    }

    // Compare the numerical and analytical solutions

    std::cout << "Numerical | analytical\n";
    for (int i = 0; i < pointCount * pointCount; ++i)
        std::cout << values(0, i) << " "
                  << values(1, i) << " "
                  << values(2, i) << " | "
                  << exactValues(0, i) << " "
                  << exactValues(1, i) << " "
                  << exactValues(2, i) << "\n";
}
BoundaryOperator<BasisFunctionType, ResultType>
modifiedHelmholtz3dSyntheticHypersingularBoundaryOperator(
    const shared_ptr<const Context<BasisFunctionType, ResultType>> &context,
    const shared_ptr<const Space<BasisFunctionType>> &domain,
    const shared_ptr<const Space<BasisFunctionType>> &range,
    const shared_ptr<const Space<BasisFunctionType>> &dualToRange,
    KernelType waveNumber, std::string label, int internalSymmetry,
    bool useInterpolation, int interpPtsPerWavelength,
    const BoundaryOperator<BasisFunctionType, ResultType> &externalSlp) {
  typedef typename ScalarTraits<BasisFunctionType>::RealType CoordinateType;

  typedef Fiber::ScalarFunctionValueFunctor<CoordinateType> ValueFunctor;
  typedef Fiber::ScalarFunctionValueTimesNormalFunctor<CoordinateType>
      ValueTimesNormalFunctor;
  typedef Fiber::SurfaceCurl3dFunctor<CoordinateType> CurlFunctor;
  typedef Fiber::SingleComponentTestTrialIntegrandFunctor<
      BasisFunctionType, ResultType> IntegrandFunctor;

  typedef GeneralElementaryLocalOperator<BasisFunctionType, ResultType> LocalOp;
  typedef SyntheticIntegralOperator<BasisFunctionType, ResultType> SyntheticOp;

  if (!domain || !range || !dualToRange)
    throw std::invalid_argument(
        "modifiedHelmholtz3dSyntheticHypersingularBoundaryOperator(): "
        "domain, range and dualToRange must not be null");

  shared_ptr<const Space<BasisFunctionType>> newDomain = domain;
  shared_ptr<const Space<BasisFunctionType>> newDualToRange = dualToRange;

  bool isBarycentric =
      (domain->isBarycentric() || dualToRange->isBarycentric());

  if (isBarycentric) {
    newDomain = domain->barycentricSpace(domain);
    newDualToRange = dualToRange->barycentricSpace(dualToRange);
  }

  shared_ptr<const Context<BasisFunctionType, ResultType>> internalContext,
      auxContext;
  SyntheticOp::getContextsForInternalAndAuxiliaryOperators(
      context, internalContext, auxContext);
  shared_ptr<const Space<BasisFunctionType>> internalTrialSpace =
      newDomain->discontinuousSpace(newDomain);
  shared_ptr<const Space<BasisFunctionType>> internalTestSpace =
      newDualToRange->discontinuousSpace(newDualToRange);

  // Note: we don't really need to care about ranges and duals to domains of
  // the internal operator. The only range space that matters is that of the
  // leftmost operator in the product.

  const char xyz[] = "xyz";
  const size_t dimWorld = 3;

  if (label.empty())
    label =
        AbstractBoundaryOperator<BasisFunctionType, ResultType>::uniqueLabel();

  BoundaryOperator<BasisFunctionType, ResultType> slp;
  if (!externalSlp.isInitialized()) {

    slp = modifiedHelmholtz3dSingleLayerBoundaryOperator<
        BasisFunctionType, KernelType, ResultType>(
        internalContext, internalTrialSpace,
        internalTestSpace /* or whatever */, internalTestSpace, waveNumber,
        "(" + label + ")_internal_SLP", internalSymmetry, useInterpolation,
        interpPtsPerWavelength);
  } else {

    slp = externalSlp;
  }

  // symmetry of the decomposition
  int syntheseSymmetry = 0; // symmetry of the decomposition
  if (newDomain == newDualToRange && internalTrialSpace == internalTestSpace)
    syntheseSymmetry =
        HERMITIAN | (boost::is_complex<BasisFunctionType>() ? 0 : SYMMETRIC);

  std::vector<BoundaryOperator<BasisFunctionType, ResultType>> testLocalOps;
  std::vector<BoundaryOperator<BasisFunctionType, ResultType>> trialLocalOps;
  testLocalOps.resize(dimWorld);
  for (size_t i = 0; i < dimWorld; ++i)
    testLocalOps[i] = BoundaryOperator<BasisFunctionType, ResultType>(
        auxContext, boost::make_shared<LocalOp>(
                        internalTestSpace, range, newDualToRange,
                        ("(" + label + ")_test_curl_") + xyz[i], NO_SYMMETRY,
                        CurlFunctor(), ValueFunctor(), IntegrandFunctor(i, 0)));

  if (!syntheseSymmetry) {
    trialLocalOps.resize(dimWorld);
    for (size_t i = 0; i < dimWorld; ++i)
      trialLocalOps[i] = BoundaryOperator<BasisFunctionType, ResultType>(
          auxContext,
          boost::make_shared<LocalOp>(
              newDomain, internalTrialSpace /* or whatever */,
              internalTrialSpace, ("(" + label + ")_trial_curl_") + xyz[i],
              NO_SYMMETRY, ValueFunctor(), CurlFunctor(),
              IntegrandFunctor(0, i)));
  }
  // It might be more prudent to distinguish between the symmetry of the total
  // operator and the symmetry of the decomposition
  BoundaryOperator<BasisFunctionType, ResultType> term0(
      context, boost::make_shared<SyntheticOp>(testLocalOps, slp, trialLocalOps,
                                               label, syntheseSymmetry));

  for (size_t i = 0; i < dimWorld; ++i)
    testLocalOps[i] = BoundaryOperator<BasisFunctionType, ResultType>(
        auxContext,
        boost::make_shared<LocalOp>(
            internalTestSpace, range, newDualToRange,
            ("(" + label + ")_test_k_value_n_") + xyz[i], NO_SYMMETRY,
            ValueTimesNormalFunctor(), ValueFunctor(), IntegrandFunctor(i, 0)));
  if (!syntheseSymmetry)
    for (size_t i = 0; i < dimWorld; ++i)
      trialLocalOps[i] = BoundaryOperator<BasisFunctionType, ResultType>(
          auxContext,
          boost::make_shared<LocalOp>(
              newDomain, internalTrialSpace /* or whatever */,
              internalTrialSpace, ("(" + label + ")_trial_k_value_n_") + xyz[i],
              NO_SYMMETRY, ValueFunctor(), ValueTimesNormalFunctor(),
              IntegrandFunctor(0, i)));
  BoundaryOperator<BasisFunctionType, ResultType> slp_waveNumberSq =
      (waveNumber * waveNumber) * slp;
  BoundaryOperator<BasisFunctionType, ResultType> term1(
      context,
      boost::make_shared<SyntheticOp>(testLocalOps, slp_waveNumberSq,
                                      trialLocalOps, label, syntheseSymmetry));

  return term0 + term1;
}
BoundaryOperator<BasisFunctionType, ResultType>
modifiedHelmholtz3dHypersingularBoundaryOperator(
    const shared_ptr<const Context<BasisFunctionType, ResultType>> &context,
    const shared_ptr<const Space<BasisFunctionType>> &domain,
    const shared_ptr<const Space<BasisFunctionType>> &range,
    const shared_ptr<const Space<BasisFunctionType>> &dualToRange,
    KernelType waveNumber, const std::string &label, int symmetry,
    bool useInterpolation, int interpPtsPerWavelength,
    const BoundaryOperator<BasisFunctionType, ResultType> &externalSlp) {
  const AssemblyOptions &assemblyOptions = context->assemblyOptions();
  if ((assemblyOptions.assemblyMode() == AssemblyOptions::ACA &&
       assemblyOptions.acaOptions().mode == AcaOptions::LOCAL_ASSEMBLY) ||
      externalSlp.isInitialized())
    return modifiedHelmholtz3dSyntheticHypersingularBoundaryOperator(
        context, domain, range, dualToRange, waveNumber, label, symmetry,
        useInterpolation, interpPtsPerWavelength, externalSlp);

  typedef typename ScalarTraits<BasisFunctionType>::RealType CoordinateType;

  typedef Fiber::ModifiedHelmholtz3dHypersingularKernelFunctor<KernelType>
      NoninterpolatedKernelFunctor;
  typedef Fiber::ModifiedHelmholtz3dHypersingularKernelInterpolatedFunctor<
      KernelType> InterpolatedKernelFunctor;
  typedef Fiber::ModifiedHelmholtz3dHypersingularTransformationFunctor<
      CoordinateType> TransformationFunctor;
  typedef Fiber::ModifiedHelmholtz3dHypersingularIntegrandFunctor2<
      BasisFunctionType, KernelType, ResultType> IntegrandFunctor;

  typedef Fiber::ModifiedHelmholtz3dHypersingularTransformationFunctor2<
      CoordinateType> TransformationFunctorWithBlas;

  typedef Fiber::
      ModifiedHelmholtz3dHypersingularOffDiagonalInterpolatedKernelFunctor<
          KernelType> OffDiagonalInterpolatedKernelFunctor;
  typedef Fiber::ModifiedHelmholtz3dHypersingularOffDiagonalKernelFunctor<
      KernelType> OffDiagonalNoninterpolatedKernelFunctor;
  typedef Fiber::ScalarFunctionValueFunctor<CoordinateType>
      OffDiagonalTransformationFunctor;
  typedef Fiber::SimpleTestScalarKernelTrialIntegrandFunctorExt<
      BasisFunctionType, KernelType, ResultType, 1> OffDiagonalIntegrandFunctor;

  CoordinateType maxDistance_ =
      static_cast<CoordinateType>(1.1) *
      maxDistance(*domain->grid(), *dualToRange->grid());

  shared_ptr<Fiber::TestKernelTrialIntegral<BasisFunctionType, KernelType,
                                            ResultType>> integral,
      offDiagonalIntegral;
  if (shouldUseBlasInQuadrature(assemblyOptions, *domain, *dualToRange)) {
    integral.reset(new Fiber::TypicalTestScalarKernelTrialIntegral<
        BasisFunctionType, KernelType, ResultType>());
    offDiagonalIntegral = integral;
  } else {
    integral.reset(new Fiber::DefaultTestKernelTrialIntegral<IntegrandFunctor>(
        IntegrandFunctor()));
    offDiagonalIntegral.reset(
        new Fiber::DefaultTestKernelTrialIntegral<OffDiagonalIntegrandFunctor>(
            OffDiagonalIntegrandFunctor()));
  }

  typedef GeneralHypersingularIntegralOperator<BasisFunctionType, KernelType,
                                               ResultType> Op;
  shared_ptr<Op> newOp;
  if (shouldUseBlasInQuadrature(assemblyOptions, *domain, *dualToRange)) {
    shared_ptr<Fiber::TestKernelTrialIntegral<BasisFunctionType, KernelType,
                                              ResultType>> integral,
        offDiagonalIntegral;
    integral.reset(new Fiber::TypicalTestScalarKernelTrialIntegral<
        BasisFunctionType, KernelType, ResultType>());
    offDiagonalIntegral = integral;
    if (useInterpolation)
      newOp.reset(new Op(
          domain, range, dualToRange, label, symmetry,
          InterpolatedKernelFunctor(waveNumber, maxDistance_,
                                    interpPtsPerWavelength),
          TransformationFunctorWithBlas(), TransformationFunctorWithBlas(),
          integral, OffDiagonalInterpolatedKernelFunctor(
                        waveNumber, maxDistance_, interpPtsPerWavelength),
          OffDiagonalTransformationFunctor(),
          OffDiagonalTransformationFunctor(), offDiagonalIntegral));
    else
      newOp.reset(new Op(
          domain, range, dualToRange, label, symmetry,
          NoninterpolatedKernelFunctor(waveNumber),
          TransformationFunctorWithBlas(), TransformationFunctorWithBlas(),
          integral, OffDiagonalNoninterpolatedKernelFunctor(waveNumber),
          OffDiagonalTransformationFunctor(),
          OffDiagonalTransformationFunctor(), offDiagonalIntegral));
  } else { // no blas
    if (useInterpolation)
      newOp.reset(new Op(
          domain, range, dualToRange, label, symmetry,
          InterpolatedKernelFunctor(waveNumber, maxDistance_,
                                    interpPtsPerWavelength),
          TransformationFunctor(), TransformationFunctor(), IntegrandFunctor(),
          OffDiagonalInterpolatedKernelFunctor(waveNumber, maxDistance_,
                                               interpPtsPerWavelength),
          OffDiagonalTransformationFunctor(),
          OffDiagonalTransformationFunctor(), OffDiagonalIntegrandFunctor()));
    else
      newOp.reset(new Op(
          domain, range, dualToRange, label, symmetry,
          NoninterpolatedKernelFunctor(waveNumber), TransformationFunctor(),
          TransformationFunctor(), IntegrandFunctor(),
          OffDiagonalNoninterpolatedKernelFunctor(waveNumber),
          OffDiagonalTransformationFunctor(),
          OffDiagonalTransformationFunctor(), OffDiagonalIntegrandFunctor()));
  }
  return BoundaryOperator<BasisFunctionType, ResultType>(context, newOp);
}