#include "Base/Axis/Bin.h"
#include "Base/Axis/Pixel.h"
#include "Base/Const/Units.h"
#include "Base/Spin/SpinMatrix.h"
#include "Base/Spin/Spinor.h"
#include "Resample/Refine/ParticlesInSlice.h"
#include "Sample/Aggregate/Dilute2D.h"
#include "Sample/Element/DiffuseElement.h"
#include "Sample/Flux/Flux.h"
#include "Sample/Flux/MatrixFlux.h"
#include "Sample/Flux/ScalarFlux.h"
#include "Sample/HardParticle/HardParticles.h"
#include "Sample/HardParticle/Polyhedra.h"
#include "Sample/HardParticle/Sphere.h"
#include "Sample/Lattice/Lattice3D.h"
#include "Sample/Material/SLDMaterial.h"
#include "Sample/Material/Vacuum.h"
#include "Sample/Multilayer/Layer.h"
#include "Sample/Multilayer/Slice.h"
#include "Sample/Option/SimulationOptions.h"
#include "Sample/Particle/Compound.h"
#include "Sample/Particle/Crystal.h"
#include "Sample/Particle/DwbaChannels.h"
#include "Sample/Particle/Formfactor.h"
#include "Sample/Particle/Mesocrystal.h"
#include "Sample/Particle/Mixture.h"
#include "Sample/Particle/Particle.h"
#include "Tests/GTestWrapper/google_test.h"
#include <algorithm>
#include <array>
#include <complex>
#include <memory>
#include <stdexcept>
#include <vector>

namespace {

constexpr size_t TEST_SLICE = 0;
constexpr double TEST_PARTICLE_SLD = 6e-6;
constexpr double TEST_POL_DWBA_MAG_SLD = 1.2e-6;
const MaterialColor TEST_MATERIAL_COLOR(0.86, 0.24, 0.18);

struct TestDwbaConfig {
    double wavelength;
    double alpha_i;
    double phi_i;
    double alpha_f;
    double phi_f;
    complex_t kz_in;
    complex_t T_in;
    complex_t R_in;
    complex_t kz_out;
    complex_t T_out;
    complex_t R_out;
};

// Test form factor that counts scalar and polarized FF evaluations through formfactorAtBottom().
class CountingFormfactor : public Formfactor {
public:
    explicit CountingFormfactor(std::shared_ptr<size_t> calls)
        : m_calls(std::move(calls))
    {
    }

    std::string className() const override { return "CountingFormfactor"; }

    complex_t formfactorAtBottom(C3) const override
    {
        ++*m_calls;
        return 1.0;
    }

    double volume() const override { return 1.0; }

    CountingFormfactor* clone() const override { return new CountingFormfactor(m_calls); }

    double radialExtension() const override { return 1.0; }

private:
    std::shared_ptr<size_t> m_calls;
};

const TestDwbaConfig& defaultScalarDwbaConfig()
{
    static const TestDwbaConfig cfg{1.54,
                                    0.17,
                                    0.11,
                                    0.29,
                                    -0.21,
                                    complex_t(1.13, 0.07),
                                    complex_t(0.82, 0.04),
                                    complex_t(-0.21, 0.16),
                                    complex_t(0.91, -0.05),
                                    complex_t(0.76, -0.03),
                                    complex_t(0.19, -0.12)};
    return cfg;
}

void setUpParticle(Particle& particle, const Material& ambient, R3 offset = R3(0.0, 0.0, 0.0))
{
    particle.setAmbientMaterial(ambient);
    particle.translate(offset);
}

void fillSphereBoxMixture(Mixture& mixture, const Material& material, const Material& ambient,
                          double p_sphere, double p_box, R3 offset = R3(0.0, 0.0, 0.0))
{
    Particle sphere(material, Sphere(1.0));
    setUpParticle(sphere, ambient);
    Particle box(material, Box(0.8, 1.4, 1.1));
    setUpParticle(box, ambient);

    mixture.addParticle(sphere, p_sphere);
    mixture.addParticle(box, p_box);
    mixture.validate();
    mixture.translate(offset);
}

struct ScalarDwbaElement {
    Pixel pixel;
    Fluxes fluxes_in;
    Fluxes fluxes_out;
    DiffuseElement element;

    ScalarDwbaElement(double wavelength, double alpha_i, double phi_i, double alpha_f, double phi_f,
                      complex_t kz_in, const Spinor& tr_in, complex_t kz_out, const Spinor& tr_out,
                      bool only_outgoing)
        : pixel(Bin1D::At(phi_f), Bin1D::At(alpha_f))
        , fluxes_in()
        , fluxes_out()
        , element(wavelength, alpha_i, phi_i, &pixel, SpinMatrix::One() / 2.0, SpinMatrix::One(),
                  &fluxes_in, &fluxes_out)
    {
        fluxes_in.emplace_back(std::make_unique<ScalarFlux>(kz_in, tr_in));
        fluxes_out.emplace_back(std::make_unique<ScalarFlux>(kz_out, tr_out));
        fluxes_in[0]->only_outgoing = only_outgoing;
        fluxes_out[0]->only_outgoing = only_outgoing;
    }
};

std::unique_ptr<ScalarDwbaElement> makeTestDwbaElement(bool only_outgoing = false)
{
    const auto& cfg = defaultScalarDwbaConfig();
    return std::make_unique<ScalarDwbaElement>(
        cfg.wavelength, cfg.alpha_i, cfg.phi_i, cfg.alpha_f, cfg.phi_f, cfg.kz_in,
        Spinor(cfg.T_in, cfg.R_in), cfg.kz_out, Spinor(cfg.T_out, cfg.R_out), only_outgoing);
}

MesoOptions reciprocalMesoOptions()
{
    MesoOptions result;
    result.use_reciprocal_sum = true;
    result.radius_factor = 2.5;
    return result;
}

void prepareReciprocalMesocrystal(Mesocrystal& mc, const Material& ambient)
{
    mc.prepareForComputation(ambient, reciprocalMesoOptions());
}

std::unique_ptr<Mesocrystal> makeReciprocalDeterministicCompoundMesocrystal(const Material& ambient)
{
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle sphere(material, Sphere(1.0));
    setUpParticle(sphere, ambient);
    Particle box(material, Box(0.8, 1.4, 1.1));
    setUpParticle(box, ambient);
    box.translate(R3(0.6, -0.3, 0.2));

    Compound basis{};
    basis.addComponent(sphere);
    basis.addComponent(box);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(8.0, 0.0, 0.0), R3(0.0, 8.0, 0.0), R3(0.0, 0.0, 8.0));
    const Crystal crystal(basis, lattice);
    auto result = std::make_unique<Mesocrystal>(crystal, Box(20.0, 20.0, 20.0));
    result->setSliceNumber(TEST_SLICE);
    prepareReciprocalMesocrystal(*result, ambient);
    return result;
}

std::unique_ptr<Mesocrystal> makeReciprocalMixtureBasisMesocrystal(const Material& ambient)
{
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle small(material, Sphere(1.0));
    setUpParticle(small, ambient);
    Particle large(material, Sphere(1.6));
    setUpParticle(large, ambient);
    Mixture mixture{};
    mixture.addParticle(small, 1.0);
    mixture.addParticle(large, 1.0);
    mixture.validate();

    const Lattice3D lattice(R3(8.0, 0.0, 0.0), R3(0.0, 8.0, 0.0), R3(0.0, 0.0, 8.0));
    const Crystal crystal(mixture, lattice);
    auto result = std::make_unique<Mesocrystal>(crystal, Box(20.0, 20.0, 20.0));
    result->setSliceNumber(TEST_SLICE);
    return result;
}

std::unique_ptr<Mesocrystal> makeReciprocalNestedMixtureBasisMesocrystal(const Material& ambient)
{
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle left(material, Sphere(1.0));
    setUpParticle(left, ambient);
    Particle right(material, Sphere(1.6));
    setUpParticle(right, ambient);
    Mixture stochastic_child{};
    stochastic_child.addParticle(left, 1.0);
    stochastic_child.addParticle(right, 1.0);
    stochastic_child.validate();

    Compound basis{};
    Particle center(material, Sphere(0.8));
    setUpParticle(center, ambient);
    basis.addComponent(center);
    basis.addComponent(stochastic_child);

    const Lattice3D lattice(R3(8.0, 0.0, 0.0), R3(0.0, 8.0, 0.0), R3(0.0, 0.0, 8.0));
    const Crystal crystal(basis, lattice);
    auto result = std::make_unique<Mesocrystal>(crystal, Box(20.0, 20.0, 20.0));
    result->setSliceNumber(TEST_SLICE);
    return result;
}

ScatteringMoments contractDwba(const DwbaChannels& dwba, const ChannelMoments& cm)
{
    complex_t mean = 0;
    double variance = 0;
    for (size_t m = 0; m < dwba.n_active; ++m) {
        mean += dwba.w[m] * cm.mu[m];
        for (size_t n = 0; n < dwba.n_active; ++n)
            variance += std::real(dwba.w[m] * std::conj(dwba.w[n]) * cm.C[m][n]);
    }
    return {mean, variance};
}

PolScatteringMoments contractDwba(const PolDwbaChannels& dwba, const PolChannelMoments& cm)
{
    SpinMatrix mean;
    SpinMatrix variance;
    for (size_t m = 0; m < dwba.n_active; ++m) {
        mean += applyWeight(cm.mu[m], dwba.flux[m]);
        for (size_t n = 0; n < dwba.n_active; ++n)
            variance += cm.C[m][n];
    }
    return {mean, variance};
}

complex_t quadraticForm(const ChannelMoments& moments, const std::array<complex_t, N_DWBA>& coeffs)
{
    complex_t result = 0;
    for (size_t m = 0; m < N_DWBA; ++m)
        for (size_t n = 0; n < N_DWBA; ++n)
            result += coeffs[m] * std::conj(coeffs[n]) * moments.C[m][n];
    return result;
}

