example 1

#include "pressio/ode_advancers.hpp"
#include "pressio/ode_steppers_explicit.hpp"
#include <Eigen/Core>

template<class ScalarType>
class SimpleSystem{
public:
  using independent_variable_type = ScalarType;
  using state_type                = Eigen::Matrix<ScalarType,-1,1>;
  using rhs_type	          = state_type;

  state_type createState() const{
    auto s = state_type(3);
    s.setConstant(0);
    return s;
  };

  rhs_type createRhs() const{
    auto v = rhs_type(3);
    v.setConstant(0);
    return v;
  };

  void rhs(const state_type & state,
	   const independent_variable_type timeIn,
	   rhs_type & rhs) const
  {
    constexpr ScalarType ten{10};
    rhs(0) = ten * state(0);
    rhs(1) = ten * state(1);
    rhs(2) = ten * state(2);
  };
};

int main(int argc, char *argv[])
{
  PRESSIOLOG_INITIALIZE(pressiolog::LogLevel::sparse);

  using scalar_type = double;
  SimpleSystem<scalar_type> problem;

  auto y = problem.createState();

  namespace pode = pressio::ode;
  constexpr auto scheme = pode::StepScheme::ForwardEuler;
  auto stepperObj = pode::create_explicit_stepper(scheme, problem);

  {
    /*
      run a fixed number of steps
    */
    y(0) = 1.; y(1) = 2.; y(2) = 3.;
    const scalar_type dt{0.1};
    const scalar_type startTime{0.0};
    pode::advance_n_steps(stepperObj, y, startTime, dt, pode::StepCount{1});
    // check solution
    std::cout << "Computed solution: ["
	      << y(0) << " " << y(1) << " " << y(2) << "] "
	      << "Expected solution: [2,4,6] \n";
  }
  std::cout << std::endl;

  {
    /*
      run until target time
    */
    y(0) = 1.; y(1) = 2.; y(2) = 3.;
    const scalar_type startTime{0.0};
    const scalar_type finalTime{1.0};
    pode::advance_to_target_point(stepperObj, y, startTime, finalTime,
				  [](const pode::StepCount & /*unused*/,
				     const pode::StepStartAt<scalar_type> & /*unused*/,
				     pode::StepSize<scalar_type> & dt)
				  {
				    dt = 0.1;
				  });
    // check solution
    std::cout << "Computed solution: ["
	      << y(0) << " " << y(1) << " " << y(2) << "] "
	      << "Expected solution: [1024, 2048, 3074] \n";
  }

  std::cout << "PASSED\n";
  PRESSIOLOG_FINALIZE();
  return 0;
}

example 2

Integrate the original Lorenz system via explicit time integration.

#include "pressio/ode_advancers.hpp"
#include "pressio/ode_steppers_explicit.hpp"
#include <Eigen/Core>

template<class ScalarType>
class Lorenz3{
  const int N_ = 3;
  const ScalarType rho_   = static_cast<ScalarType>(28);
  const ScalarType sigma_ = static_cast<ScalarType>(10);
  const ScalarType beta_  = static_cast<ScalarType>(8)/3;

public:
  using independent_variable_type = ScalarType;
  using state_type                = Eigen::Matrix<ScalarType,-1,1>;
  using rhs_type      = state_type;

  state_type createState() const{
    auto s = state_type(N_);
    s.setConstant(0);
    return s;
  };

  rhs_type createRhs() const{
    auto v = rhs_type(N_);
    v.setConstant(0);
    return v;
  };

  void rhs(const state_type & state,
	   const independent_variable_type timeIn,
	   rhs_type & rhs) const
  {
    const auto x = state(0);
    const auto y = state(1);
    const auto z = state(2);

    rhs(0) = sigma_ * (y - x);
    rhs(1) = x * (rho_ - z) - y;
    rhs(2) = x * y - beta_ * z;
  };
};

template<class ScalarType>
class StateObserver
{
public:
  explicit StateObserver(int sampleFreq)
    : myfile_("state_snapshots.bin",  std::ios::out | std::ios::binary),
      sampleFreq_(sampleFreq){}

  ~StateObserver(){ myfile_.close(); }

  template<typename TimeType>
  void operator()(pressio::ode::StepCount step,
		  const TimeType /*timeIn*/,
		  const Eigen::Matrix<ScalarType,-1,1> & state)
  {
    if (step.get() % sampleFreq_ == 0){
      const std::size_t ext = state.size()*sizeof(ScalarType);
      myfile_.write(reinterpret_cast<const char*>(&state(0)), ext);
    }
  }

private:
  std::ofstream myfile_;
  const int sampleFreq_ = {};
};

int main(int argc, char *argv[])
{
  PRESSIOLOG_INITIALIZE(pressiolog::LogLevel::sparse);

  using scalar_type = double;
  Lorenz3<scalar_type> problem;

  namespace pode = pressio::ode;
  constexpr auto scheme = pode::StepScheme::RungeKutta4;
  auto stepperObj = pode::create_explicit_stepper(scheme, problem);

  auto y = problem.createState();
  y(0) = 0.5; y(1) = 1.0; y(2) = 1.;
  const scalar_type startTime{0.0};
  const scalar_type finalTime{40.0};
  const int stateSampleFreq = 2;
  pode::advance_to_target_point(stepperObj, y, startTime,
				finalTime,
				  [=](const pode::StepCount & /*unused*/,
				     const pode::StepStartAt<scalar_type> & /*unused*/,
				     pode::StepSize<scalar_type> & dt)
				  { dt = 0.01; },
				StateObserver<scalar_type>{stateSampleFreq});

  std::cout << "PASSED\n";
  PRESSIOLOG_FINALIZE();
  return 0;
}

