倒立振子のモデル予測制御(MPC)
参考
- https://qiita.com/Gomachan_2021/items/41c792b99a64a043ae60
- https://qiita.com/taka_horibe/items/47f86e02e2db83b0c570
- https://qiita.com/HoneyMack/items/2c0d7369cfa539f756f7
- https://qiita.com/Gomachan_2021/items/908a95a73f807ebc207d
- https://www.jstage.jst.go.jp/article/jrsj/32/6/32_32_499/_pdf
- https://jp.mathworks.com/discovery/model-predictive-control.html
- https://au.mathworks.com/content/dam/mathworks/tag-team/Objects/j/JP_Model_Predictive_Control_Whitepaper.pdf
- https://jp.mathworks.com/discovery/model-predictive-control.html
- https://zenn.dev/takuya_fukatsu/articles/439dd7d6163abf
- https://myenigma.hatenablog.com/entry/2016/07/25/214014
- https://myenigma.hatenablog.com/entry/2017/12/20/090701
- https://blog.control-theory.com/entry/2024/01/15/model-predictive-control-mpc
- https://negligible.hatenablog.com/entry/2023/03/06/190928
- https://negligible.hatenablog.com/entry/2023/04/27/184908
- http://www.minokura.net/works/wheelpendulum.html
- https://ja.wikipedia.org/wiki/倒立振子
目標
モデル予測制御(Model Predictive Control)を用いて倒立振子の制御を行う。
制御対象は左右の脚に関節が付いたもので、下記を制御達成の目安とする。
- 立つこと
- コントローラの操作に合わせて前後左右に移動すること
- 関節を用いて左右間の段差(高低差)に対応できること
- 高所から落ちて着地できること。
- ジャンプできること。
手段
- M5Stack Core2を用いて制御する。
- 下記センサをフュージョンして状態の推定を行う。
- M5Stack Core2に乗っている6軸IMU(MPU6886)を使用。
- モーター駆動軸に磁気式ロータリ位置センサ(AS5047D)を使用。
- モーター駆動軸にM2006を使用。データシート
手順
- 倒立振子の物理モデルを立てる。(状態方程式に変換。)
- MPCを実装。
- シミュレータを実装。
- MPCを用いてシミュレータ内で倒立振子を立たせる。
- 立たせる実機実験。
- MPCを用いてシミュレータ内で追従を行う。
- 追従実機実験。
- 関節の制御で段差対応。
- ジャンプ。
台車型倒立振子の物理モデル
モデルを単純化するため、はじめは関節なしの台車型倒立振子型ロボットを
鉛直上向きに対する
台車に力
ここで水平方向の反力
振り子の質量を
以上より式を整理して
反力H、Vを近似しない場合
台車の変位
上式を2階微分して
また振り子の運動方程式は
ここに
これを台車の運動方程式に代入し、
同様にモーメントの式に代入し
これを整理して
以上より状態方程式表現を得る。
車輪型倒立振子
状態変数を
ただし
地面・タイヤ間の摩擦力を
ホイールの角変位を
物理的制約より、加速度と角加速度の関係式
以上3式を整理して、
上式の導出
(2)式に
これを(1)式と比較して、
振り子のモーメントの式(
ここで車輪の変位
以上を代入して整理することで
上式の導出
(4)式に
上2式を連立して整理する。
以上より状態方程式表現を得る。
また出力を
モータに電流
可制御性
可制御性行列
このとき
可観測性
可観測行列
このとき
MPCの実装
- 状態方程式の離散化
- 最適化問題に落とし込む。
- 凸最適化問題を解く。
状態方程式をdt間隔で離散化
時刻
すなわち
最適化問題に落とし込む
時刻
以降同様に
これらをすべてまとめて以下の行列とする。
また出力の方程式は単に
これらをまとめて以下のように表現する。
これは
評価関数
最適な入力列
評価関数(目的関数)
ここで
(例:制御入力を最小にする場合、
また
(
評価関数
入力
詳細な変換
また転置の性質より
ここで評価関数
これを微分して
これによって
MPCではこの入力のうちの最初の要素のみを毎回使用するため、今使用する制御入力は
なおこの制御入力列を適用したときの状態の遷移は以下の通り予測できる。
最適化問題のソルバに勾配を入力する場合
上記に参照軌道を追加
Rustで数値計算を行うライブラリはいくつかある模様。
RustでMPCを実装してPythonでシミュレーション結果をplotしたい。
車輪型倒立振子を差動二輪として動かす
状態変数を
左右のモーターそれぞれに発生させるトルクを
地面・タイヤ間の摩擦力をそれぞれ
坂道対応
坂の角度を
状態の推定
センサにIMUとロータリーエンコーダがあるため、
Rustで行列演算を行う
Rustには行列演算を行う有名なcrateが2つある。
ndarrayはヒープに値を持つため組み込み向きではない。
よってNalgebraを使用する。
[dependencies]
nalgebra = "0.32.5"
公式の推奨より以下のエイリアスを定義。
extern crate nalgebra as na;
行列は以下で与えられている。ここでTは要素の型。Rは行数、Cは列数を表す。
na::MatrixMN<T, R, C>
エイリアスが定義されているので通常使用する分にはこれを使えばよさそう。
let m = Matrix3::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
println!("A = {}", m);
let m = Matrix3::from_fn(|r, c| 10 * r + c);
println!("B = {}", m);
let m = m.fixed_view::<2, 2>(1, 0).clone_owned();
println!("C = {}", m);
マクロを使用すればさらに簡単に構築できる。
use nalgebra::matrix;
// Produces a Matrix3<_> == SMatrix<_, 3, 3>
let a = matrix![1, 2, 3;
4, 5, 6;
7, 8, 9];
一般的な行列演算を行える。v1,v2
をベクトル、m1,m2
を行列とする。
let _ = 2.0 * v1 + 3.0 * v2;
let _ = 4.0 * m1 + 5.0 * m2;
let _ = m1 * v1 + m2 * v2;
print!
も可能。
println!("{}", v1);
println!("{}", m1);
制約付き最適化問題はopt_engineで解けそう。[1]
使用手順
- 最適化したい関数
f
とその関数の勾配df
を用意する。 - 制約を用意する。
- 関数・勾配・制約をもとに最適化問題を生成。
-
solve()
で最適化問題を解き、最適な入力を取り出す。
雑に
use optimization_engine::{panoc::*, *};
fn f(u: &[f64], c: &mut f64) -> Result<(), SolverError> {
*c = u[0].powi(2) + u[1].powi(2);
Ok(())
}
fn df(u: &[f64], grad: &mut [f64]) -> Result<(), SolverError> {
grad[0] = 2.0 * u[0];
grad[1] = 2.0 * u[1];
Ok(())
}
fn main() {
let tolerance = 1e-6;
let n = 2;
let lbfgs_memory = 10;
let max_iters = 200;
let mut u = [0.0, 0.0];
let radius = 1.0;
// the cache is created only ONCE
let mut panoc_cache = PANOCCache::new(n, tolerance, lbfgs_memory);
// define the bounds at every iteration
let bounds = constraints::Ball2::new(None, radius);
// the problem definition is updated at every iteration
let problem = Problem::new(&bounds, df, f);
// updated instance of the solver
let mut panoc = PANOCOptimizer::new(problem, &mut panoc_cache).with_max_iter(max_iters);
let status = panoc.solve(&mut u).unwrap();
// print useful information
println!(
"parameters: (r={:.4}), iters = {}",
radius,
status.iterations()
);
println!("u = {:#.6?}", u);
}
OpEnでMPCを動かしてみる
OpEnを使用して最適化問題を解くには目的関数とその勾配が必要。
勾配は数値微分によって求めることとする。
系ダイナミクスの数値積分の誤差が気になる場合はルンゲクッタ。
mppiも使ってみたい
線形近似せずに状態遷移関数を求める
以下を式①から式④とする。
式①に式③を代入し、式⑤を得る。
式②に式③,④を代入し、式⑥を得る。
すなわち下記が式⑤、式⑥である。
すなわち
反力Hが左右の車輪で等分される場合
以下を式①から式④とする。
式①に式③を代入し、式⑤を得る。
式②に式③,④を代入し、式⑥を得る。
すなわち下記が式⑤、式⑥である。
すなわち
ジャイロセンサを状態推定に用いる
#反力H、Vを近似しない場合 より振り子の加速度
重力加速度
ジャイロセンサの観測値を
すなわち