ScatteringMoments bruteForceBinaryMesocrystal(const DwbaChannels& dwba,
                                              const std::vector<R3>& positions,
                                              const IParticle& particle_a, double p_a,
                                              const IParticle& particle_b, double p_b)
{
    complex_t mean = 0;
    double second_moment = 0;
    const size_t n_configs = size_t{1} << positions.size();
    for (size_t mask = 0; mask < n_configs; ++mask) {
        double probability = 1.0;
        complex_t amplitude = 0;
        for (size_t i = 0; i < positions.size(); ++i) {
            const bool use_b = (mask >> i) & 1u;
            const IParticle& particle = use_b ? particle_b : particle_a;
            probability *= use_b ? p_b : p_a;
            for (size_t m = 0; m < N_DWBA; ++m) {
                const complex_t phase =
                    std::exp(complex_t(0.0, 1.0) * positions[i].dot(dwba.q[m].getQ()));
                amplitude += dwba.w[m] * phase * particle.theFF(dwba.q[m]);
            }
        }
        mean += probability * amplitude;
        second_moment += probability * std::norm(amplitude);
    }
    return {mean, second_moment - std::norm(mean)};
}

complex_t transferFactor(const std::vector<R3>& positions, const C3& dq, double sigma)
{
    complex_t phase_sum = 0;
    for (const auto& position : positions)
        phase_sum += std::exp(complex_t(0.0, 1.0) * position.dot(dq));
    return phase_sum * std::exp(-dq.analytic_square() * sigma * sigma / 2.0);
}

void expectComplexNear(complex_t actual, complex_t expected, double tol)
{
    const complex_t diff = actual - expected;
    EXPECT_NEAR(diff.real(), 0.0, tol);
    EXPECT_NEAR(diff.imag(), 0.0, tol);
}

void expectScatteringMomentsNear(const ScatteringMoments& actual, const ScatteringMoments& expected,
                                 double tol)
{
    expectComplexNear(actual.mean, expected.mean, tol);
    EXPECT_NEAR(actual.variance, expected.variance, tol);
}

void expectChannelMomentsNear(const ChannelMoments& actual, const ChannelMoments& expected,
                              double tol)
{
    for (size_t m = 0; m < N_DWBA; ++m) {
        expectComplexNear(actual.mu[m], expected.mu[m], tol);
        for (size_t n = 0; n < N_DWBA; ++n)
            expectComplexNear(actual.C[m][n], expected.C[m][n], tol);
    }
}

void expectZeroCovariance(const ChannelMoments& moments, double tol)
{
    for (size_t m = 0; m < N_DWBA; ++m)
        for (size_t n = 0; n < N_DWBA; ++n)
            expectComplexNear(moments.C[m][n], complex_t(0.0), tol);
}

void expectHermitian(const ChannelMoments& moments, double tol)
{
    for (size_t m = 0; m < N_DWBA; ++m) {
        EXPECT_NEAR(moments.C[m][m].imag(), 0.0, tol);
        EXPECT_GE(moments.C[m][m].real(), -tol);
        for (size_t n = m + 1; n < N_DWBA; ++n)
            expectComplexNear(moments.C[n][m], std::conj(moments.C[m][n]), tol);
    }
}

double maxOffDiagonalMagnitude(const ChannelMoments& moments)
{
    double result = 0;
    for (size_t m = 0; m < N_DWBA; ++m) {
        for (size_t n = 0; n < N_DWBA; ++n) {
            if (m == n)
                continue;
            result = std::max(result, std::abs(moments.C[m][n]));
        }
    }
    return result;
}

void expectPositiveSemidefinite(const ChannelMoments& moments,
                                const std::array<complex_t, N_DWBA>& coeffs, double tol)
{
    const complex_t value = quadraticForm(moments, coeffs);
    EXPECT_NEAR(value.imag(), 0.0, tol);
    EXPECT_GE(value.real(), -tol);
}

} // namespace

// A mixture of identical particles must have the same moments as the single particle,
// and zero covariance (no form-factor contrast between components).
TEST(DwbaChannelMoments, IdenticalMixtureEquivalentToParticle)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle particle(material, Sphere(1.3));
    setUpParticle(particle, ambient);
    particle.setSliceNumber(TEST_SLICE);

    Mixture mixture{};
    mixture.addParticle(particle, 0.25);
    mixture.addParticle(particle, 0.75);
    mixture.validate();
    mixture.setSliceNumber(TEST_SLICE);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);

    const auto particle_cm = particle.channelMoments(dwba);
    const auto mixture_cm = mixture.channelMoments(dwba);
    expectChannelMomentsNear(mixture_cm, particle_cm, 1e-12);
    expectZeroCovariance(mixture_cm, 1e-12);

    const auto particle_sm = particle.scatteringMoments(ele->element);
    const auto mixture_sm = mixture.scatteringMoments(ele->element);
    expectScatteringMomentsNear(mixture_sm, particle_sm, 1e-12);
}

// Contracting channel moments with DWBA weights must reproduce the scattering
// moments obtained directly. Also checks that C is Hermitian and non-trivial.
TEST(DwbaChannelMoments, MixtureContractionMatchesScatteringMoments)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Mixture mixture{};
    fillSphereBoxMixture(mixture, material, ambient, 0.6, 0.4);
    mixture.setSliceNumber(TEST_SLICE);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    const auto cm = mixture.channelMoments(dwba);
    const auto contracted = contractDwba(dwba, cm);
    const auto scattering = mixture.scatteringMoments(ele->element);

    expectScatteringMomentsNear(contracted, scattering, 1e-11);
    expectHermitian(cm, 1e-12);
    EXPECT_GT(maxOffDiagonalMagnitude(cm), 1e-12);
    EXPECT_GT(scattering.variance, 0.0);
}

// A mesocrystal with a single lattice node must reproduce the bare basis moments
// (lattice sums and transfer factors reduce to unity).
TEST(DwbaChannelMoments, SingleNodeMesocrystalMatchesBasis)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, 0.4, 0.6);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(100.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(5.0, 5.0, 5.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 1u);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    const auto basis_cm = basis.channelMoments(dwba);
    const auto meso_cm = mc.channelMoments(dwba);

    expectChannelMomentsNear(meso_cm, basis_cm, 1e-11);
    expectHermitian(meso_cm, 1e-12);

    const auto basis_sm = basis.scatteringMoments(ele->element);
    const auto meso_sm = mc.scatteringMoments(ele->element);
    expectScatteringMomentsNear(meso_sm, basis_sm, 1e-11);
}

// A mesocrystal whose basis is a mixture of identical particles must give the same
// result as one whose basis is the single particle (no stochastic contrast).
TEST(DwbaChannelMoments, IdenticalMixtureBasisMatchesParticleBasis)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle basis_particle(material, Sphere(1.1));
    setUpParticle(basis_particle, ambient);
    basis_particle.setSliceNumber(TEST_SLICE);

    Mixture basis_mixture{};
    basis_mixture.addParticle(basis_particle, 0.4);
    basis_mixture.addParticle(basis_particle, 0.6);
    basis_mixture.validate();
    basis_mixture.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(8.0, 0.0, 0.0), R3(0.0, 8.0, 0.0), R3(0.0, 0.0, 8.0));
    const Crystal particle_crystal(basis_particle, lattice);
    const Crystal mixture_crystal(basis_mixture, lattice);

    Mesocrystal particle_meso(particle_crystal, Box(20.0, 20.0, 20.0));
    Mesocrystal mixture_meso(mixture_crystal, Box(20.0, 20.0, 20.0));
    particle_meso.setSliceNumber(TEST_SLICE);
    mixture_meso.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    particle_meso.prepareForComputation(ambient, meso_options);
    mixture_meso.prepareForComputation(ambient, meso_options);

    EXPECT_GT(particle_meso.numNodes(), 1u);
    EXPECT_EQ(mixture_meso.numNodes(), particle_meso.numNodes());

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    const auto particle_cm = particle_meso.channelMoments(dwba);
    const auto mixture_cm = mixture_meso.channelMoments(dwba);

    expectChannelMomentsNear(mixture_cm, particle_cm, 1e-11);
    expectZeroCovariance(mixture_cm, 1e-11);

    const auto particle_sm = particle_meso.scatteringMoments(ele->element);
    const auto mixture_sm = mixture_meso.scatteringMoments(ele->element);
    expectScatteringMomentsNear(mixture_sm, particle_sm, 1e-11);
}