Visualize

You can use the data collected to plot the results via the following script:

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

if __name__== "__main__":
    numVars = 3
    D = np.fromfile("./state_snapshots.bin")
    numTimeSteps = int(np.size(D)/numVars)
    print(numTimeSteps)
    #D  = np.reshape(data, (numTimeSteps, numVars))

    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1, projection='3d')
    ax.plot(D[0::3], D[1::3], D[2::3])
    fig.savefig("plot.png", format="png", bbox_inches='tight', dpi=300)
    plt.show()
Lorenz

example 3

Integrate the original Lorenz system using implicit time integration.

#include "pressio/ode_advancers.hpp"
#include "pressio/ode_steppers_implicit.hpp"
#include <Eigen/Core>

template<class ScalarType>
class Lorenz3{
  const int N_ = 3;
  const ScalarType rho_   = static_cast<ScalarType>(28);
  const ScalarType sigma_ = static_cast<ScalarType>(10);
  const ScalarType beta_  = static_cast<ScalarType>(8)/3;

  using trip_t = Eigen::Triplet<ScalarType>;
  mutable std::vector<trip_t> tripletList_;

public:
  using independent_variable_type = ScalarType;
  using state_type                = Eigen::Matrix<ScalarType,-1,1>;
  using rhs_type		  = state_type;
  // since the Jacobain of Lorenz3 is fairly dens,
  // we could use a dense matrix for it, but we use a sparse one
  // here for demonstration purposes
  using jacobian_type             = Eigen::SparseMatrix<ScalarType>;

  Lorenz3(){
    allocateTriplets();
  }

  state_type createState() const{
    state_type s(N_);
    s.setConstant(0);
    return s;
  };

  rhs_type createRhs() const{
    rhs_type v(N_);
    v.setConstant(0);
    return v;
  };

  jacobian_type createJacobian() const{
    jacobian_type J(N_, N_);
    J.setFromTriplets(tripletList_.begin(), tripletList_.end());
    return J;
  };

  void rhsAndJacobian(const state_type & state,
		      const independent_variable_type timeIn,
		      rhs_type & rhs,
		      std::optional<jacobian_type*> Jopt) const
  {
    const auto x = state(0);
    const auto y = state(1);
    const auto z = state(2);

    rhs(0) = sigma_ * (y - x);
    rhs(1) = x * (rho_ - z) - y;
    rhs(2) = x * y - beta_ * z;

    if (Jopt)
    {
      auto & J = *Jopt.value();
      tripletList_[0] = {0, 0, -sigma_};
      tripletList_[1] = {0, 1,  sigma_};

      tripletList_[2] = {1, 0, rho_-z};
      tripletList_[3] = {1, 1, -1.};
      tripletList_[4] = {1, 2, -x};

      tripletList_[5] = {2, 0, y};
      tripletList_[6] = {2, 1, x};
      tripletList_[7] = {2, 2, -beta_};
      J.setFromTriplets(tripletList_.begin(), tripletList_.end());
    }
  };

private:
  void allocateTriplets()
  {
    // first row
    tripletList_.push_back( trip_t( 0, 0, 0.) );
    tripletList_.push_back( trip_t( 0, 1, 0.) );
    // second and third row
    for (int i=1; i<=2; ++i){
      tripletList_.push_back( trip_t( i, 0, 0.) );
      tripletList_.push_back( trip_t( i, 1, 0.) );
      tripletList_.push_back( trip_t( i, 2, 0.) );
    }
  }
};

int main(int argc, char *argv[])
{
  PRESSIOLOG_INITIALIZE(pressiolog::LogLevel::sparse);

  using scalar_type = double;
  using problem_type = Lorenz3<scalar_type>;
  problem_type problem;

  namespace pode = pressio::ode;
  namespace plins  = pressio::linearsolvers;

  // create stepper
  constexpr auto scheme = pode::StepScheme::BDF2;
  auto stepper = pode::create_implicit_stepper(scheme, problem);

  // linear and nonlinear solver
  using fom_jacobian_t = typename problem_type::jacobian_type;
  using linear_solver_t = plins::Solver<plins::iterative::Bicgstab, fom_jacobian_t>;
  linear_solver_t linearSolver;

  auto nonLinearSolver = pressio::create_newton_solver(stepper, linearSolver);
  nonLinearSolver.setStopTolerance(1e-13);

  auto y = problem.createState();
  y(0) = 0.5; y(1) = 1.0; y(2) = 1.;
  const scalar_type startTime{0.0};
  const scalar_type finalTime{40.0};
  std::ofstream myfile("state_snapshots.bin",  std::ios::out | std::ios::binary);
  pode::advance_to_target_point(stepper, y, startTime, finalTime,
				// lambda to set the time step
				[](const pode::StepCount & /*unused*/,
				   const pode::StepStartAt<scalar_type> & /*unused*/,
				   pode::StepSize<scalar_type> & dt)
				{ dt = 0.01; },
				// lambda to observe and write to file the state
				[&](pressio::ode::StepCount step,
				    const scalar_type /*timeIn*/,
				    const typename problem_type::state_type & state)
				{
				  std::cout << "saving state at = " << step.get() << "\n";
				  const std::size_t ext = state.size()*sizeof(scalar_type);
				  myfile.write(reinterpret_cast<const char*>(&state(0)), ext);
				},
				nonLinearSolver);
  myfile.close();

  std::cout << "PASSED\n";
  PRESSIOLOG_FINALIZE();
  return 0;
}

Visualize

You can visualize the results using the same Python script above.