C++; 我在C++中使用了不同的ODE求解器,熟悉标准的理论和更先进的ODE数值方法。我想了解的是ODE类的“设计模式”是什么。比如看
我们注意到,r.h.s的定义包括一个C++; 我在C++中使用了不同的ODE求解器,熟悉标准的理论和更先进的ODE数值方法。我想了解的是ODE类的“设计模式”是什么。比如看,c++,class,design-patterns,numerical-methods,ode,C++,Class,Design Patterns,Numerical Methods,Ode,我们注意到,r.h.s的定义包括一个void函数,该函数引用z和dzdt,如下所示: void ode( const state_type &z , state_type &dzdt , double t ) { dzdt[0] = z[1]; dzdt[1] = -1 * z[0] * w * w; } 然后在主体上与主体进行整合 int main() { ... integrate( ode , z , t , 1000 , 0.1 ,
void
函数,该函数引用z
和dzdt
,如下所示:
void ode( const state_type &z , state_type &dzdt , double t ) {
dzdt[0] = z[1];
dzdt[1] = -1 * z[0] * w * w;
}
然后在主体上与主体进行整合
int main() { ...
integrate( ode , z , t , 1000 , 0.1 , write_ode );
return 0;
}
当然,这样的库确实是硬编码的,但我只想掌握“步进器”背后的一般思想,比如说显式Euler方法
由于r.h.s的定义类似于
void-ode(…)
,我认为在步进部分中有一个对void-ode(…)
的调用,允许更新dzdt
。它可以如下实现(使用std::vector
类)
第一个想法是,RK方法各阶段的导数向量是解算器类的组成部分。这可以防止在integrator运行期间频繁分配内存和取消分配/垃圾回收。也许最好使用高阶方法来看看为什么这是有用的,因为欧拉太琐碎了,可能会给出错误的直觉 下一个要考虑的想法是使用可变的、自适应的步长方法。这意味着您有在需要时执行的内部步骤,以及通过插值供外部使用的评估,通常使用“密集输出”概念。在那里,您可以完全隐藏内部步骤,或者公开它们和插值函数/对象。这两种思想都可以在
scipy中进行研究。集成解算器,旧的步进器类ode
隐藏了内部步骤,步进器类RK45,Radau,…
在新的解算器ivp
接口后面实现了第二个概念
然后你很快就到达了一个有结构状态空间的点。我们可以实现这样一种理念,即数据必须组装在一个平面的一维向量中,这是(旧的)标准。或者可以为状态空间类配备必要的向量和范数运算。您应该在boost::odeint模板参数中找到这一点
下一点是,状态空间可能有段,如不同的对象、位置与速度等,这些段在很大程度上是不同的量级,应在步长控制器的误差估计(包括绝对误差公差)中单独处理(也就是说,计算每个段的最佳步长,然后取最小值)
最后一点,只有当解算器具有事件操作机制时,它才会变得普遍有用状态的某些函数的过零,动作可以是记录事件、在事件处终止,或者更不标准的状态向量修改。非常感谢您的详细回答。对我来说最重要的部分是第一段,即“导数是ode解算器类的组件”。我用RK类编辑了我的问题,我想了解如何实现上述概念。在实践中,问题是:“RK方法各阶段的导数向量以何种方式成为解算器类的组成部分”实现了吗?我想你指的是ode的rhs是带导数的向量,但你如何在代码中编写它?作为类内的结构?任何最小的工作示例都受到高度赞赏@LutzLehmann
void do_step(std::vector<double>& z, double tn, double h){
//tn current time, h time step
std::vector<double> dzdt(2);
ode(tn,y,dzdt);
z[0] += h*dzdt[0];
z[1] += h*dzdt[1];
}
#include <iostream>
#include <cmath>
#include <vector>
std::vector<double> operator+(const std::vector<double>& a, const std::vector<double>& b){
std::vector<double> ret(a.size());
for(unsigned int i=0;i<a.size();++i){
ret[i] = a[i]+b[i];
}
return ret;
}
constexpr double k = 3.0;
constexpr double m = 2.0;
constexpr double km = k/m;
class Rk{
private:
std::vector<double> f(const double t, const std::vector<double>& y){
std::vector<double> state(2);
state[0] = y[1];
state[1] = -(k/m)*y[0] + t;
return state;
}
std::vector<double> y0;
const double T;
double dt;
public:
Rk( std::vector<double> _y0,const double _T,double _dt) : y0{_y0}, T{_T}, dt{_dt}{}
~Rk()=default;
std::vector<double> mvec(const std::vector<double>& v, const double c) {
//implements m*vec
const auto size = v.size();
std::vector<double> res(size);
for (std::size_t i = 0; i < size; ++i){
res[i] = c*v[i];
}
return res;
}
void step(std::vector<double>& state, const double t){
//performs a Rk4 step from time t to t+dt
//state: current state y_1(tn),y_2(tn),...
const double dth = 0.5*dt;
std::vector<double> k1 = f(t,state);
std::vector<double> k2 = f(t+dth,state+mvec(k1,0.5*dt));
std::vector<double> k3 = f(t+dth,state+mvec(k2,0.5*dt));
std::vector<double> k4 = f(t+dt,state+mvec(k3,dt));
state = state + mvec ((k1+mvec(k2,2)+mvec(k3,2) + k4),dt/6.0);
}
void integrate(){
std::vector<double> state(2);
state = y0;
double t = 0.0;
for (unsigned int i=0;i<std::ceil(T/dt);++i){
step(state,t);
t+=dt;
// double err = std::fabs(std::sqrt(1/km) * std::sin(std::sqrt(km)*(t)) - state[0]);
double err = std::fabs((1.0/9.0) * (6*t+std::sqrt(6)*std::sin(std::sqrt(km)*t)) - state[0]);
std::cout << err <<std::endl;
}
}
};
int main(){
const double dt = 0.01;
std::vector<double> y0;
y0.push_back(0.0);
y0.push_back(1.0);
Rk my_ode{y0,1.0,dt};
my_ode.integrate();
return 0;
}