// Enumerate all 2^N configurations of a small (4-node) binary mesocrystal and verify
// that the channel-moments approach reproduces the exact mean and variance.
TEST(DwbaChannelMoments, BruteForceSmallMesocrystal)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 cell_offset(5.0, 5.0, 0.0);
    constexpr double p_sphere = 0.35;
    constexpr double p_box = 0.65;

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, p_sphere, p_box, cell_offset);
    basis.setSliceNumber(TEST_SLICE);

    Particle shifted_sphere(material, Sphere(1.0));
    setUpParticle(shifted_sphere, ambient, cell_offset);
    Particle shifted_box(material, Box(0.8, 1.4, 1.1));
    setUpParticle(shifted_box, ambient, cell_offset);

    const Lattice3D lattice(R3(10.0, 0.0, 0.0), R3(0.0, 10.0, 0.0), R3(0.0, 0.0, 10.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(10.0, 10.0, 4.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 4u);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    const auto exact = bruteForceBinaryMesocrystal(dwba, mc.calcBasisPositions(), shifted_sphere,
                                                   p_sphere, shifted_box, p_box);
    const auto meso_cm = mc.channelMoments(dwba);
    const auto contracted = contractDwba(dwba, meso_cm);
    const auto meso_sm = mc.scatteringMoments(ele->element);

    expectScatteringMomentsNear(meso_sm, exact, 1e-11);
    expectScatteringMomentsNear(contracted, exact, 1e-11);
}

// Born variant of BruteForceSmallMesocrystal.  The Mesocrystal brute-force branch of
// channelMoments now skips zero-weight channels; the skip must not alter the numerical
// result, since the brute-force reference (which iterates all channels) contracts with
// the same w = {1, 0, 0, 0}. Regression guard for Task 1 (skip in brute-force branch).
TEST(DwbaChannelMoments, BruteForceSmallMesocrystalBorn)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 cell_offset(5.0, 5.0, 0.0);
    constexpr double p_sphere = 0.35;
    constexpr double p_box = 0.65;

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, p_sphere, p_box, cell_offset);
    basis.setSliceNumber(TEST_SLICE);

    Particle shifted_sphere(material, Sphere(1.0));
    setUpParticle(shifted_sphere, ambient, cell_offset);
    Particle shifted_box(material, Box(0.8, 1.4, 1.1));
    setUpParticle(shifted_box, ambient, cell_offset);

    const Lattice3D lattice(R3(10.0, 0.0, 0.0), R3(0.0, 10.0, 0.0), R3(0.0, 0.0, 10.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(10.0, 10.0, 4.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 4u);

    auto ele = makeTestDwbaElement(/*only_outgoing=*/true);
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    ASSERT_EQ(dwba.w[0], 1.0);
    for (size_t m = 1; m < N_DWBA; ++m)
        ASSERT_EQ(dwba.w[m], 0.0);

    const auto exact = bruteForceBinaryMesocrystal(dwba, mc.calcBasisPositions(), shifted_sphere,
                                                   p_sphere, shifted_box, p_box);
    const auto meso_cm = mc.channelMoments(dwba);
    const auto contracted = contractDwba(dwba, meso_cm);
    const auto meso_sm = mc.scatteringMoments(ele->element);

    expectScatteringMomentsNear(meso_sm, exact, 1e-11);
    expectScatteringMomentsNear(contracted, exact, 1e-11);
}

// A sliced mesocrystal that is cloned and re-prepared must produce the same
// scattering as the original.  This verifies that prepareForComputation is
// idempotent for an already-sliced mesocrystal.
TEST(DwbaChannelMoments, SlicedMesocrystalPreparationIsIdempotent)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle basis(material, Sphere(2.3));
    setUpParticle(basis, ambient);

    const Lattice3D lattice(R3(5.0, 0.0, 0.0), R3(0.0, 5.0, 0.0), R3(0.0, 0.0, 5.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal source(crystal, Cone(20.0, 50.0, 75.0 * Units::deg));

    // Slice the top 10 nm of a 50 nm-tall cone.
    Layer top_layer(ambient);
    top_layer.setNumberOfSlices(5);
    Slice slice(1, top_layer, Span(40.0, 50.0), R3{});

    std::unique_ptr<IParticle> shifted(source.clone());
    shifted->translate(R3(0.0, 0.0, -50.0));

    MesoOptions opts;
    auto pieces = Refine::particlesInSlice(shifted.get(), Span(-10.0, 0.0), &slice, ambient, opts);
    ASSERT_EQ(pieces.size(), 1u);
    auto* sliced = dynamic_cast<Mesocrystal*>(pieces.front().get());
    ASSERT_TRUE(sliced);

    std::unique_ptr<Mesocrystal> reprepared(sliced->clone());
    reprepared->prepareForComputation(ambient, opts);

    EXPECT_EQ(sliced->numNodes(), reprepared->numNodes());

    const WavevectorInfo wv(R3(0, 0, 0), R3(0.173, -0.071, 0.113), 1.54);
    expectComplexNear(sliced->theFF(wv), reprepared->theFF(wv), 1e-12);
}

// In the Born approximation (only_outgoing), only one DWBA channel is active, so
// the mesocrystal variance must equal N × basis variance (all transfer factors = N).
TEST(DwbaChannelMoments, BornLimitReducesToNodeCount)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 cell_offset(5.0, 5.0, 0.0);

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, 0.45, 0.55, cell_offset);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(10.0, 0.0, 0.0), R3(0.0, 10.0, 0.0), R3(0.0, 0.0, 10.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(10.0, 10.0, 4.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 4u);

    auto ele = makeTestDwbaElement(true);
    const auto basis_sm = basis.scatteringMoments(ele->element);
    const auto meso_sm = mc.scatteringMoments(ele->element);
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);

    EXPECT_EQ(dwba.w[0], complex_t(1.0));
    EXPECT_EQ(dwba.w[1], complex_t(0.0));
    EXPECT_EQ(dwba.w[2], complex_t(0.0));
    EXPECT_EQ(dwba.w[3], complex_t(0.0));
    EXPECT_NEAR(meso_sm.variance, mc.numNodes() * basis_sm.variance, 1e-11);
}

// Reciprocal-sum mode with a deterministic Compound basis: mu = FF, C = 0,
// and contracting the channel moments reproduces the scattering moments.
TEST(DwbaChannelMoments, ReciprocalMomentsAllowDeterministicCompound)
{
    const Vacuum ambient;
    auto mc = makeReciprocalDeterministicCompoundMesocrystal(ambient);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    const auto cm = mc->channelMoments(dwba);
    const auto sm = mc->scatteringMoments(ele->element);
    const auto contracted = contractDwba(dwba, cm);

    for (size_t m = 0; m < N_DWBA; ++m)
        expectComplexNear(cm.mu[m], mc->theFF(dwba.q[m]), 1e-12);
    expectZeroCovariance(cm, 1e-12);
    expectScatteringMomentsNear(sm, contracted, 1e-12);
    EXPECT_NEAR(contracted.variance, 0.0, 1e-14);
}

// A deterministic mesocrystal basis must be accepted in reciprocal-sum mode
// and still satisfy the deterministic-moments contract.
TEST(DwbaChannelMoments, ReciprocalMomentsAllowMesocrystalBasis)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle inner_basis(material, Sphere(0.9));
    setUpParticle(inner_basis, ambient, R3(0.4, -0.3, 0.6));
    inner_basis.setSliceNumber(TEST_SLICE);

    const Crystal inner_crystal(
        inner_basis, Lattice3D(R3(6.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0)));
    Mesocrystal inner(inner_crystal, Box(20.0, 20.0, 20.0));
    inner.translate(R3(0.9, -0.5, 1.1));
    inner.setSliceNumber(TEST_SLICE);

    const Crystal outer_crystal(
        inner, Lattice3D(R3(9.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0)));
    auto nested = std::make_unique<Mesocrystal>(outer_crystal, Box(24.0, 20.0, 20.0));
    nested->setSliceNumber(TEST_SLICE);

    prepareReciprocalMesocrystal(*nested, ambient);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    const auto cm = nested->channelMoments(dwba);
    const auto sm = nested->scatteringMoments(ele->element);
    const auto contracted = contractDwba(dwba, cm);

    for (size_t m = 0; m < N_DWBA; ++m)
        expectComplexNear(cm.mu[m], nested->theFF(dwba.q[m]), 1e-12);
    expectZeroCovariance(cm, 1e-12);
    expectScatteringMomentsNear(sm, contracted, 1e-12);
    EXPECT_NEAR(contracted.variance, 0.0, 1e-14);
}

// Reciprocal-sum mode must reject a Mixture basis (stochastic content is
// incompatible with the reciprocal-sum assumption).
TEST(DwbaChannelMoments, ReciprocalMomentsRejectMixtureBasis)
{
    const Vacuum ambient;
    auto mc = makeReciprocalMixtureBasisMesocrystal(ambient);

    EXPECT_THROW(prepareReciprocalMesocrystal(*mc, ambient), std::runtime_error);
}

// Reciprocal-sum mode must also reject a Compound that contains a Mixture
// anywhere in the hierarchy (stochastic content nested inside deterministic).
TEST(DwbaChannelMoments, ReciprocalMomentsRejectNestedMixtureBasis)
{
    const Vacuum ambient;
    auto mc = makeReciprocalNestedMixtureBasisMesocrystal(ambient);

    EXPECT_THROW(prepareReciprocalMesocrystal(*mc, ambient), std::runtime_error);
}

// Reciprocal-sum mode must reject a Mixture hidden inside a nested
// mesocrystal basis tree as well.
TEST(DwbaChannelMoments, ReciprocalMomentsRejectDoubleNestedMixtureBasis)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle left(material, Sphere(1.0));
    setUpParticle(left, ambient);
    Particle right(material, Sphere(1.6));
    setUpParticle(right, ambient);
    Mixture stochastic_child{};
    stochastic_child.addParticle(left, 1.0);
    stochastic_child.addParticle(right, 1.0);
    stochastic_child.validate();

    Compound inner_basis{};
    Particle center(material, Sphere(0.8));
    setUpParticle(center, ambient);
    inner_basis.addComponent(center);
    inner_basis.addComponent(stochastic_child);
    inner_basis.setSliceNumber(TEST_SLICE);

    Mesocrystal inner(
        Crystal(inner_basis, Lattice3D(R3(8.0, 0.0, 0.0), R3(0.0, 8.0, 0.0), R3(0.0, 0.0, 8.0))),
        Box(20.0, 20.0, 20.0));
    inner.translate(R3(0.4, -0.2, 0.6));
    inner.setSliceNumber(TEST_SLICE);

    Mesocrystal outer(
        Crystal(inner, Lattice3D(R3(12.0, 0.0, 0.0), R3(0.0, 12.0, 0.0), R3(0.0, 0.0, 12.0))),
        Box(30.0, 30.0, 30.0));
    outer.setSliceNumber(TEST_SLICE);

    EXPECT_THROW(prepareReciprocalMesocrystal(outer, ambient), std::runtime_error);
}

// Translating the mixture basis as a whole must give the same moments as
// translating each child particle individually (transform propagation).
TEST(DwbaChannelMoments, MesocrystalMixtureTransformEquivalentToChildTransformForMoments)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 shift(0.31, -0.17, 0.23);

    Mixture mix_with_parent_transform{};
    fillSphereBoxMixture(mix_with_parent_transform, material, ambient, 0.35, 0.65, shift);
    Particle shifted_sphere(material, Sphere(1.0));
    setUpParticle(shifted_sphere, ambient, shift);
    Particle shifted_box(material, Box(0.8, 1.4, 1.1));
    setUpParticle(shifted_box, ambient, shift);

    Mixture mix_with_child_transform{};
    mix_with_child_transform.addParticle(shifted_sphere, 0.35);
    mix_with_child_transform.addParticle(shifted_box, 0.65);
    mix_with_child_transform.validate();

    const Lattice3D lattice(R3(100.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0));
    const Crystal crystal_parent(mix_with_parent_transform, lattice);
    const Crystal crystal_child(mix_with_child_transform, lattice);

    Mesocrystal mc_parent(crystal_parent, Box(20.0, 20.0, 20.0));
    Mesocrystal mc_child(crystal_child, Box(20.0, 20.0, 20.0));
    mc_parent.setSliceNumber(TEST_SLICE);
    mc_child.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc_parent.prepareForComputation(ambient, meso_options);
    mc_child.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc_parent.numNodes(), 1u);
    EXPECT_EQ(mc_child.numNodes(), 1u);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    const auto parent_cm = mc_parent.channelMoments(dwba);
    const auto child_cm = mc_child.channelMoments(dwba);

    expectChannelMomentsNear(parent_cm, child_cm, 1e-12);
    expectScatteringMomentsNear(mc_parent.scatteringMoments(ele->element),
                                mc_child.scatteringMoments(ele->element), 1e-12);
}

