Commit 133a3ea1 by Lasse Hinrichsen

### Start working on a semilinear functional

parent 1fbd2501
Pipeline #34608 passed with stage
in 16 minutes and 9 seconds
 #pragma once #include #include #include #include #include namespace Dune::TNNMG { namespace Impl { /** Compute Euclidian scalar product */ template auto dot(const V& v, const V& w) { // TODO: Make this more general for vector types that do not implement // operator*. // Maybe, dune-matrix-vector also has this. return v * w; } template auto zeroCopy(const V& v) { auto ret = autoCopy(v); ret = 0.; return ret; } /** Apply a function f componentwise to a vector v and store it in u, i.e. * u_i = f(v_i) */ template void applyComponentwise(F&& f, V& u, const V& v) { namespace H = Dune::Hybrid; H::ifElse( Dune::IsNumber>(), [&](auto&& id) { u = f(id(v)); }, [&](auto id) { H::forEach(H::integralRange(H::size(id(v))), [&](auto i) { applyComponentwise(f, H::elementAt(u, i), H::elementAt(v, i)); }); }); } template void directionalDomain(const V& origin, const V& direction, const F& phi, S& lower, S& upper) { namespace H = Dune::Hybrid; if constexpr (Dune::IsNumber>()) { if (direction == 0) { return; } // TODO check this else if (direction > 0) { lower = std::max(lower, (phi.domain()[0] - origin) / direction); upper = std::min(upper, (phi.domain()[1] - origin) / direction); } else { lower = std::max(lower, (phi.domain()[1] - origin) / direction); upper = std::min(upper, (phi.domain()[0] - origin) / direction); } } else { H::forEach(H::integralRange(H::size(origin)), [&](auto i) { directionalDomain( H::elementAt(origin, i), H::elementAt(direction, i), phi, lower, upper); }); } } template void addSubdiffs(const V& origin, const V& direction, const V& weights, const F& phi, double t, S& interval) { // TODO: Check this namespace H = Dune::Hybrid; if constexpr (Dune::IsNumber>()) { auto local_subdiff = phi.subDifferential(origin + t * direction); local_subdiff *= weights * direction; // TODO stimmt das? interval += local_subdiff; } else { H::forEach(H::integralRange(H::size(origin)), [&](auto i) { addSubdiffs(H::elementAt(origin, i), H::elementAt(direction, i), H::elementAt(weights, i), phi, t, interval); }); } } auto reals() { return Dune::Solvers::Interval( { std::numeric_limits::lowest(), std::numeric_limits::max() }); } template auto computeFunctionalValue(const V& weights, V pos, const F& phi) { applyComponentwise(phi, pos, pos); return dot(weights, pos); } } template class SemilinearfunctionalDirectionalRestriction { public: using Vector = V; using Range = R; template SemilinearfunctionalDirectionalRestriction(const Vector& weights, const Vector& origin, const Vector& direction, FF&& phi) : weights_(weights) , origin_(origin) , direction_(direction) , phi_(std::forward(phi)) {} Range operator()(const Range& t) const { // This is of course a PITA having to copy vectors // every single time. Well, what can you do... // TODO: origin_ part is missing! DUNE_THROW(Dune::Exception, "Dont use this yet"); auto val = direction_; val *= t; return Impl::computeFunctionalValue(weights_, std::move(val), phi_); } auto domain() const { auto dom = Impl::reals(); Impl::directionalDomain(origin_, direction_, phi_, dom[0], dom[1]); return dom; } auto subDifferential(double t) const { // TODO: /* Okay, we need to compute the subdifferential of \sum_i w_i * phi(x_i + t*c_i) in t. This should be \sum_i w_i * 6phi(x_i + t*c_i)*c_i, right? */ auto sd = Dune::Solvers::Interval({ 0., 0. }); Impl::addSubdiffs(origin_, direction_, weights_, phi_, t, sd); return sd; } // TODO: Provide some read-only access to data such that a clever // person could write a sophisticated optimization routine for her/his // particular problem. private: Vector weights_; Vector origin_; Vector direction_; ScalarFunction phi_; }; template class Semilinearfunctional { public: using Vector = V; using Range = R; template Semilinearfunctional(const Vector& weights, FF&& phi) : weights_(weights) , origin_(Impl::zeroCopy(weights_)) , phi_(std::forward(phi)) {} template Semilinearfunctional(const Vector& weights, const Vector& origin, FF&& phi) : weights_(weights) , origin_(origin) , phi_(std::forward(phi)) {} void updateOrigin() {} // TODO what should this actually do? void updateOrigin(std::size_t) {} auto domain() const { return phi_.domain(); } auto subDifferential(double pos) const { auto shiftedPos = origin_ + pos; return phi_.subDifferential(shiftedPos); } Range operator()(Vector x) const { // TODO copying x is expensive. We should supply both vectors to the compute // function. x += origin_; return Impl::computeFunctionalValue(weights_, std::move(x), phi_); } friend auto shift(const Semilinearfunctional& f, const Vector& origin) { auto new_origin = f.origin_; new_origin += origin; return Semilinearfunctional(f.weights_, new_origin, f.phi_); } friend auto derivative(const Semilinearfunctional& f) { // TODO: is this too much to ask for? // auto phi_prime = derivative(f.phi_); DUNE_THROW(Dune::NotImplemented, "Derivative not yet implemented"); return 42; } friend auto directionalRestriction(const Semilinearfunctional& f, const Vector& origin, const Vector& direction) { // TODO What happens if f already has a nontrivial origin? And if so, // what SHOULD happen? return SemilinearfunctionalDirectionalRestriction( f.weights_, origin, direction, f.phi_); } template friend auto coordinateRestriction(const Semilinearfunctional& f, const Index& i) { using BT = std::decay_t; // TODO: This will of course leave out the constant contributions in the // other components. But since the minimization is independent from these, // why bother? The coupling between components should be handled in the // quadratic part. return Semilinearfunctional( f.weights_[i], f.origin_[i], f.phi_); } const Vector& weights() const { return weights_; } /** TODO: Doc me! */ const ScalarFunction& phi() const { return phi_; } private: Vector weights_; Vector origin_; ScalarFunction phi_; }; } \ No newline at end of file
 #pragma once #include #include #include #include namespace Dune::TNNMG { namespace Helper { /* In this namespace, we have a bunch of helper functions that are actually could be applied in other contexts, too. Some are even part of open MR that gathered dust by now. */ /** \brief For two vectors of equal size (and blocking), apply a scalar function * componentswise such that u_i = f(v_i). */ template void applyComponentwise(F&& f, V& u, const V& v) { namespace H = Dune::Hybrid; H::ifElse( Dune::IsNumber>(), [&](auto&& id) { u = f(id(v)); }, [&](auto id) { H::forEach(H::integralRange(H::size(id(v))), [&](auto i) { applyComponentwise(f, H::elementAt(u, i), H::elementAt(v, i)); }); }); } /** Multiply the i-th component of u by the i-th component of v, i.e. * u_i <- u_i*v_i */ template void multiplyComponentwise(V& u, const V& v) { namespace H = Dune::Hybrid; H::ifElse( Dune::IsNumber>(), [&](auto&& id) { u *= id(v); }, [&](auto id) { H::forEach(H::integralRange(H::size(id(v))), [&](auto i) { multiplyComponentwise(H::elementAt(u, i), H::elementAt(v, i)); }); }); } template void writeDiagonal(M& m, V&& v) { namespace H = Dune::Hybrid; H::ifElse( Dune::IsNumber>(), [&](auto&& id) { m = id(v); }, [&](auto&& id) { H::forEach(H::integralRange(H::size(id(v))), [&](auto i) { writeDiagonal(H::elementAt(H::elementAt(m, i), i), H::elementAt(v, i)); }); }); } template void determineTruncation(const NV& x, NBV&& truncationFlags, const F& function) { namespace H = Dune::Hybrid; if constexpr (IsNumber()) { truncationFlags = function.should_truncate(x); } else { assert(x.size() == truncationFlags.size()); H::forEach(H::integralRange(H::size(x)), [&](auto&& i) { determineTruncation(x[i], truncationFlags[i], function); // todo element_at }); } } template void truncateVector(NV& x, const NBV& truncationFlags) { namespace H = Dune::Hybrid; if constexpr (IsNumber()) { if (truncationFlags) x = 0; } else { H::forEach(H::integralRange(H::size(x)), [&](auto&& i) { truncateVector(x[i], truncationFlags[i]); }); } } template void truncateMatrix(NM& A, const RBV& rowTruncationFlags, const CBV& colTruncationFlags) { namespace H = Dune::Hybrid; using namespace Dune::MatrixVector; if constexpr (IsNumber()) { if (rowTruncationFlags or colTruncationFlags) A = 0; } else { H::forEach(H::integralRange(H::size(rowTruncationFlags)), [&](auto&& i) { auto&& Ai = A[i]; sparseRangeFor(Ai, [&](auto&& Aij, auto&& j) { truncateMatrix(Aij, rowTruncationFlags[i], colTruncationFlags[j]); }); }); } } } /** A constrained linearization for the SemilinearFunctional */ template class SemilinearFunctionalConstrainedLinearization { public: using BitVector = BV; using Matrix = M; using Vector = V; using ConstrainedMatrix = Matrix; using ConstrainedVector = Vector; using ConstrainedBitVector = BitVector; SemilinearFunctionalConstrainedLinearization(const F& functional, const BitVector& ignore) : f_(functional) , ignore_(ignore) {} /** \brief Set a matrix that serves as a template for the Hessian. * * This needs to be a matrix of correct size and sparsity pattern (including * at least the diagonal entries). * TODO: I don't like neither the name nor the fact of having a setter * function at all. This should rather be done in the constructor. */ template void setMatrixDummy(const MM& dummy) { // mat_ = std::forward(dummy); mat_ = dummy; } void bind(const Vector& x) { // Prepare the negative gradient ngrad_ = x; // Assuming that the supplied function does not explode if it is evaluated // in a point where it's actually not defined. Helper::applyComponentwise(derivative(f_.phi()), ngrad_, x); Helper::multiplyComponentwise(ngrad_, f_.weights()); ngrad_ *= -1; mat_ = 0; // compute diagonal entries auto diag = x; Helper::applyComponentwise(derivative(derivative(f_.phi())), diag, x); Helper::multiplyComponentwise(diag, f_.weights()); // now, write them into the diagonal of the Hessian Helper::writeDiagonal(mat_, diag); truncationFlags_ = ignore_; Helper::determineTruncation(x, truncationFlags_, f_.phi()); Helper::truncateMatrix(mat_, truncationFlags_, truncationFlags_); Helper::truncateVector(ngrad_, truncationFlags_); } void extendCorrection(ConstrainedVector& cv, Vector& v) const { v = cv; Helper::truncateVector(v, truncationFlags_); } const BitVector& truncated() const { return truncationFlags_; } const auto& negativeGradient() const { return ngrad_; } const auto& hessian() const { return mat_; } private: const F& f_; Matrix mat_; Vector ngrad_; const BitVector& ignore_; BitVector truncationFlags_; }; } \ No newline at end of file
 #pragma once #include #include #include #include #include #include namespace Dune { namespace Concept { struct HasDomain { template auto require(C&& c) -> decltype(c.domain()); }; struct HasSubdifferential { template auto require(C&& c) -> decltype(c.subDifferential(0.)); }; } namespace TNNMG { using namespace Dune::Concept; template()>> auto domain(const Functional& f) { return f.domain(); } /** For a tuple of intervals, return their intersection. * * If the intersection is empty, the function will throw! */ template auto intervalIntersection(const std::tuple& intervals) { // We want the greatest interval such that all functionals are well-defined. auto left_max = std::numeric_limits::lowest(); auto right_min = std::numeric_limits::max(); auto shrink_intervals = [&](const auto&... interval) { ((left_max = std::max(left_max, interval[0])), ...); ((right_min = std::min(right_min, interval[1])), ...); }; std::apply(shrink_intervals, intervals); // In case there is no such interval (not even a point), we throw an // exception. // TODO: Is this the most reasonable thing to do here? if (right_min < left_max) DUNE_THROW(Dune::Exception, "The intersection over all intervals is empty."); return Dune::Solvers::Interval({ left_max, right_min }); } template auto domain(const std::tuple& f) { auto domains = std::apply( [](const auto&... x) { return std::make_tuple(TNNMG::domain(x)...); }, f); return intervalIntersection(domains); } template()>> auto subDifferential(Functional&& f, double pos) { return f.subDifferential(pos); } template auto subDifferential(const std::tuple& f, double pos) { // Compute each subdifferential and sum them up. return std::apply( [pos](const auto&... x) { return (TNNMG::subDifferential(x, pos) += ...); }, f); } } } \ No newline at end of file
 #pragma once #include // constains the helper functions namespace Dune { namespace TNNMG { namespace Helper { template void projectInDomain(F&& f, const V& x, V& c) { // TODO: // This operators only on the semilinear functional. // For the sumfunctional, we need to ignore the other functionals. //