diff --git a/genetIC/src/ic.hpp b/genetIC/src/ic.hpp index b95edab7..6e9bcb53 100644 --- a/genetIC/src/ic.hpp +++ b/genetIC/src/ic.hpp @@ -1659,7 +1659,7 @@ class ICGenerator { } //! Splicing: fixes the flagged region, while reinitialising the exterior from a new random field - virtual void splice(size_t newSeed) { + virtual void splice(size_t newSeed, T accuracy) { initialiseRandomComponentIfUninitialised(); if(outputFields.size()>1) throw std::runtime_error("Splicing is not yet implemented for the case of multiple transfer functions"); @@ -1680,14 +1680,16 @@ class ICGenerator { newGenerator.draw(); logging::entry() << "Finished constructing new random field. Beginning splice operation." << endl; - for(size_t level=0; levelgetFieldForLevel(level); - auto &newFieldThisLevel = newField.getFieldForLevel(level); - auto splicedFieldThisLevel = modifications::spliceOneLevel(newFieldThisLevel, originalFieldThisLevel, - *multiLevelContext.getCovariance(level, particle::species::all)); - splicedFieldThisLevel.toFourier(); - originalFieldThisLevel = std::move(splicedFieldThisLevel); - } + modifications::splice(newField, *(outputFields[0]), accuracy); + + // for(size_t level=0; levelgetFieldForLevel(level); + // auto &newFieldThisLevel = newField.getFieldForLevel(level); + // auto splicedFieldThisLevel = modifications::spliceOneLevel(newFieldThisLevel, originalFieldThisLevel, + // *multiLevelContext.getCovariance(level, particle::species::all)); + // splicedFieldThisLevel.toFourier(); + // originalFieldThisLevel = std::move(splicedFieldThisLevel); + // } } //! Reverses the sign of the low-k modes. diff --git a/genetIC/src/simulation/field/multilevelfield.hpp b/genetIC/src/simulation/field/multilevelfield.hpp index d4593f5a..8f20cbdb 100644 --- a/genetIC/src/simulation/field/multilevelfield.hpp +++ b/genetIC/src/simulation/field/multilevelfield.hpp @@ -168,6 +168,11 @@ namespace fields { addScaled(other, 1.0); } + //! Subtracts the specified multi-level field to this one. + void operator-=(const MultiLevelField &other) { + addScaled(other, -1.0); + } + //! Divides the field by the specified ratio. void operator/=(DataType ratio) { using namespace tools::numerics; @@ -178,6 +183,15 @@ namespace fields { } } + void operator*=(DataType val) { + using namespace tools::numerics; + + for (size_t level = 0; level < getNumLevels(); level++) { + auto &data = getFieldForLevel(level).getDataVector(); + data *= val; + } + } + //! Flips the sign of the field. void reverse() { for (size_t level = 0; level < getNumLevels(); ++level) { @@ -193,19 +207,17 @@ namespace fields { //! Add a scaled multilevel field to the current one void addScaled(const MultiLevelField &other, DataType scale) { assertContextConsistent(); - assert(other.isFourierOnAllLevels()); + if (other.isFourierOnAllLevels()) toFourier(); + else if (other.isRealOnAllLevels()) toReal(); + else throw std::runtime_error("Incompatible field types"); + assert (isCompatible(other)); - toFourier(); for (size_t level = 0; level < getNumLevels(); level++) { if (hasFieldForLevel(level) && other.hasFieldForLevel(level)) { Field &fieldThis = getFieldForLevel(level); const Field &fieldOther = other.getFieldForLevel(level); - T kMin = fieldThis.getGrid().getFourierKmin(); - fieldThis.forEachFourierCellInt([&fieldOther, kMin, scale] - (ComplexType currentVal, int kx, int ky, int kz) { - return currentVal + scale * fieldOther.getFourierCoefficient(kx, ky, kz); - }); + fieldThis.addScaled(fieldOther, scale); } } @@ -569,6 +581,14 @@ namespace fields { } + //! Returns a constant reference to the field on the specified grid, assuming it has been populated. + const Field &getFieldForLevel(size_t i) const override { + this->assertContextConsistent(); + assert(i < this->fieldsOnLevels.size()); + + return *(this->fieldsOnLevels[i]); + } + }; diff --git a/genetIC/src/simulation/modifications/splice.hpp b/genetIC/src/simulation/modifications/splice.hpp index 2cda17f4..5ca6f6c6 100644 --- a/genetIC/src/simulation/modifications/splice.hpp +++ b/genetIC/src/simulation/modifications/splice.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace modifications { template @@ -33,86 +34,289 @@ namespace modifications { return mask; } - //! Return the field f which satisfies f = a in flagged region while minimising (f-b).C^-1.(f-b) elsewhere + // Compute the operator that applies T^+ ... T *at all levels* template> - fields::Field spliceOneLevel(fields::Field & a, - fields::Field & b, - const fields::Field & cov) { + fields::OutputField T_op_T ( + const fields::OutputField &inputs, + const std::vector> &covs, + const std::function &)> op + ) { + fields::OutputField outputs(inputs.getContext(), inputs.getTransferType()); + outputs.getFieldForLevel(0); // trigger allocation + + fields::OutputField inputs_delta(inputs); + auto filters = inputs.getFilters(); + int Nlevel = inputs.getNumLevels(); + + const auto& multiLevelContext = outputs.getContext(); + + assert (inputs.isRealOnAllLevels()); + + // Apply C^0.5 to the input fields + for (size_t level=0; leveltoReal(); + *source_field *= sqrt(pixel_volume_ratio); + + out->addFieldFromDifferentGrid(*source_field); + } + + // Apply operator + op(level, *out); + + // Add contributions from all finer levels + for (size_t source_level = level + 1; source_level < Nlevel; ++source_level) { + auto source_field = inputs_delta.getFieldForLevel(source_level).copy(); + T pixel_volume_ratio = multiLevelContext.getWeightForLevel(level) / + multiLevelContext.getWeightForLevel(source_level); + + source_field->toFourier(); + *source_field *= sqrt(pixel_volume_ratio); + op(source_level, *source_field); + + out->addFieldFromDifferentGrid(*source_field); + } + + outputs.getFieldForLevel(level) = std::move(*out); + } + + // Filter all levels (but the last) in their windows + // Notice we are doing filter then window (the opposite order to above) + for (size_t level=0; level> + fields::OutputField Mbar_Cm1_Mbar( + const fields::OutputField & inputs, + const auto& covs, const auto& masks, const auto& masksCompl, size_t Nlevel + ) { + auto outputs = T_op_T( + inputs, + covs, + [&](const int level, fields::Field & input) -> void + { + input.toReal(); + input *= masksCompl[level]; + input.toFourier(); + input.applyTransferFunction(covs[level], -1.0); + input.toReal(); + input *= masksCompl[level]; + }); + return outputs; + } + + template> + fields::OutputField Mbar_Cm1_M( + const fields::OutputField & inputs, + const auto& covs, const auto& masks, const auto& masksCompl, size_t Nlevel + ) { + auto outputs = T_op_T( + inputs, covs, + [&](const int level, fields::Field & input) -> void + { + input.toReal(); + input *= masks[level]; + input.toFourier(); + input.applyTransferFunction(covs[level], -1.0); + input.toReal(); + input *= masksCompl[level]; + }); + return outputs; + }; + + template> + fields::OutputField combine( + const fields::OutputField &a, + const std::vector> &covs + ) { + fields::OutputField inputs(a); + const auto& multiLevelContext = inputs.getContext(); + auto filters = a.getFilters(); + int Nlevel = a.getNumLevels(); + + for (size_t level=0; level outputs(inputs); + + // Add contribution from coarser level + for (size_t level = 1; level> + fields::OutputField splice(fields::OutputField & a, + fields::OutputField & b, + T accuracy) { + + assert (a.getTransferType() == particle::species::whitenoise); + assert (b.getTransferType() == particle::species::whitenoise); + assert (a.isFourierOnAllLevels()); + assert (b.isFourierOnAllLevels()); // To understand the implementation below, first read Appendix A of Cadiou et al (2021), // and/or look at the 1D toy implementation (in tools/toy_implementation/gene_splicing.ipynb) which // contains a similar derivation and near-identical implementation. - assert (&a.getGrid() == &b.getGrid()); - assert (&a.getGrid() == &cov.getGrid()); - auto mask = generateMaskFromFlags(a.getGrid()); - auto maskCompl = generateMaskComplementFromFlags(a.getGrid()); - - a.toFourier(); - b.toFourier(); - - // The preconditioner should be almost equal to the covariance. - // We however set the fundamental of the power spectrum to a non-null value, - // otherwise, the mean value in the spliced region is unconstrained. - fields::Field preconditioner(cov); - preconditioner.setFourierCoefficient(0, 0, 0, 1); - - fields::Field delta_diff = b-a; - delta_diff.applyTransferFunction(preconditioner, 0.5); - delta_diff.toReal(); - - - fields::Field z(delta_diff); - z*=mask; - z.toFourier(); - z.applyTransferFunction(preconditioner, -1.0); - z.toReal(); - z*=maskCompl; - z.toFourier(); - z.applyTransferFunction(preconditioner, 0.5); - z.toReal(); - - auto X = [&preconditioner, &maskCompl](const fields::Field & input) -> fields::Field - { - fields::Field v(input); - assert (!input.isFourier()); - assert (!v.isFourier()); - v.toFourier(); - v.applyTransferFunction(preconditioner, 0.5); - v.toReal(); - v*=maskCompl; - v.toFourier(); - v.applyTransferFunction(preconditioner, -1.0); - v.toReal(); - v*=maskCompl; - v.toFourier(); - v.applyTransferFunction(preconditioner, 0.5); - v.toReal(); - return v; + std::vector> covs; + std::vector> masks; + std::vector> masksCompl; + + int Nlevel = a.getNumLevels(); + for(size_t level=0; level cov(*ctxt.getCovariance(level, particle::species::all)); + cov.setFourierCoefficient(0, 0, 0, 1); + covs.push_back(cov); + + masks.push_back(generateMaskFromFlags(ctxt.getGridForLevel(level))); + masksCompl.push_back(generateMaskComplementFromFlags(ctxt.getGridForLevel(level))); + } + + a.toReal(); + b.toReal(); + + fields::OutputField delta(b); + delta -= a; + + fields::OutputField z = Mbar_Cm1_M(delta, covs, masks, masksCompl, Nlevel); + + auto A = [&](const fields::OutputField & inputs) { + return Mbar_Cm1_Mbar(inputs, covs, masks, masksCompl, Nlevel); }; - fields::Field alpha = tools::numerics::conjugateGradient(X,z); + // fields::OutputField alpha = tools::numerics::conjugateGradient(A, z); + fields::OutputField alpha = tools::numerics::minres(A, z, accuracy); + + // Combine fields + alpha = combine(alpha, covs); + a = combine(a, covs); + b = combine(b, covs); + a.toReal(); b.toReal(); alpha.toReal(); + + // output = b + M(a - b) + Mbar alpha [all in delta basis now] + fields::OutputField outputs(b.getContext(), particle::species::all); + outputs.getFieldForLevel(0); // trigger allocation + outputs.toReal(); + + for (size_t level = 0; level < Nlevel; ++level) { + const auto & a_field = a.getFieldForLevel(level); + const auto & b_field = b.getFieldForLevel(level); + const auto & alpha_field = alpha.getFieldForLevel(level); + + auto & out = outputs.getFieldForLevel(level); + out += b_field; + + { + auto temp = a_field.copy(); + *temp -= b_field; + *temp *= masks[level]; + + out += *temp; + } + + { + auto temp = alpha_field.copy(); + *temp *= masksCompl[level]; + + out += *temp; + } + out.toReal(); + + a_field.dumpGridData("a_" + std::to_string(level) + ".dat"); + b_field.dumpGridData("b_" + std::to_string(level) + ".dat"); + alpha_field.dumpGridData("alpha_" + std::to_string(level) + ".dat"); + out.dumpGridData("splice_level_" + std::to_string(level) + ".dat"); + } - alpha.toFourier(); - alpha.applyTransferFunction(preconditioner, 0.5); - alpha.toReal(); + // alpha.toFourier(); + // alpha.applyTransferFunction(preconditioner, 0.5); + // alpha.toReal(); - fields::Field bInDeltaBasis(b); - bInDeltaBasis.toFourier(); - bInDeltaBasis.applyTransferFunction(preconditioner, 0.5); - bInDeltaBasis.toReal(); + // fields::Field bInDeltaBasis(b); + // bInDeltaBasis.toFourier(); + // bInDeltaBasis.applyTransferFunction(preconditioner, 0.5); + // bInDeltaBasis.toReal(); - alpha*=maskCompl; - alpha+=bInDeltaBasis; + // alpha*=maskCompl; + // alpha+=bInDeltaBasis; - delta_diff*=mask; - alpha-=delta_diff; + // delta_diff*=mask; + // alpha-=delta_diff; - assert(!alpha.isFourier()); - alpha.toFourier(); - alpha.applyTransferFunction(preconditioner, -0.5); - alpha.toReal(); + // assert(!alpha.isFourier()); + // alpha.toFourier(); + // alpha.applyTransferFunction(preconditioner, -0.5); + // alpha.toReal(); return alpha; } diff --git a/genetIC/src/tools/numerics/cg.hpp b/genetIC/src/tools/numerics/cg.hpp index bce1cf18..2ac765e5 100644 --- a/genetIC/src/tools/numerics/cg.hpp +++ b/genetIC/src/tools/numerics/cg.hpp @@ -9,45 +9,69 @@ namespace tools { namespace numerics { + template + T innerProduct(const fields::OutputField &a, const fields::OutputField &b) { + T result = 0; + for (auto ilevel = 0; ilevel < a.getNumLevels(); ++ilevel) { + const auto& left = a.getFieldForLevel(ilevel); + const auto& right = b.getFieldForLevel(ilevel); + result += left.innerProduct(right); + } + return result; + } + + template + double norm(const fields::OutputField &a) { + return std::sqrt(innerProduct(a, a)); + } + //! Solve linear equation Qx = b, and return x, using conjugate gradient template - fields::Field conjugateGradient(std::function(const fields::Field &)> Q, - const fields::Field &b, + fields::OutputField conjugateGradient(std::function(const fields::OutputField &)> Q, + const fields::OutputField &b, double rtol = 1e-6, double atol = 1e-12) { - fields::Field residual(b); - fields::Field direction = -residual; - fields::Field x = fields::Field(b.getGrid(), false); + fields::OutputField residual(b); + fields::OutputField direction(residual); + direction *= -1; + fields::OutputField x = fields::OutputField(b.getContext(), b.getTransferType()); + x.getFieldForLevel(0); // trigger allocation - double scale = b.norm(); + double scale = norm(residual); if(scale==0.0) { logging::entry(logging::warning) << "Conjugate gradient: result is zero!" << std::endl; return x; } - size_t dimension = b.getGrid().size3; + size_t dimension = 0; + for (auto ilevel = 0; ilevel < b.getNumLevels(); ++ilevel) { + const auto ctxt = residual.getContext(); + dimension += ctxt.getGridForLevel(ilevel).size3; + } size_t i; for(i=0; i Q_direction = Q(direction); + auto Q_direction = Q(direction); + // distance to travel in specified direction - double alpha = -residual.innerProduct(direction) / direction.innerProduct(Q_direction); + double alpha = -innerProduct(residual, direction) / innerProduct(direction, Q_direction); + x.addScaled(direction, alpha); residual = Q(x); residual -= b; - auto norm = residual.norm(); - if (norm < rtol * scale || norm < atol) + auto res_norm = norm(residual); + if (res_norm < rtol * scale || res_norm < atol) break; - logging::entry() << "Conjugate gradient iteration " << i << " residual=" << norm << std::endl; + logging::entry() << "Conjugate gradient iteration " << i << " residual=" << res_norm << "/" << scale << std::endl; // update direction for next cycle; must be Q-orthogonal to all previous updates - double beta = residual.innerProduct(Q_direction) / direction.innerProduct(Q_direction); + double beta = innerProduct(residual, Q_direction) / innerProduct(direction, Q_direction); direction*=beta; direction-=residual; diff --git a/genetIC/src/tools/numerics/minres.hpp b/genetIC/src/tools/numerics/minres.hpp new file mode 100644 index 00000000..8c8d325e --- /dev/null +++ b/genetIC/src/tools/numerics/minres.hpp @@ -0,0 +1,80 @@ +#ifndef IC_MINRES_HPP +#define IC_MINRES_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cg.hpp" + +namespace tools { + namespace numerics { + + /* Adapted from https://stanford.edu/group/SOL/reports/SOL-2011-2R.pdf */ + template + fields::OutputField minres(std::function(const fields::OutputField &)> A, + const fields::OutputField &b, + double rtol = 1e-6, + double atol = 1e-12 ) + { + fields::OutputField x(b.getContext(), b.getTransferType()); + x.getFieldForLevel(0); // Trigger allocation + fields::OutputField r(b); + fields::OutputField s(b); + s = A(r); + + fields::OutputField p(r); + fields::OutputField q(s); + + double scale = tools::numerics::norm(b); + double rho = tools::numerics::innerProduct(r, s); + + size_t dimension = 0; + for (auto ilevel = 0; ilevel < b.getNumLevels(); ++ilevel) { + const auto ctxt = r.getContext(); + dimension += ctxt.getGridForLevel(ilevel).size3; + } + + size_t max_iterations = dimension * 10; + double old_norm = 0; + + size_t iter = 0; + + for(; iter