// A mesocrystal with deterministic Compound basis must match the sum of the
// corresponding mesocrystals built from the Compound components separately.
TEST(DwbaChannelMoments, MesocrystalCompoundBasisMatchesCompoundOfMesocrystalsForMoments)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle basis_a(material, Sphere(0.9));
    setUpParticle(basis_a, ambient, R3(0.4, -0.3, 0.6));
    basis_a.setSliceNumber(TEST_SLICE);

    Particle basis_b(material, Box(0.7, 1.2, 0.8));
    setUpParticle(basis_b, ambient, R3(-0.5, 0.2, 0.4));
    basis_b.setSliceNumber(TEST_SLICE);

    Mesocrystal inner_a(
        Crystal(basis_a, Lattice3D(R3(6.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0))),
        Box(20.0, 20.0, 20.0));
    inner_a.translate(R3(0.9, -0.5, 1.1));
    inner_a.setSliceNumber(TEST_SLICE);

    Mesocrystal inner_b(
        Crystal(basis_b, Lattice3D(R3(100.0, 0.0, 0.0), R3(0.0, 6.5, 0.0), R3(0.0, 0.0, 100.0))),
        Box(20.0, 20.0, 20.0));
    inner_b.translate(R3(-0.6, 0.8, 0.7));
    inner_b.setSliceNumber(TEST_SLICE);

    Compound compound_basis{};
    compound_basis.addComponent(inner_a);
    compound_basis.addComponent(inner_b);
    compound_basis.setSliceNumber(TEST_SLICE);

    const Lattice3D outer_lattice(R3(9.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0));
    Mesocrystal nested(Crystal(compound_basis, outer_lattice), Box(24.0, 20.0, 20.0));
    nested.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    nested.prepareForComputation(ambient, meso_options);

    auto outer_a =
        std::make_unique<Mesocrystal>(Crystal(inner_a, outer_lattice), Box(24.0, 20.0, 20.0));
    outer_a->setSliceNumber(TEST_SLICE);
    outer_a->prepareForComputation(ambient, meso_options);

    auto outer_b =
        std::make_unique<Mesocrystal>(Crystal(inner_b, outer_lattice), Box(24.0, 20.0, 20.0));
    outer_b->setSliceNumber(TEST_SLICE);
    outer_b->prepareForComputation(ambient, meso_options);

    Compound distributed{};
    distributed.addComponent(std::move(outer_a));
    distributed.addComponent(std::move(outer_b));
    distributed.setSliceNumber(TEST_SLICE);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    expectChannelMomentsNear(nested.channelMoments(dwba), distributed.channelMoments(dwba), 1e-11);
    expectScatteringMomentsNear(nested.scatteringMoments(ele->element),
                                distributed.scatteringMoments(ele->element), 1e-11);
}

// A mesocrystal with nested mesocrystal-mixture basis must match the exact
// brute-force average over all outer-node A/B configurations.
TEST(DwbaChannelMoments, BruteForceSmallMesocrystalWithMesocrystalMixtureBasis)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 cell_offset(5.0, 5.0, 0.0);
    constexpr double p_a = 0.35;
    constexpr double p_b = 0.65;

    Particle basis_a(material, Sphere(0.9));
    setUpParticle(basis_a, ambient, R3(0.2, -0.1, 0.5));
    basis_a.setSliceNumber(TEST_SLICE);

    Particle basis_b(material, Box(0.7, 1.2, 0.8));
    setUpParticle(basis_b, ambient, R3(-0.3, 0.4, 0.2));
    basis_b.setSliceNumber(TEST_SLICE);

    Mesocrystal meso_a(
        Crystal(basis_a, Lattice3D(R3(6.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0))),
        Box(18.0, 18.0, 18.0));
    meso_a.translate(R3(0.7, -0.3, 0.9));
    meso_a.setSliceNumber(TEST_SLICE);

    Mesocrystal meso_b(
        Crystal(basis_b, Lattice3D(R3(100.0, 0.0, 0.0), R3(0.0, 6.5, 0.0), R3(0.0, 0.0, 100.0))),
        Box(18.0, 18.0, 18.0));
    meso_b.translate(R3(-0.6, 0.5, 0.8));
    meso_b.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    meso_a.prepareForComputation(ambient, meso_options);
    meso_b.prepareForComputation(ambient, meso_options);

    Mixture basis{};
    basis.addParticle(meso_a, p_a);
    basis.addParticle(meso_b, p_b);
    basis.validate();
    basis.translate(cell_offset);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D outer_lattice(R3(10.0, 0.0, 0.0), R3(0.0, 10.0, 0.0), R3(0.0, 0.0, 10.0));
    Mesocrystal mc(Crystal(basis, outer_lattice), Box(10.0, 10.0, 4.0));
    mc.setSliceNumber(TEST_SLICE);
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 4u);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    auto shifted_positions = mc.calcBasisPositions();
    for (auto& position : shifted_positions)
        position += cell_offset;
    const auto exact =
        bruteForceBinaryMesocrystal(dwba, shifted_positions, meso_a, p_a, meso_b, p_b);
    const auto meso_cm = mc.channelMoments(dwba);
    const auto contracted = contractDwba(dwba, meso_cm);
    const auto meso_sm = mc.scatteringMoments(ele->element);

    expectScatteringMomentsNear(meso_sm, exact, 1e-11);
    expectScatteringMomentsNear(contracted, exact, 1e-11);
}

// The covariance matrix C[m][n] must be positive semi-definite: v† C v ≥ 0
// for arbitrary coefficient vectors, both for the basis and the mesocrystal.
TEST(DwbaChannelMoments, CovarianceIsPositiveSemidefinite)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, 0.4, 0.6);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(8.0, 0.0, 0.0), R3(0.0, 8.0, 0.0), R3(0.0, 0.0, 8.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(20.0, 20.0, 20.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    const auto basis_cm = basis.channelMoments(dwba);
    const auto meso_cm = mc.channelMoments(dwba);

    const std::array<std::array<complex_t, N_DWBA>, 3> coeffs{
        std::array<complex_t, N_DWBA>{1.0, 0.0, 0.0, 0.0},
        std::array<complex_t, N_DWBA>{complex_t(1.0, 0.0), complex_t(0.3, -0.2),
                                      complex_t(-0.4, 0.1), complex_t(0.2, 0.5)},
        dwba.w};

    for (const auto& a : coeffs) {
        expectPositiveSemidefinite(basis_cm, a, 1e-11);
        expectPositiveSemidefinite(meso_cm, a, 1e-10);
    }
}

// Non-zero position variance (Debye–Waller) must attenuate the mean by the
// expected exp(-q²σ²/2) factor compared to the zero-variance case.
TEST(DwbaChannelMoments, PositionVarianceAffectsTransferFactors)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    constexpr double sigma = 0.37;

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, 0.4, 0.6);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(100.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0));
    const Crystal crystal_zero(basis, lattice, 0.0);
    const Crystal crystal_sigma(basis, lattice, sigma);
    Mesocrystal mc_zero(crystal_zero, Box(5.0, 5.0, 5.0));
    Mesocrystal mc_sigma(crystal_sigma, Box(5.0, 5.0, 5.0));
    mc_zero.setSliceNumber(TEST_SLICE);
    mc_sigma.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc_zero.prepareForComputation(ambient, meso_options);
    mc_sigma.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc_zero.numNodes(), 1u);
    EXPECT_EQ(mc_sigma.numNodes(), 1u);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    const auto cm_zero = mc_zero.channelMoments(dwba);
    const auto cm_sigma = mc_sigma.channelMoments(dwba);

    constexpr size_t m = 1;
    const complex_t expected_s =
        std::exp(-dwba.q[m].getQ().analytic_square() * sigma * sigma / 2.0);

    expectComplexNear(cm_sigma.mu[m], expected_s * cm_zero.mu[m], 1e-11);
    EXPECT_GT(std::abs(cm_sigma.mu[m] - cm_zero.mu[m]), 1e-8);
}

// Off-diagonal covariance C_meso[m][n] must equal T[m][n] × C_basis[m][n],
// where T is the lattice transfer factor (phase sum × Debye–Waller).
TEST(DwbaChannelMoments, MesocrystalOffDiagonalTransferMatchesFormula)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 cell_offset(5.0, 5.0, 0.0);
    constexpr double sigma = 0.37;

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, 0.45, 0.55, cell_offset);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(10.0, 0.0, 0.0), R3(0.0, 10.0, 0.0), R3(0.0, 0.0, 10.0));
    const Crystal crystal(basis, lattice, sigma);
    Mesocrystal mc(crystal, Box(10.0, 10.0, 4.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 4u);

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    const auto basis_cm = basis.channelMoments(dwba);
    const auto meso_cm = mc.channelMoments(dwba);

    constexpr size_t m = 1;
    constexpr size_t n = 2;
    const C3 dq = dwba.q[m].getQ() - dwba.q[n].getQ().conj();
    const complex_t expected_t = transferFactor(mc.calcBasisPositions(), dq, sigma);

    expectComplexNear(meso_cm.C[m][n], expected_t * basis_cm.C[m][n], 1e-11);
    expectComplexNear(meso_cm.C[n][m], std::conj(expected_t) * basis_cm.C[n][m], 1e-11);
}

// A mesocrystal with zero lattice nodes (basis outside outer shape) must
// return identically zero moments and scattering.
TEST(DwbaChannelMoments, EmptyMesocrystalMomentsAreZero)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle basis(material, Sphere(1.0));
    setUpParticle(basis, ambient, R3(50.0, 50.0, 50.0));
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(100.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(5.0, 5.0, 5.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 0u);
    EXPECT_TRUE(mc.calcBasisPositions().empty());

    auto ele = makeTestDwbaElement();
    const auto dwba = extractDwbaChannels(ele->element, TEST_SLICE);
    const auto cm = mc.channelMoments(dwba);
    const auto sm = mc.scatteringMoments(ele->element);

    for (size_t m = 0; m < N_DWBA; ++m) {
        expectComplexNear(cm.mu[m], complex_t(0.0), 1e-14);
        for (size_t n = 0; n < N_DWBA; ++n)
            expectComplexNear(cm.C[m][n], complex_t(0.0), 1e-14);
    }
    expectComplexNear(sm.mean, complex_t(0.0), 1e-14);
    EXPECT_NEAR(sm.variance, 0.0, 1e-14);
}

// --- Polarized DWBA channel moments tests ---

