アップキャスト-派生クラスのインスタンスの引渡しについて
オブジェクト指向についてまだまだ初心者ですが,ちょっとした気付きをメモ.
クラスのあるメンバ関数を毎回実行させるような仕様にしたい際には,引数にインスタンスを入れ,関数内でそのメンバ関数を実行する.これにより,渡したインスタンスのメンバ関数を実行することができる.
引き渡すインスタンスは型にしたクラスを継承した派生クラスでも同様にメンバ関数を実行することができる.ただし,呼び出されるのは親クラスのメンバ関数.
引き渡したインスタンスのクラスでオーバーライドしたメンバ関数を実行するためにはその親クラスのメンバ関数を仮想関数にしておく必要がある模様.
以下サンプルコード
また関数hogefunc()では型が親クラスHogeのインスタンスを引数とし,関数内で引数のメンバ関数run0()とrun1()を実行させている.
2重振り子のあにめーしょん
ルンゲクッタにより数値的に非線形微分方程式の解析解が求まるようになったので、これを用いて2重振り子のあにめーしょんを作成。
2重振り子モデル、Cで書いてみた
— ぷらねギア (@planetary3gear) 2018年3月1日
(4次のルンゲ・クッタ) pic.twitter.com/vq90zX9VS0
グラフィックにはGLUTを用いた(GLUTによる「手抜き」OpenGL入門を参考)
運動方程式はラグランジュを使って手で求めた。(あってるか不安)
メインのコードはこんな感じ(汚くてごめんなさい)
<main.c>
- #include<stdio.h>
- #include<stdlib.h>
- #include<math.h>
- #include<unistd.h>
- #include<pthread.h>
- #include"animat.h"
- #include"arm.h"
animat.hにはanimat.cで使う関数の定義等が入ってる。また構造体の型Vector4Dを定義している。メンバはdouble theta, phi,dottheta,dotphi。それぞれの角度と角速度にしている。
arm.hには2重振り子の長さや重さ、重力加速度等を#define。
- Vector4D runge_2hensu(Vector4D hensu);
- Vector4D shoki_jouken(void);
- double f_the(Vector4D *p);
- double f_phi(Vector4D *p);
- double cot(double x);
- double sec(double x);
- static void *simulation(void);
- Vector4D k;
- pthread_mutex_t mutex;
- int main(int ac, char *av[]) {
- //test animation
- pthread_mutex_init(&mutex,NULL);
- pthread_t pthread;
- //f
- pthread_create( &pthread, NULL,&simulation,NULL);
- InitialGlut(ac,av);
- }
main()ではグラフィックの関数InitialGlut()を呼び出す前に スレッドによって関数simulation()を呼び出し、並行処理させている。そんな便利なもんがあるとは今まで知らなかった。いろいろショック。
- static void *simulation(void){
- FILE *outputfile;
- char filename[100];
- sprintf(filename,"bane_2ji_th0_%5.3f_phi0_%5.3f_m_%5.3f_l1_%5.3f_l2_%5.3f_v0_%5.3f.txt",TH0,PH0,M2,L1,L2,V0);
- outputfile = fopen(filename,"w");
- if (outputfile == NULL){
- printf("=====ERROR=========");
- exit(1);
- }
- //fprintf(outputfile,"theta0_%15.6f v0_%15.6f\n",TH0,V0);
- static float t = 0.0;
- static Vector4D kakudo,kakudop;
- static double x,y;
- static int i;
- kakudo = shoki_jouken();//初期条件を求める
- fprintf(outputfile," t theta phi dottheta dotphi \n");
- printf("t = %6.2f, theta = %15.6f phi = %15.6f\n", t, kakudo.theta,kakudo.phi);
- fprintf(outputfile,"%6.2f %15.6f %15.6f %15.6f %15.6f \n",t,kakudo.theta,kakudo.phi,kakudo.dottheta,kakudo.dotphi);
- for (i=0; i<=630000000; i++) {
- t = DT*(double)i;
- kakudop = runge_2hensu(kakudo);//dt後の値を求める
- while(kakudop.theta < -M_PI){
- kakudop.theta += 2*M_PI;
- }
- while(kakudop.theta > M_PI){
- kakudop.theta -= 2*M_PI;
- }
- while(kakudop.phi < -M_PI){
- kakudop.phi += 2*M_PI;
- }
- while(kakudop.phi > M_PI){
- kakudop.phi -= 2*M_PI;
- }
- if(i%100==0){
- printf("t = %11.7f, theta 1 = %15.6f phi = %15.6f\n", t, kakudop.theta,kakudop.phi);
- fprintf(outputfile,"%6.2f %15.6f %15.6f %15.6f %15.6f \n",t,kakudop.theta,kakudop.phi,kakudop.dottheta,kakudop.dotphi);
- }
- kakudo = kakudop;
- pthread_mutex_lock(&mutex);
- k = kakudo;
- pthread_mutex_unlock(&mutex);
- if*1{
- printf("==========発散!_デバックしろ_================\n");
- exit(1);
- }
- }
とりあえずめっちゃ長い時間計算するように設定した。DTは0.001。放置すれば保存したファイルで大変なことになる。関数runge_2hensu()に今の角度と角速度を渡し0.001秒後の状態を受け取る。これを繰り返すことで運動方程式を満たす運動をしていることになる。
- }
- Vector4D runge_2hensu(Vector4D hensu){
- Vector4D ans,buf;
- Vector4D k1,k2,k3,k4;//微分項
- //Vector4D h1,h2,h3,h4;//微分項
- /*微小変化1次*/
- k1.theta = DT * hensu.dottheta;//dotを積分1次近似
- k1.phi = DT * hensu.dotphi;//dotを積分1次近似
- k1.dottheta = DT * f_the(&hensu);//ddotを積分1次近似
- k1.dotphi = DT * f_phi(&hensu);//ddotを積分1次近
- /*微小変化2次*/
- k2.theta = DT * (hensu.dottheta + k1.dottheta/2);//微小変化2次近似
- k2.phi = DT * (hensu.dotphi + k1.dotphi/2);//微小変化2次近似
- buf.dottheta = hensu.dottheta + k1.dottheta / 2;//代入用
- buf.dotphi = hensu.dotphi + k1.dotphi / 2;//代入用
- buf.theta = hensu.theta + k1.theta / 2;//代入用
- buf.phi = hensu.phi + k1.phi / 2;//代入用
- k2.dottheta = DT * f_the(&buf);//ddotを積分2次近似
- k2.dotphi = DT * f_phi(&buf);//ddotを積分2次近
- /*微小変化3次*/
- k3.theta = DT * (hensu.dottheta + k2.dottheta/2);//微小変化3次近似
- k3.phi = DT * (hensu.dotphi + k2.dotphi/2);//微小変化3次近似
- buf.dottheta = hensu.dottheta + k2.dottheta / 2;//代入用
- buf.dotphi = hensu.dotphi + k2.dotphi / 2;//代入用
- buf.theta = hensu.theta + k2.theta / 2;//代入用
- buf.phi = hensu.phi + k2.phi / 2;//代入用
- k3.dottheta = DT * f_the(&buf);//ddotを積分3次近似
- k3.dotphi = DT * f_phi(&buf);//ddotを積分3次近
- /*微小変化4次*/
- k4.theta = DT * (hensu.dottheta + k3.dottheta);//微小変化3次近似
- k4.phi = DT * (hensu.dotphi + k3.dotphi);//微小変化3次近似
- buf.dottheta = hensu.dottheta + k3.dottheta ;//代入用
- buf.dotphi = hensu.dotphi + k3.dotphi ;//代入用
- buf.theta = hensu.theta + k3.theta ;//代入用
- buf.phi = hensu.phi + k3.phi;//代入用
- k4.dottheta = DT * f_the(&buf);//ddotを積分3次近似
- k4.dotphi = DT * f_phi(&buf);//ddotを積分3次近似
- /*変化量総和*/
- ans.dottheta = hensu.dottheta + (k1.dottheta + 2*k2.dottheta + 2*k3.dottheta + k4.dottheta)/6;
- ans.dotphi = hensu.dotphi + (k1.dotphi + 2*k2.dotphi + 2*k3.dotphi + k4.dotphi)/6;
- ans.theta = hensu.theta + (k1.theta + 2*k2.theta + 2*k3.theta + k4.theta)/6;
- ans.phi = hensu.phi + (k1.phi + 2*k2.phi + 2*k3.phi + k4.phi)/6;
- return ans;
- }
これがルンゲクッタの演算部分。ルンゲクッタについてはルンゲ=クッタ法 - Wikipediaを参照。
2変数だからどうってことはない。2階微分について式をまとめてあと2つのは角度と角速度を打ち込めばいい。ただ、↑の式を手で式変形させたものを使ったら発散して吹っ飛んだので、クラメルを使ってプログラムで求めるようにした。(あとで修正しやすいというのもある)
2階微分項を求めるのが関数f_the()とf_phi()。
- // \ddot{\theta}の方程式
- double f_the(Vector4D *p){
- double ddt;
- double keisu;
- /*keisu = 1/(M*L1*L2*(sec(p.theta + p.phi) - cos(p.theta + p.phi)));
- ddt = keisu*(M*L2*L2*pow(p.dotphi,2)*tan(p.theta + p.phi) -M*L1*L2*pow(p.dottheta,2)*sin(p.theta + p.phi)
- + M*G*L2*sec(p.theta + p.phi)*sin(p.theta) +M*G*L2*sin(p.phi)
- - L2/L1*sec(p.theta + p.phi)*K*(p.theta + p.phi - ALPHA) + K*(p.theta + p.phi - ALPHA) );*/
- Matrix4D A,B;
- A.a11 = (M1+M2)*L1*L1;
- A.a21 = M2*L1*L2*cos(p->theta + p->phi);
- A.a12 = M2*L1*L2*cos(p->theta + p->phi);
- A.a22 = M2*L2*L2;
- B.a11 = M2*L1*L2*p->dotphi*p->dotphi*sin(p->theta + p->phi) + (M1+M2)*G*L1*sin(p->theta) - K*(p->theta + p->phi - ALPHA);
- B.a21 = M2*L1*L2*p->dottheta*p->dottheta*sin(p->theta + p->phi) - M2*G*L2*sin(p->phi) - K*(p->theta + p->phi - ALPHA);
- B.a12 = A.a12;
- B.a22 = A.a22;
- if (A.a11*A.a22 == A.a12*A.a21){
- printf("=======_ERROR_det(A) = 0_ =======\n");
- exit(1);
- }
- ddt = (B.a11*B.a22 - B.a12*B.a21) / (A.a11*A.a22 - A.a12*A.a21);
- return ddt;
- }
- // \ddot{\phi}の方程式
- double f_phi(Vector4D *p){
- double ddp;
- double keisu;
- /*keisu = 1/(M*L1*L2*(cos(p.theta + p.phi) - sec(p.theta + p.phi)));
- ddp = keisu*(M*L1*L2*pow(p.dotphi,2)*sin(p.theta + p.phi) - M*L1*L1*pow(p.dottheta,2)*tan(p.theta + p.phi)
- + M*G*L1*sin(p.theta) +M*G*L1*sec(p.theta + p.phi)*sin(p.phi)
- + L1/L2*sec(p.theta + p.phi)*K*(p.theta + p.phi - ALPHA) - K*(p.theta + p.phi - ALPHA) );*/
- Matrix4D A,C;
- A.a11 = (M1+M2)*L1*L1;
- A.a21 = M2*L1*L2*cos(p->theta + p->phi);
- A.a12 = M2*L1*L2*cos(p->theta + p->phi);
- A.a22 = M2*L2*L2;
- C.a12 = M2*L1*L2*p->dotphi*p->dotphi*sin(p->theta + p->phi) + (M1+M2)*G*L1*sin(p->theta) - K*(p->theta + p->phi - ALPHA);
- C.a22 = M2*L1*L2*p->dottheta*p->dottheta*sin(p->theta + p->phi) - M2*G*L2*sin(p->phi) - K*(p->theta + p->phi - ALPHA);
- C.a11 = A.a11;
- C.a21 = A.a21;
- if (A.a11*A.a22 == A.a12*A.a21){
- printf("=======_ERROR_det(A) = 0_ =======\n");
- exit(1);
- }
- ddp = (C.a11*C.a22 - C.a12*C.a21) / (A.a11*A.a22 - A.a12*A.a21);
- //単バネで確認
- //ddp = -K/M*p.phi;
- //printf("%20.18f\n",ddp);
- return ddp;
- }
- //数学_未定義三角関数
- double cot(double x){
- double y;
- y = 1/(tan(x));
- return y;
- }
- double sec(double x){
- double y;
- y = 1/(cos(x));
- return y;
- }
- //初期条件
- Vector4D shoki_jouken(void){
- Vector4D ans;
- ans.theta = TH0;
- ans.phi = PH0;
- ans.dottheta = V0/L1/(cos(TH0) + sin(TH0)*cot(PH0));
- //theta固定にしてみる
- //ans.dottheta = 0;
- ans.dotphi = V0/L2/(cot(TH0)*sin(PH0) + cos(PH0));
- //ans.dotphi = 0;
- return ans;
- }
↑これはちょっと特殊な初期条件でやっている。動画のものはdottheta =0,dotphi=0の初期条件を途中から撮ったもの。
また、メイン文の方でグラフィック関数を呼び出し、数値計算の方はスレッドを使った。
一方、GLUTの方のコードはこんな感じ
<animat.c>
- #include <stdio.h>
- #include <GL/glut.h>
- #include <stdlib.h>
- #include <GL/gl.h>
- #include <GL/glu.h>
- #include <pthread.h>
- #include <string.h>
- #include <math.h>
- #include "arm.h"
- pthread_mutex_t mutex;
- typedef struct{
- double dottheta,dotphi;
- double theta,phi;
- } Vector4D;
- typedef struct{
- double x;
- double y;
- } point;
- pthread_mutex_t mutex;
- extern Vector4D k;
- static void display(void)
- {
- point a,b;
- Vector4D k_c;
- glClear(GL_COLOR_BUFFER_BIT);
- pthread_mutex_lock(&mutex);
- k_c = k;
- pthread_mutex_unlock(&mutex);
- glBegin(GL_POINTS);
- glColor3d(1.0, 0.0, 0.0);// 赤
- glVertex2d(0,0);
- /*calculation poit*/
- a.x = -L1*sin(k_c.theta);
- a.y = L1*cos(k_c.theta);
- b.x = -L1*sin(k_c.theta) - L2*sin(k_c.phi);
- b.y = L1*cos(k_c.theta) - L2*cos(k_c.phi);;
- glColor3d(1.0, 0.0, 0.0);// 赤
- glVertex2d(a.x,a.y);
- glVertex2d(b.x,b.y);
- glEnd();
- draw_a_link(&a,&k_c);
- draw_b_link(&a,&b,&k_c);
- drawCircle(&a,R1);
- drawCircle(&b,R2);
- glFlush();
- }
- void drawBox(point *p0,point *p1,point *p2,point *p3){
- glBegin(GL_POLYGON);
- glColor3d(1.0, 1.0, 0.0);// 赤+?
- glVertex2d(p0->x,p0->y);
- glVertex2d(p1->x,p1->y);
- glVertex2d(p2->x,p2->y);
- glVertex2d(p3->x,p3->y);
- glEnd();
- }
- void draw_a_link(point *a,Vector4D *kc){
- point p0,p1,p2,p3;
- p0.x = BB/2*cos(kc->theta);
- p0.y = BB/2*sin(kc->theta);
- p1.x = a->x + BB/2*cos(kc->theta);
- p1.y = a->y + BB/2*sin(kc->theta);
- p2.x = a->x - BB/2*cos(kc->theta);
- p2.y = a->y - BB/2*sin(kc->theta);
- p3.x = -BB/2*cos(kc->theta);
- p3.y = -BB/2*sin(kc->theta);
- drawBox(&p0,&p1,&p2,&p3);
- }
- void draw_b_link(point *a ,point *b,Vector4D *kc){
- point p0,p1,p2,p3;
- p0.x = a->x + BB/2*cos(kc->phi);
- p0.y = a->y - BB/2*sin(kc->phi);
- p1.x = b->x + BB/2*cos(kc->phi);
- p1.y = b->y - BB/2*sin(kc->phi);
- p2.x = b->x - BB/2*cos(kc->phi);
- p2.y = b->y + BB/2*sin(kc->phi);
- p3.x = a->x - BB/2*cos(kc->phi);
- p3.y = a->y + BB/2*sin(kc->phi);
- drawBox(&p0,&p1,&p2,&p3);
- }
- #define D_C (16) //bunkatusuu
- void drawCircle(point *n,double r){
- double st = 2.0*M_PI/D_C;
- int i;
- double th,x,y;
- glBegin(GL_POLYGON);
- glColor3d(0.8, 0.0, 1.0);// 赤
- for(i=0;i<D_C;i++){
- th = st*(double)i;
- x = n->x + r*cos(th);
- y = n->y + r*sin(th);
- glVertex2d(x,y);
- }
- glEnd();
- }
- void init(void)
- {
- glClearColor(0.0, 0.0, 0.0, 0.5);
- }
- void timer(int value){
- glutPostRedisplay();
- glutTimerFunc(1,timer,0);
- }
- void InitialGlut(int ac,char **av)
- {
- glutInit(&ac,av);
- printf("Welcome! \n");
- glutInitDisplayMode(GLUT_RGBA);
- glutCreateWindow("animation");
- glutDisplayFunc(display);
- //init();
- glutTimerFunc(1,timer,0);
- glutPostRedisplay();
- glutMainLoop();
- }
やはりグラフィックの方が大変であった。
正直全部説明するのはしんどい。
関数glutPostRedisplay()だけではディスプレイを更新させてくれないようで、glutTimerFunc()関数を使って強制的に更新させてる。原理はまだよくわかってない。
あと、振り子のおもりは図形を移動させているのではなく、毎回書き出してる。linuxだからちゃんと動いてるように見えるけど、windowsだったらカクカクしそう。
GLUT使ってもっといろいろやってみたいな〜。
*1:kakudop.dotphi>100000)||(kakudop.dottheta>100000