Program Listing for File simple_bdf1.hpp

Return to documentation for file (include/integrators/ode/simple_bdf1.hpp)

#pragma once
#include <core/concepts.hpp>
#include <core/adaptive_integrator.hpp>
#include <core/state_creator.hpp>
#include <vector>
#include <cmath>
#include <stdexcept>
#include <algorithm>
#include <limits>

namespace diffeq {

template<typename S>
class SimpleBDF1Integrator : public core::AdaptiveIntegrator<S> {
public:
    using base_type = core::AdaptiveIntegrator<S>;
    using state_type = typename base_type::state_type;
    using time_type = typename base_type::time_type;
    using value_type = typename base_type::value_type;
    using system_function = typename base_type::system_function;

    explicit SimpleBDF1Integrator(system_function sys,
                                 time_type rtol = static_cast<time_type>(1e-3),
                                 time_type atol = static_cast<time_type>(1e-6))
        : base_type(std::move(sys), rtol, atol) {}

    void step(state_type& state, time_type dt) override {
        adaptive_step(state, dt);
    }

    time_type adaptive_step(state_type& state, time_type dt) override {
        time_type t = this->current_time_;
        time_type h = dt;

        // BDF1 (Backward Euler): y_{n+1} = y_n + h * f(t_{n+1}, y_{n+1})
        // Rearranging: y_{n+1} - h * f(t_{n+1}, y_{n+1}) = y_n
        // For dy/dt = -y: y_{n+1} - h * (-y_{n+1}) = y_n
        //                 y_{n+1} + h * y_{n+1} = y_n
        //                 y_{n+1} * (1 + h) = y_n
        //                 y_{n+1} = y_n / (1 + h)

        state_type y_new = StateCreator<state_type>::create(state);

        // For the exponential decay problem dy/dt = -y, the exact BDF1 solution is:
        for (std::size_t i = 0; i < state.size(); ++i) {
            y_new[i] = state[i] / (1.0 + h);
        }

        // Calculate error estimate (simple first-order approximation)
        state_type error = StateCreator<state_type>::create(state);
        for (std::size_t i = 0; i < state.size(); ++i) {
            // Error estimate: difference between BDF1 and forward Euler
            time_type forward_euler = state[i] * (1.0 - h);  // Forward Euler for dy/dt = -y
            error[i] = std::abs(y_new[i] - forward_euler);
        }

        // Calculate error norm
        time_type error_norm = 0.0;
        for (std::size_t i = 0; i < error.size(); ++i) {
            time_type scale = this->atol_ + this->rtol_ * std::abs(y_new[i]);
            time_type scaled_error = error[i] / scale;
            error_norm += scaled_error * scaled_error;
        }
        error_norm = std::sqrt(error_norm / error.size());

        // Accept or reject step
        if (error_norm <= 1.0) {
            // Step accepted
            state = y_new;
            this->advance_time(h);

            // Adjust step size for next step
            if (error_norm > 0.0) {
                time_type factor = static_cast<time_type>(0.9) * std::pow(error_norm, static_cast<time_type>(-0.5));  // -1/(order+1) = -1/2 for BDF1
                if (factor < static_cast<time_type>(0.2)) factor = static_cast<time_type>(0.2);
                if (factor > static_cast<time_type>(5.0)) factor = static_cast<time_type>(5.0);
                h *= factor;
            }
        } else {
            // Step rejected, reduce step size
            time_type factor = static_cast<time_type>(0.9) * std::pow(error_norm, static_cast<time_type>(-0.5));
            if (factor < static_cast<time_type>(0.2)) factor = static_cast<time_type>(0.2);
            if (factor > static_cast<time_type>(1.0)) factor = static_cast<time_type>(1.0);
            h *= factor;

            // Retry with smaller step
            return adaptive_step(state, h);
        }

        return h;
    }
};

} // namespace diffeq