namespace {

struct PolDwbaElement {
    Pixel pixel;
    Fluxes fluxes_in;
    Fluxes fluxes_out;
    DiffuseElement element;

    PolDwbaElement(double wavelength, double alpha_i, double phi_i, double alpha_f, double phi_f,
                   const Spinor& k_eigen_in, const R3& b_in, double mag_sld_in,
                   const Spinor& k_eigen_out, const R3& b_out, double mag_sld_out,
                   bool only_outgoing, const SpinMatrix& polarizer)
        : pixel(Bin1D::At(phi_f), Bin1D::At(alpha_f))
        , fluxes_in()
        , fluxes_out()
        , element(wavelength, alpha_i, phi_i, &pixel, polarizer, SpinMatrix::One(), &fluxes_in,
                  &fluxes_out)
    {
        fluxes_in.emplace_back(std::make_unique<MatrixFlux>(-1.0, k_eigen_in, b_in, mag_sld_in));
        fluxes_out.emplace_back(
            std::make_unique<MatrixFlux>(+1.0, k_eigen_out, b_out, mag_sld_out));
        fluxes_in[0]->only_outgoing = only_outgoing;
        fluxes_out[0]->only_outgoing = only_outgoing;
    }
};

std::unique_ptr<PolDwbaElement> makeTestPolDwbaElement(bool only_outgoing = false)
{
    const R3 b(0.0, 0.0, 1.0);
    const SpinMatrix polarizer = SpinMatrix::FromBlochVector(R3(0.0, 0.0, 1.0));
    const auto& cfg = defaultScalarDwbaConfig();
    auto result = std::make_unique<PolDwbaElement>(
        cfg.wavelength, cfg.alpha_i, cfg.phi_i, cfg.alpha_f, cfg.phi_f,
        Spinor(complex_t(1.13, 0.07), complex_t(0.91, -0.03)), b, TEST_POL_DWBA_MAG_SLD,
        Spinor(complex_t(0.91, -0.05), complex_t(1.07, 0.02)), b, TEST_POL_DWBA_MAG_SLD,
        only_outgoing, polarizer);
    if (!only_outgoing) {
        // Set non-trivial T and R matrices so that T/R channels are independent.
        // Default m_T=I, m_R=-I makes R spinors proportional to T spinors (degenerate).
        auto* f_in = dynamic_cast<MatrixFlux*>(result->fluxes_in[0].get());
        auto* f_out = dynamic_cast<MatrixFlux*>(result->fluxes_out[0].get());
        f_in->m_T = SpinMatrix(complex_t(0.82, 0.04), complex_t(0.05, -0.02),
                               complex_t(-0.03, 0.01), complex_t(0.76, -0.03));
        f_in->m_R = SpinMatrix(complex_t(-0.21, 0.16), complex_t(0.08, 0.03),
                               complex_t(0.06, -0.04), complex_t(0.19, -0.12));
        f_out->m_T = SpinMatrix(complex_t(0.79, -0.02), complex_t(-0.04, 0.03),
                                complex_t(0.02, 0.01), complex_t(0.83, 0.05));
        f_out->m_R = SpinMatrix(complex_t(0.17, -0.09), complex_t(-0.06, 0.02),
                                complex_t(0.04, 0.03), complex_t(-0.15, 0.11));
    }
    return result;
}

// Counts scalar FF calls and checks that inactive DWBA channels are skipped.
TEST(DwbaChannelMoments, ScalarChannelMomentsSkipZeroWeightChannels)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    auto calls = std::make_shared<size_t>(0);

    Particle particle(material, CountingFormfactor(calls));
    setUpParticle(particle, ambient);
    particle.setSliceNumber(TEST_SLICE);

    auto born = makeTestDwbaElement(true);
    const auto born_channels = extractDwbaChannels(born->element, TEST_SLICE);
    particle.channelMoments(born_channels);
    EXPECT_EQ(*calls, born_channels.n_active);

    *calls = 0;
    auto dwba = makeTestDwbaElement(false);
    const auto dwba_channels = extractDwbaChannels(dwba->element, TEST_SLICE);
    particle.channelMoments(dwba_channels);
    EXPECT_EQ(*calls, dwba_channels.n_active);
}

// Counts polarized FF calls and checks that inactive DWBA channels are skipped.
TEST(DwbaChannelMoments, PolarizedChannelMomentsSkipZeroFluxChannels)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    auto calls = std::make_shared<size_t>(0);

    Particle particle(material, CountingFormfactor(calls));
    setUpParticle(particle, ambient);
    particle.setSliceNumber(TEST_SLICE);

    auto born = makeTestPolDwbaElement(true);
    const auto born_channels = extractPolDwbaChannels(born->element, TEST_SLICE);
    particle.polChannelMoments(born_channels);
    EXPECT_EQ(*calls, born_channels.n_active);

    *calls = 0;
    auto dwba = makeTestPolDwbaElement(false);
    const auto dwba_channels = extractPolDwbaChannels(dwba->element, TEST_SLICE);
    particle.polChannelMoments(dwba_channels);
    EXPECT_EQ(*calls, dwba_channels.n_active);
}

// Reciprocal mesocrystal should call scalar FF only for active channels.
TEST(DwbaChannelMoments, ReciprocalMesocrystalScalarChannelMomentsSkipZeroWeightChannels)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    auto calls = std::make_shared<size_t>(0);

    Particle basis(material, CountingFormfactor(calls));
    Mesocrystal mc(
        Crystal(basis, Lattice3D(R3(8.0, 0.0, 0.0), R3(0.0, 8.0, 0.0), R3(0.0, 0.0, 8.0))),
        Box(20.0, 20.0, 20.0));
    mc.setSliceNumber(TEST_SLICE);
    prepareReciprocalMesocrystal(mc, ambient);

    auto born = makeTestDwbaElement(true);
    const auto born_channels = extractDwbaChannels(born->element, TEST_SLICE);
    *calls = 0;
    mc.channelMoments(born_channels);
    const size_t born_actual = *calls;
    *calls = 0;
    for (size_t m = 0; m < born_channels.n_active; ++m)
        mc.theFF(born_channels.q[m]);
    EXPECT_EQ(born_actual, *calls);

    *calls = 0;
    auto dwba = makeTestDwbaElement(false);
    const auto dwba_channels = extractDwbaChannels(dwba->element, TEST_SLICE);
    mc.channelMoments(dwba_channels);
    const size_t dwba_actual = *calls;
    *calls = 0;
    for (size_t m = 0; m < dwba_channels.n_active; ++m)
        mc.theFF(dwba_channels.q[m]);
    EXPECT_EQ(dwba_actual, *calls);
}

// Reciprocal mesocrystal should call polarized FF only for active channels.
TEST(DwbaChannelMoments, ReciprocalMesocrystalPolarizedChannelMomentsSkipZeroFluxChannels)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    auto calls = std::make_shared<size_t>(0);

    Particle basis(material, CountingFormfactor(calls));
    Mesocrystal mc(
        Crystal(basis, Lattice3D(R3(8.0, 0.0, 0.0), R3(0.0, 8.0, 0.0), R3(0.0, 0.0, 8.0))),
        Box(20.0, 20.0, 20.0));
    mc.setSliceNumber(TEST_SLICE);
    prepareReciprocalMesocrystal(mc, ambient);

    auto born = makeTestPolDwbaElement(true);
    const auto born_channels = extractPolDwbaChannels(born->element, TEST_SLICE);
    *calls = 0;
    mc.polChannelMoments(born_channels);
    const size_t born_actual = *calls;
    *calls = 0;
    for (size_t m = 0; m < born_channels.n_active; ++m)
        mc.thePolFF(born_channels.q[m]);
    EXPECT_EQ(born_actual, *calls);

    *calls = 0;
    auto dwba = makeTestPolDwbaElement(false);
    const auto dwba_channels = extractPolDwbaChannels(dwba->element, TEST_SLICE);
    mc.polChannelMoments(dwba_channels);
    const size_t dwba_actual = *calls;
    *calls = 0;
    for (size_t m = 0; m < dwba_channels.n_active; ++m)
        mc.thePolFF(dwba_channels.q[m]);
    EXPECT_EQ(dwba_actual, *calls);
}

void expectSpinMatrixNear(const SpinMatrix& actual, const SpinMatrix& expected, double tol)
{
    expectComplexNear(actual.a, expected.a, tol);
    expectComplexNear(actual.b, expected.b, tol);
    expectComplexNear(actual.c, expected.c, tol);
    expectComplexNear(actual.d, expected.d, tol);
}

void expectPolScatteringMomentsNear(const PolScatteringMoments& actual,
                                    const PolScatteringMoments& expected, double tol)
{
    expectSpinMatrixNear(actual.mean, expected.mean, tol);
    expectSpinMatrixNear(actual.variance, expected.variance, tol);
}

void expectPolChannelMomentsNear(const PolChannelMoments& actual, const PolChannelMoments& expected,
                                 double tol)
{
    for (size_t m = 0; m < N_POL_DWBA; ++m) {
        expectSpinMatrixNear(actual.mu[m], expected.mu[m], tol);
        for (size_t n = 0; n < N_POL_DWBA; ++n)
            expectSpinMatrixNear(actual.C[m][n], expected.C[m][n], tol);
    }
}

void expectPolZeroCovariance(const PolChannelMoments& moments, double tol)
{
    for (size_t m = 0; m < N_POL_DWBA; ++m)
        for (size_t n = 0; n < N_POL_DWBA; ++n)
            expectSpinMatrixNear(moments.C[m][n], SpinMatrix(), tol);
}

PolScatteringMoments bruteForceBinaryMesocrystalPol(const PolDwbaChannels& dwba,
                                                    const std::vector<R3>& positions,
                                                    const IParticle& particle_a, double p_a,
                                                    const IParticle& particle_b, double p_b)
{
    SpinMatrix mean;
    SpinMatrix second_moment;
    const SpinMatrix& P = dwba.polarizer;
    const size_t n_configs = size_t{1} << positions.size();
    for (size_t mask = 0; mask < n_configs; ++mask) {
        double probability = 1.0;
        SpinMatrix amplitude;
        for (size_t i = 0; i < positions.size(); ++i) {
            const bool use_b = (mask >> i) & 1u;
            const IParticle& particle = use_b ? particle_b : particle_a;
            probability *= use_b ? p_b : p_a;
            for (size_t m = 0; m < dwba.n_active; ++m) {
                const complex_t phase =
                    std::exp(complex_t(0.0, 1.0) * positions[i].dot(dwba.q[m].getQ()));
                amplitude += applyWeight(phase * particle.thePolFF(dwba.q[m]), dwba.flux[m]);
            }
        }
        mean += probability * amplitude;
        second_moment += probability * amplitude * P * amplitude.adjoint();
    }
    return {mean, second_moment - mean * P * mean.adjoint()};
}

} // namespace

// Polarized analogue: a mixture of identical particles must reproduce the single
// particle's polarized channel moments, with zero covariance.
TEST(PolDwbaChannelMoments, IdenticalMixtureEquivalentToParticle)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle particle(material, Sphere(1.3));
    setUpParticle(particle, ambient);
    particle.setSliceNumber(TEST_SLICE);

    Mixture mixture{};
    mixture.addParticle(particle, 0.25);
    mixture.addParticle(particle, 0.75);
    mixture.validate();
    mixture.setSliceNumber(TEST_SLICE);

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);

    const auto particle_cm = particle.polChannelMoments(dwba);
    const auto mixture_cm = mixture.polChannelMoments(dwba);
    expectPolChannelMomentsNear(mixture_cm, particle_cm, 1e-12);
    expectPolZeroCovariance(mixture_cm, 1e-12);
}

// Contracting polarized channel moments with DWBA fluxes must reproduce
// the polarized scattering moments obtained directly.
TEST(PolDwbaChannelMoments, MixtureContractionMatchesPolScatteringMoments)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Mixture mixture{};
    fillSphereBoxMixture(mixture, material, ambient, 0.6, 0.4);
    mixture.setSliceNumber(TEST_SLICE);

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    const auto cm = mixture.polChannelMoments(dwba);
    const auto contracted = contractDwba(dwba, cm);
    const auto pol_sm = mixture.polScatteringMoments(ele->element);
    expectPolScatteringMomentsNear(contracted, pol_sm, 1e-11);
}

// Polarized analogue: a single-node mesocrystal must reproduce its basis's
// polarized channel moments (trivial lattice sums).
TEST(PolDwbaChannelMoments, SingleNodeMesocrystalMatchesBasis)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, 0.4, 0.6);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(100.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(5.0, 5.0, 5.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 1u);

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    const auto basis_cm = basis.polChannelMoments(dwba);
    const auto meso_cm = mc.polChannelMoments(dwba);
    expectPolChannelMomentsNear(meso_cm, basis_cm, 1e-11);
}

// Polarized Born limit: with a single active channel the polarized variance
// must scale by N (number of lattice nodes).
TEST(PolDwbaChannelMoments, BornLimitReducesToNodeCount)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 cell_offset(5.0, 5.0, 0.0);

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, 0.45, 0.55, cell_offset);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(10.0, 0.0, 0.0), R3(0.0, 10.0, 0.0), R3(0.0, 0.0, 10.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(10.0, 10.0, 4.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 4u);

    auto ele = makeTestPolDwbaElement(true);
    const auto basis_sm = basis.polScatteringMoments(ele->element);
    const auto meso_sm = mc.polScatteringMoments(ele->element);

    // In Born limit all T[m][n] = N, so variance scales by N.
    const double N = mc.numNodes();
    expectSpinMatrixNear(meso_sm.variance, N * basis_sm.variance, 1e-11);
}

// Polarized reciprocal-sum mode with a deterministic Compound: mu = polFF,
// C = 0, and contracting the channel moments reproduces the scattering moments.
TEST(PolDwbaChannelMoments, ReciprocalMomentsAllowDeterministicCompound)
{
    const Vacuum ambient;
    auto mc = makeReciprocalDeterministicCompoundMesocrystal(ambient);

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    const auto cm = mc->polChannelMoments(dwba);
    const auto sm = mc->polScatteringMoments(ele->element);
    const auto contracted = contractDwba(dwba, cm);

    for (size_t m = 0; m < N_POL_DWBA; ++m)
        expectSpinMatrixNear(cm.mu[m], mc->thePolFF(dwba.q[m]), 1e-12);
    expectPolZeroCovariance(cm, 1e-12);
    expectPolScatteringMomentsNear(sm, contracted, 1e-12);
    expectSpinMatrixNear(sm.variance, SpinMatrix(), 1e-14);
}

// Polarized reciprocal-sum mode must reject a Mixture basis.
TEST(PolDwbaChannelMoments, ReciprocalMomentsRejectMixtureBasis)
{
    const Vacuum ambient;
    auto mc = makeReciprocalMixtureBasisMesocrystal(ambient);

    EXPECT_THROW(prepareReciprocalMesocrystal(*mc, ambient), std::runtime_error);
}

// Polarized reciprocal-sum mode must reject a Compound containing a nested Mixture.
TEST(PolDwbaChannelMoments, ReciprocalMomentsRejectNestedMixtureBasis)
{
    const Vacuum ambient;
    auto mc = makeReciprocalNestedMixtureBasisMesocrystal(ambient);

    EXPECT_THROW(prepareReciprocalMesocrystal(*mc, ambient), std::runtime_error);
}

// Polarized reciprocal-sum mode must reject a Mixture hidden inside a nested
// mesocrystal basis tree as well.
TEST(PolDwbaChannelMoments, ReciprocalMomentsRejectDoubleNestedMixtureBasis)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle left(material, Sphere(1.0));
    setUpParticle(left, ambient);
    Particle right(material, Sphere(1.6));
    setUpParticle(right, ambient);
    Mixture stochastic_child{};
    stochastic_child.addParticle(left, 1.0);
    stochastic_child.addParticle(right, 1.0);
    stochastic_child.validate();

    Compound inner_basis{};
    Particle center(material, Sphere(0.8));
    setUpParticle(center, ambient);
    inner_basis.addComponent(center);
    inner_basis.addComponent(stochastic_child);
    inner_basis.setSliceNumber(TEST_SLICE);

    Mesocrystal inner(
        Crystal(inner_basis, Lattice3D(R3(8.0, 0.0, 0.0), R3(0.0, 8.0, 0.0), R3(0.0, 0.0, 8.0))),
        Box(20.0, 20.0, 20.0));
    inner.translate(R3(0.4, -0.2, 0.6));
    inner.setSliceNumber(TEST_SLICE);

    Mesocrystal outer(
        Crystal(inner, Lattice3D(R3(12.0, 0.0, 0.0), R3(0.0, 12.0, 0.0), R3(0.0, 0.0, 12.0))),
        Box(30.0, 30.0, 30.0));
    outer.setSliceNumber(TEST_SLICE);

    EXPECT_THROW(prepareReciprocalMesocrystal(outer, ambient), std::runtime_error);
}

// Polarized analogue: a mesocrystal with identical-particle mixture basis must
// match one with the single particle basis (no stochastic contrast).
TEST(PolDwbaChannelMoments, IdenticalMixtureBasisMatchesParticleBasis)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle basis_particle(material, Sphere(1.1));
    setUpParticle(basis_particle, ambient);
    basis_particle.setSliceNumber(TEST_SLICE);

    Mixture basis_mixture{};
    basis_mixture.addParticle(basis_particle, 0.4);
    basis_mixture.addParticle(basis_particle, 0.6);
    basis_mixture.validate();
    basis_mixture.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(8.0, 0.0, 0.0), R3(0.0, 8.0, 0.0), R3(0.0, 0.0, 8.0));
    const Crystal particle_crystal(basis_particle, lattice);
    const Crystal mixture_crystal(basis_mixture, lattice);

    Mesocrystal particle_meso(particle_crystal, Box(20.0, 20.0, 20.0));
    Mesocrystal mixture_meso(mixture_crystal, Box(20.0, 20.0, 20.0));
    particle_meso.setSliceNumber(TEST_SLICE);
    mixture_meso.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    particle_meso.prepareForComputation(ambient, meso_options);
    mixture_meso.prepareForComputation(ambient, meso_options);

    EXPECT_GT(particle_meso.numNodes(), 1u);
    EXPECT_EQ(mixture_meso.numNodes(), particle_meso.numNodes());

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    const auto particle_cm = particle_meso.polChannelMoments(dwba);
    const auto mixture_cm = mixture_meso.polChannelMoments(dwba);

    expectPolChannelMomentsNear(mixture_cm, particle_cm, 1e-11);
    expectPolZeroCovariance(mixture_cm, 1e-11);

    const auto particle_sm = particle_meso.polScatteringMoments(ele->element);
    const auto mixture_sm = mixture_meso.polScatteringMoments(ele->element);
    expectPolScatteringMomentsNear(mixture_sm, particle_sm, 1e-11);
}

// Polarized analogue: translating the mixture as a whole vs translating each
// child must give the same polarized channel moments.
TEST(PolDwbaChannelMoments, MesocrystalMixtureTransformEquivalentToChildTransformForMoments)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 shift(0.31, -0.17, 0.23);

    Mixture mix_with_parent_transform{};
    fillSphereBoxMixture(mix_with_parent_transform, material, ambient, 0.35, 0.65, shift);
    Particle shifted_sphere(material, Sphere(1.0));
    setUpParticle(shifted_sphere, ambient, shift);
    Particle shifted_box(material, Box(0.8, 1.4, 1.1));
    setUpParticle(shifted_box, ambient, shift);

    Mixture mix_with_child_transform{};
    mix_with_child_transform.addParticle(shifted_sphere, 0.35);
    mix_with_child_transform.addParticle(shifted_box, 0.65);
    mix_with_child_transform.validate();

    const Lattice3D lattice(R3(100.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0));
    const Crystal crystal_parent(mix_with_parent_transform, lattice);
    const Crystal crystal_child(mix_with_child_transform, lattice);

    Mesocrystal mc_parent(crystal_parent, Box(20.0, 20.0, 20.0));
    Mesocrystal mc_child(crystal_child, Box(20.0, 20.0, 20.0));
    mc_parent.setSliceNumber(TEST_SLICE);
    mc_child.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc_parent.prepareForComputation(ambient, meso_options);
    mc_child.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc_parent.numNodes(), 1u);
    EXPECT_EQ(mc_child.numNodes(), 1u);

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    const auto parent_cm = mc_parent.polChannelMoments(dwba);
    const auto child_cm = mc_child.polChannelMoments(dwba);

    expectPolChannelMomentsNear(parent_cm, child_cm, 1e-12);
    expectSpinMatrixNear(mc_parent.polScatteringMoments(ele->element).mean,
                         mc_child.polScatteringMoments(ele->element).mean, 1e-12);
}

// Polarized analogue: a mesocrystal with deterministic Compound basis must
// match the sum of the corresponding mesocrystals built separately.
TEST(PolDwbaChannelMoments, MesocrystalCompoundBasisMatchesCompoundOfMesocrystalsForMoments)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle basis_a(material, Sphere(0.9));
    setUpParticle(basis_a, ambient, R3(0.4, -0.3, 0.6));
    basis_a.setSliceNumber(TEST_SLICE);

    Particle basis_b(material, Box(0.7, 1.2, 0.8));
    setUpParticle(basis_b, ambient, R3(-0.5, 0.2, 0.4));
    basis_b.setSliceNumber(TEST_SLICE);

    Mesocrystal inner_a(
        Crystal(basis_a, Lattice3D(R3(6.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0))),
        Box(20.0, 20.0, 20.0));
    inner_a.translate(R3(0.9, -0.5, 1.1));
    inner_a.setSliceNumber(TEST_SLICE);

    Mesocrystal inner_b(
        Crystal(basis_b, Lattice3D(R3(100.0, 0.0, 0.0), R3(0.0, 6.5, 0.0), R3(0.0, 0.0, 100.0))),
        Box(20.0, 20.0, 20.0));
    inner_b.translate(R3(-0.6, 0.8, 0.7));
    inner_b.setSliceNumber(TEST_SLICE);

    Compound compound_basis{};
    compound_basis.addComponent(inner_a);
    compound_basis.addComponent(inner_b);
    compound_basis.setSliceNumber(TEST_SLICE);

    const Lattice3D outer_lattice(R3(9.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0));
    Mesocrystal nested(Crystal(compound_basis, outer_lattice), Box(24.0, 20.0, 20.0));
    nested.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    nested.prepareForComputation(ambient, meso_options);

    auto outer_a =
        std::make_unique<Mesocrystal>(Crystal(inner_a, outer_lattice), Box(24.0, 20.0, 20.0));
    outer_a->setSliceNumber(TEST_SLICE);
    outer_a->prepareForComputation(ambient, meso_options);

    auto outer_b =
        std::make_unique<Mesocrystal>(Crystal(inner_b, outer_lattice), Box(24.0, 20.0, 20.0));
    outer_b->setSliceNumber(TEST_SLICE);
    outer_b->prepareForComputation(ambient, meso_options);

    Compound distributed{};
    distributed.addComponent(std::move(outer_a));
    distributed.addComponent(std::move(outer_b));
    distributed.setSliceNumber(TEST_SLICE);

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    expectPolChannelMomentsNear(nested.polChannelMoments(dwba), distributed.polChannelMoments(dwba),
                                1e-11);
    const auto nested_sm = nested.polScatteringMoments(ele->element);
    const auto distributed_sm = distributed.polScatteringMoments(ele->element);
    expectSpinMatrixNear(nested_sm.mean, distributed_sm.mean, 1e-11);
    expectSpinMatrixNear(nested_sm.variance, distributed_sm.variance, 1e-11);
}

// Polarized analogue: a mesocrystal with nested mesocrystal-mixture basis
// must match the exact brute-force average over all outer-node configurations.
TEST(PolDwbaChannelMoments, BruteForceSmallMesocrystalWithMesocrystalMixtureBasis)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 cell_offset(5.0, 5.0, 0.0);
    constexpr double p_a = 0.35;
    constexpr double p_b = 0.65;

    Particle basis_a(material, Sphere(0.9));
    setUpParticle(basis_a, ambient, R3(0.2, -0.1, 0.5));
    basis_a.setSliceNumber(TEST_SLICE);

    Particle basis_b(material, Box(0.7, 1.2, 0.8));
    setUpParticle(basis_b, ambient, R3(-0.3, 0.4, 0.2));
    basis_b.setSliceNumber(TEST_SLICE);

    Mesocrystal meso_a(
        Crystal(basis_a, Lattice3D(R3(6.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0))),
        Box(18.0, 18.0, 18.0));
    meso_a.translate(R3(0.7, -0.3, 0.9));
    meso_a.setSliceNumber(TEST_SLICE);

    Mesocrystal meso_b(
        Crystal(basis_b, Lattice3D(R3(100.0, 0.0, 0.0), R3(0.0, 6.5, 0.0), R3(0.0, 0.0, 100.0))),
        Box(18.0, 18.0, 18.0));
    meso_b.translate(R3(-0.6, 0.5, 0.8));
    meso_b.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    meso_a.prepareForComputation(ambient, meso_options);
    meso_b.prepareForComputation(ambient, meso_options);

    Mixture basis{};
    basis.addParticle(meso_a, p_a);
    basis.addParticle(meso_b, p_b);
    basis.validate();
    basis.translate(cell_offset);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D outer_lattice(R3(10.0, 0.0, 0.0), R3(0.0, 10.0, 0.0), R3(0.0, 0.0, 10.0));
    Mesocrystal mc(Crystal(basis, outer_lattice), Box(10.0, 10.0, 4.0));
    mc.setSliceNumber(TEST_SLICE);
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 4u);

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    auto shifted_positions = mc.calcBasisPositions();
    for (auto& position : shifted_positions)
        position += cell_offset;
    const auto exact =
        bruteForceBinaryMesocrystalPol(dwba, shifted_positions, meso_a, p_a, meso_b, p_b);
    const auto meso_cm = mc.polChannelMoments(dwba);
    const auto contracted = contractDwba(dwba, meso_cm);
    const auto meso_sm = mc.polScatteringMoments(ele->element);

    expectPolScatteringMomentsNear(meso_sm, exact, 1e-11);
    expectPolScatteringMomentsNear(contracted, exact, 1e-11);
}

// Polarized analogue: a mesocrystal with zero nodes must return zero
// polarized moments and scattering.
TEST(PolDwbaChannelMoments, EmptyMesocrystalMomentsAreZero)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);

    Particle basis(material, Sphere(1.0));
    setUpParticle(basis, ambient, R3(50.0, 50.0, 50.0));
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(100.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(5.0, 5.0, 5.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 0u);

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    const auto cm = mc.polChannelMoments(dwba);
    const auto sm = mc.polScatteringMoments(ele->element);

    for (size_t m = 0; m < N_POL_DWBA; ++m)
        expectSpinMatrixNear(cm.mu[m], SpinMatrix(), 1e-14);
    expectPolZeroCovariance(cm, 1e-14);
    expectSpinMatrixNear(sm.mean, SpinMatrix(), 1e-14);
    expectSpinMatrixNear(sm.variance, SpinMatrix(), 1e-14);
}

// Polarized brute-force: enumerate all 2^N configurations of a small binary
// mesocrystal and verify that polarized scattering moments match exactly.
TEST(PolDwbaChannelMoments, BruteForceSmallMesocrystal)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 cell_offset(5.0, 5.0, 0.0);
    constexpr double p_sphere = 0.35;
    constexpr double p_box = 0.65;

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, p_sphere, p_box, cell_offset);
    basis.setSliceNumber(TEST_SLICE);

    Particle shifted_sphere(material, Sphere(1.0));
    setUpParticle(shifted_sphere, ambient, cell_offset);
    Particle shifted_box(material, Box(0.8, 1.4, 1.1));
    setUpParticle(shifted_box, ambient, cell_offset);

    const Lattice3D lattice(R3(10.0, 0.0, 0.0), R3(0.0, 10.0, 0.0), R3(0.0, 0.0, 10.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(10.0, 10.0, 4.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 4u);

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    const auto exact = bruteForceBinaryMesocrystalPol(dwba, mc.calcBasisPositions(), shifted_sphere,
                                                      p_sphere, shifted_box, p_box);
    const auto meso_sm = mc.polScatteringMoments(ele->element);

    expectPolScatteringMomentsNear(meso_sm, exact, 1e-11);
}

// Born variant: regression guard that skipping null-flux channels in the Mesocrystal
// brute-force polChannelMoments branch (Task 1) does not alter numerical output.
TEST(PolDwbaChannelMoments, BruteForceSmallMesocrystalBorn)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 cell_offset(5.0, 5.0, 0.0);
    constexpr double p_sphere = 0.35;
    constexpr double p_box = 0.65;

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, p_sphere, p_box, cell_offset);
    basis.setSliceNumber(TEST_SLICE);

    Particle shifted_sphere(material, Sphere(1.0));
    setUpParticle(shifted_sphere, ambient, cell_offset);
    Particle shifted_box(material, Box(0.8, 1.4, 1.1));
    setUpParticle(shifted_box, ambient, cell_offset);

    const Lattice3D lattice(R3(10.0, 0.0, 0.0), R3(0.0, 10.0, 0.0), R3(0.0, 0.0, 10.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(10.0, 10.0, 4.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 4u);

    auto ele = makeTestPolDwbaElement(/*only_outgoing=*/true);
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    ASSERT_EQ(dwba.n_active, 1u);

    const auto exact = bruteForceBinaryMesocrystalPol(dwba, mc.calcBasisPositions(), shifted_sphere,
                                                      p_sphere, shifted_box, p_box);
    const auto meso_sm = mc.polScatteringMoments(ele->element);

    expectPolScatteringMomentsNear(meso_sm, exact, 1e-11);
}

// Polarized Debye–Waller: non-zero position variance must attenuate the
// polarized mean by the expected exp(-q²σ²/2) factor.
TEST(PolDwbaChannelMoments, PositionVarianceAffectsTransferFactors)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    constexpr double sigma = 0.25;

    Particle basis(material, Sphere(1.0));
    setUpParticle(basis, ambient);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(100.0, 0.0, 0.0), R3(0.0, 100.0, 0.0), R3(0.0, 0.0, 100.0));
    const Crystal crystal_zero(basis, lattice, 0.0);
    const Crystal crystal_sigma(basis, lattice, sigma);
    Mesocrystal mc_zero(crystal_zero, Box(5.0, 5.0, 5.0));
    Mesocrystal mc_sigma(crystal_sigma, Box(5.0, 5.0, 5.0));
    mc_zero.setSliceNumber(TEST_SLICE);
    mc_sigma.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc_zero.prepareForComputation(ambient, meso_options);
    mc_sigma.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc_zero.numNodes(), 1u);
    EXPECT_EQ(mc_sigma.numNodes(), 1u);

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    const auto cm_zero = mc_zero.polChannelMoments(dwba);
    const auto cm_sigma = mc_sigma.polChannelMoments(dwba);

    constexpr size_t m = 1;
    const complex_t expected_s =
        std::exp(-dwba.q[m].getQ().analytic_square() * sigma * sigma / 2.0);

    expectSpinMatrixNear(cm_sigma.mu[m], expected_s * cm_zero.mu[m], 1e-11);
    // Verify sigma actually changes the result
    const SpinMatrix diff = cm_sigma.mu[m] - cm_zero.mu[m];
    double max_diff =
        std::max({std::abs(diff.a), std::abs(diff.b), std::abs(diff.c), std::abs(diff.d)});
    EXPECT_GT(max_diff, 1e-8);
}

// Polarized off-diagonal transfer: C_meso[m][n] = T[m][n] × C_basis[m][n]
// must hold for polarized SpinMatrix covariance entries.
TEST(PolDwbaChannelMoments, MesocrystalOffDiagonalTransferMatchesFormula)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    const R3 cell_offset(5.0, 5.0, 0.0);
    constexpr double sigma = 0.37;

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, 0.45, 0.55, cell_offset);
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(10.0, 0.0, 0.0), R3(0.0, 10.0, 0.0), R3(0.0, 0.0, 10.0));
    const Crystal crystal(basis, lattice, sigma);
    Mesocrystal mc(crystal, Box(10.0, 10.0, 4.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_EQ(mc.numNodes(), 4u);

    auto ele = makeTestPolDwbaElement();
    const auto dwba = extractPolDwbaChannels(ele->element, TEST_SLICE);
    const auto basis_cm = basis.polChannelMoments(dwba);
    const auto meso_cm = mc.polChannelMoments(dwba);

    constexpr size_t m = 1;
    constexpr size_t n = 2;
    const C3 dq = dwba.q[m].getQ() - dwba.q[n].getQ().conj();
    const complex_t expected_t = transferFactor(mc.calcBasisPositions(), dq, sigma);

    expectSpinMatrixNear(meso_cm.C[m][n], expected_t * basis_cm.C[m][n], 1e-11);
    expectSpinMatrixNear(meso_cm.C[n][m], std::conj(expected_t) * basis_cm.C[n][m], 1e-11);
}

// --- Cross-validation: scalar vs polarized for non-magnetic systems ---

namespace {

//! Creates a polarized DWBA element with degenerate eigenmodes (kz.u = kz.v)
//! and diagonal T/R matrices. This gives a clean scalar-like channel structure
//! while still exercising the full polarized DWBA machinery.
std::unique_ptr<PolDwbaElement> makeDiagonalPolDwbaElement(complex_t kz_in, complex_t T_in,
                                                           complex_t R_in, complex_t kz_out,
                                                           complex_t T_out, complex_t R_out,
                                                           const SpinMatrix& polarizer)
{
    const R3 b(0, 0, 0); // no magnetic field in the layer
    const auto& cfg = defaultScalarDwbaConfig();
    auto result = std::make_unique<PolDwbaElement>(
        cfg.wavelength, cfg.alpha_i, cfg.phi_i, cfg.alpha_f, cfg.phi_f, Spinor(kz_in, kz_in), b,
        0.0, Spinor(kz_out, kz_out), b, 0.0, false, polarizer);
    auto* f_in = dynamic_cast<MatrixFlux*>(result->fluxes_in[0].get());
    auto* f_out = dynamic_cast<MatrixFlux*>(result->fluxes_out[0].get());
    f_in->m_T = SpinMatrix(T_in, complex_t(0), complex_t(0), T_in);
    f_in->m_R = SpinMatrix(R_in, complex_t(0), complex_t(0), R_in);
    f_out->m_T = SpinMatrix(T_out, complex_t(0), complex_t(0), T_out);
    f_out->m_R = SpinMatrix(R_out, complex_t(0), complex_t(0), R_out);
    return result;
}

std::unique_ptr<PolDwbaElement> makeNonMagneticPolElement(complex_t kz_in, complex_t T_in,
                                                          complex_t R_in, complex_t kz_out,
                                                          complex_t T_out, complex_t R_out)
{
    return makeDiagonalPolDwbaElement(kz_in, T_in, R_in, kz_out, T_out, R_out,
                                      SpinMatrix::One() / 2.0);
}

double traceReal(const SpinMatrix& matrix)
{
    return matrix.trace().real();
}

SpinMatrix totalPolarizedIntensityMatrix(const DiffuseElement& ele, const PolScatteringMoments& sm)
{
    const SpinMatrix coherent = sm.mean * ele.polarizer() * sm.mean.adjoint();
    return ele.analyzer() * (sm.variance + coherent);
}

} // namespace

// For a non-magnetic mesocrystal, the ratio tr(pol_variance)/scalar_variance must
// be the same for the basis and the full mesocrystal (lattice factors are spin-independent).
TEST(CrossValidation, NonMagneticMesocrystalScalarPolConsistency)
{
    const Vacuum ambient;
    const SLDMaterial material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD,
                               0.0); // non-magnetic
    const auto& dwba_cfg = defaultScalarDwbaConfig();

    Mixture basis{};
    fillSphereBoxMixture(basis, material, ambient, 0.4, 0.6, R3(5.0, 5.0, 0.0));
    basis.setSliceNumber(TEST_SLICE);

    const Lattice3D lattice(R3(10.0, 0.0, 0.0), R3(0.0, 10.0, 0.0), R3(0.0, 0.0, 10.0));
    const Crystal crystal(basis, lattice);
    Mesocrystal mc(crystal, Box(10.0, 10.0, 4.0));
    mc.setSliceNumber(TEST_SLICE);

    MesoOptions meso_options;
    meso_options.use_reciprocal_sum = false;
    mc.prepareForComputation(ambient, meso_options);

    EXPECT_GT(mc.numNodes(), 1u);

    // Matching scalar and polarized elements with same kz and T/R coefficients
    auto s_ele = makeTestDwbaElement();
    auto p_ele = makeNonMagneticPolElement(dwba_cfg.kz_in, dwba_cfg.T_in, dwba_cfg.R_in,
                                           dwba_cfg.kz_out, dwba_cfg.T_out, dwba_cfg.R_out);

    const auto s_sm = mc.scatteringMoments(s_ele->element);
    const auto p_sm = mc.polScatteringMoments(p_ele->element);

    // First verify the scalar/pol relationship for the basis Mixture itself.
    const auto s_basis = basis.scatteringMoments(s_ele->element);
    const auto p_basis = basis.polScatteringMoments(p_ele->element);
    const double basis_ratio = traceReal(p_basis.variance) / s_basis.variance;

    // The ratio depends on the DWBA weight structure but should be consistent
    // between basis and mesocrystal — the lattice transfer factors are the same
    // for both scalar and polarized (they depend only on wavevectors, not spin).
    const double meso_ratio = traceReal(p_sm.variance) / s_sm.variance;
    EXPECT_NEAR(meso_ratio, basis_ratio, 1e-8 * std::abs(basis_ratio));
    EXPECT_GT(s_sm.variance, 0.0);
}

// A magnetic mixture's total polarized intensity can be matched by a scalar sample
// with rescaled SLD, but the magnetic case shows spin asymmetry (a ≠ d in the
// intensity matrix) that the non-magnetic case does not.
TEST(CrossValidation, MagneticMixtureMatchesCorrespondingScalarIntensityAndShowsSpinAsymmetry)
{
    const Vacuum ambient;
    const SLDMaterial nonmag_material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0);
    constexpr double M_z = 1.0e6;
    const SLDMaterial mag_material("particle", TEST_MATERIAL_COLOR, TEST_PARTICLE_SLD, 0.0,
                                   R3(0, 0, M_z));
    const auto& dwba_cfg = defaultScalarDwbaConfig();

    Mixture nonmag_basis{};
    fillSphereBoxMixture(nonmag_basis, nonmag_material, ambient, 0.4, 0.6);
    nonmag_basis.setSliceNumber(TEST_SLICE);

    Mixture mag_basis{};
    fillSphereBoxMixture(mag_basis, mag_material, ambient, 0.4, 0.6);
    mag_basis.setSliceNumber(TEST_SLICE);

    auto scalar_ele = makeTestDwbaElement();
    auto pol_ele =
        makeDiagonalPolDwbaElement(dwba_cfg.kz_in, dwba_cfg.T_in, dwba_cfg.R_in, dwba_cfg.kz_out,
                                   dwba_cfg.T_out, dwba_cfg.R_out, SpinMatrix::One() / 2.0);

    const Dilute2D nonmag_scalar_sample(1.0, nonmag_basis);
    const Dilute2D mag_pol_sample(1.0, mag_basis);

    const auto nonmag_sm = nonmag_basis.polScatteringMoments(pol_ele->element);
    const auto mag_sm = mag_basis.polScatteringMoments(pol_ele->element);
    const double target_intensity = mag_pol_sample.polarizedScattering(pol_ele->element);

    const double reference_scalar_intensity =
        nonmag_scalar_sample.scalarScattering(scalar_ele->element);
    ASSERT_GT(reference_scalar_intensity, 0.0);
    const double matched_sld = std::real(nonmag_material.sld(dwba_cfg.wavelength))
                               * std::sqrt(target_intensity / reference_scalar_intensity);

    const SLDMaterial scalar_match_material("particle", TEST_MATERIAL_COLOR, matched_sld, 0.0);
    Mixture scalar_basis{};
    fillSphereBoxMixture(scalar_basis, scalar_match_material, ambient, 0.4, 0.6);
    scalar_basis.setSliceNumber(TEST_SLICE);

    const Dilute2D scalar_match_sample(1.0, scalar_basis);

    const double scalar_match_intensity = scalar_match_sample.scalarScattering(scalar_ele->element);
    EXPECT_NEAR(scalar_match_intensity, target_intensity, 1e-10 * std::abs(target_intensity));

    const SpinMatrix mag_total_matrix = totalPolarizedIntensityMatrix(pol_ele->element, mag_sm);
    const SpinMatrix nonmag_total_matrix =
        totalPolarizedIntensityMatrix(pol_ele->element, nonmag_sm);
    const complex_t mag_spin_split = mag_total_matrix.a - mag_total_matrix.d;
    const complex_t nonmag_spin_split = nonmag_total_matrix.a - nonmag_total_matrix.d;
    EXPECT_GT(std::abs(mag_spin_split - nonmag_spin_split), 1e-12);
}
