はじめに
最近Twitterで話題のモータの物理シミュレータモデルのパラメータ同定をやってみました.
モータにはRobStride社のRS02を,物理シミュレータにはMuJoCoを用います.
手順はこちらのツイートを参考にさせていただきました.ありがたや...
モータのパラメータ同定とは?
シミュレータ(MuJoCo)の動力学モデルに含まれる物理パラメータ(armature=慣性,frictionloss=クーロン摩擦,damping=粘性減衰)を実機の入出力データに当てはめて推定する作業.これにより sim2real ギャップを縮めてRL 学習結果が実機で動くようにする作業や手法のことです.
ワークフロー:
- recorder/ — Linux + SocketCAN 上で 1 kHz RT ループを回しマルチサイン純トルク(kp=0, kd=0, torque_ff=multi_sine(t))で関節を励振して位置・速度を CSV 記録する.
- optimizer_mujoco/sysid/optimize.py — シミュレーションリプレイ法で scipy.optimize.least_squares(TRF、数値ヤコビアン)により 3 パラメータ θ を最小二乗推定する.
- optimizer_mujoco/sysid/validate.py — PD クローズドループで初期値 vs 同定値 vs 実機の追従誤差を比較.
- optimizer_isaac/ — 同じ枠組みを Isaac Sim バックエンドへ移植中(feat ブランチで作業中)
要は「実機トルク→関節応答」のデータから MuJoCo/Isaac の摩擦・慣性パラメータを逆算し、RL 用シミュレータの忠実度を上げるための一連のツール群.
実験用コード
https://github.com/kim-xps12/bsl_droid_control/tree/main/sysid_ws に全て上がっています.気になる方は参照ください.
環境
- Jetson Orin Nano Super
- USB-CANアダプタ: DSD TECH SH-C31G
- RobStride RS02
こんな感じで実施しました.
実機実験の流れ
データの記録
青舶の本体に組み付け済みの左腰ヨーモータ(ID 11)でデータ収集を行います.
励振は次式を用いました.
torque(t) = amp × (sin(2π·f·t) + 0.6·sin(2π·3.4f·t) + 0.3·sin(2π·7.4f·t))
以下のコマンドを実行します.
cd sysid_ws/recorder
sudo build/sysid_recorder \
--interface can1_rs \
--motor-id 11 \
--freq 4.5 \
--amp 2.5 \
--duration 30.0 \
--output ../optimizer/data/recording_sysid_id11.csv
実行結果.30sec分,問題なく取れていそうですね.
=== sysid_recorder ===
Mode: SYSID (multi-sine)
Interface: can1_rs
Motor ID: 11
Freq: 4.5 Hz
Amp: 2.5 Nm
Gains: kp=0.0 kd=0.0 (pure torque)
Drift guard: +/- 2pi rad / +/- 30 rad/s
Duration: 30 s
Torque lim: 12 Nm
Output: ../optimizer/data/recording_sysid_id11.csv
Connected to can1_rs
Motor probe OK: pos=0.171044 rad, vel=0.0496841 rad/s
Recording... (Ctrl+C to stop early)
[ 30.0s] tick=30000 missed=0 pos= 3.829 rad
Recording done: 30100 samples, 0 missed (0.0%)
Saved: ../optimizer/data/recording_sysid_id11.csv
パラメータ最適化
30sec分のデータからモデルパラメータをフィッティングします.
cd sysid_ws/optimizer
uv run python sysid/optimize.py \
--csv data/recording_sysid_id11.csv \
--model ../models/rs02_joint.xml \
--output results/identified_params.json
以下のように計算が完了すればOKです.
Loading CSV: data/recording_sysid_id11.csv
30100 valid samples, duration=30.10 s
Optimizing (least_squares / TRF, x0=[0.01 0.1 0.05]) ...
iter= 1 armature=0.010000 frictionloss=0.100000 damping=0.050000 cost=2.14351924
iter= 6 armature=0.013489 frictionloss=0.050194 damping=0.025634 cost=0.09181274
iter= 11 armature=0.014997 frictionloss=0.044270 damping=0.022955 cost=0.02418515
iter= 16 armature=0.015309 frictionloss=0.039236 damping=0.023216 cost=0.02345425
iter= 21 armature=0.015360 frictionloss=0.001153 damping=0.027921 cost=0.02220637
iter= 26 armature=0.015363 frictionloss=0.001002 damping=0.027940 cost=0.02220158
iter= 31 armature=0.015364 frictionloss=0.001000 damping=0.027940 cost=0.02220150
iter= 36 armature=0.015364 frictionloss=0.001000 damping=0.027940 cost=0.02220150
iter= 41 armature=0.015364 frictionloss=0.001000 damping=0.027940 cost=0.02220150
iter= 46 armature=0.015364 frictionloss=0.001000 damping=0.027940 cost=0.02220150
=== Identified Parameters ===
armature: 0.015364
frictionloss: 0.001000
damping: 0.027940
Final cost: 0.0222015033
Converged: True
Iterations: 13
Saved: results/identified_params.json
得られたモデルパラメータがこちら.
{
"armature": 0.01536404780450517,
"frictionloss": 0.0010000000000000002,
"damping": 0.027939873183805693
}
これを使って評価していきましょう.
評価用データの記録
次節の評価で使う「モータ実機側の挙動」のデータ収集をします.まずは最適化の材料を取るのに使ったモータで実施.
cd sysid_ws/recorder
sudo build/sysid_recorder \
--interface can1_rs \
--motor-id 11 \
--validate --kp 8 \
--kd 0.5 \
--duration 30.0 \
--output ../optimizer/data/validation_id11.csv
保存できました.
=== sysid_recorder ===
Mode: VALIDATE (PD)
Interface: can1_rs
Motor ID: 11
kp=8 kd=0.5 ramp=200 ms
Duration: 30 s
Torque lim: 12 Nm
Output: ../optimizer/data/validation_id11.csv
Connected to can1_rs
Motor probe OK: pos=-0.992132 rad, vel=0.0174566 rad/s
Recording... (Ctrl+C to stop early)
[ 30.0s] tick=30000 missed=0 pos= 1.877 rad
Recording done: 30100 samples, 0 missed (0.0%)
Saved: ../optimizer/data/validation_id11.csv
材料収集に使ったモータとは別の個体(右脚の腰ヨーモータ)でも評価用のサンプルを取っておきます.
sudo build/sysid_recorder \
--interface can2_rs \
--motor-id 21 \
--validate \
--kp 8 \
--kd 0.5 \
--duration 30.0 \
--output ../optimizer/data/validation_id21.csv
取れました.
=== sysid_recorder ===
Mode: VALIDATE (PD)
Interface: can2_rs
Motor ID: 21
kp=8 kd=0.5 ramp=200 ms
Duration: 30 s
Torque lim: 12 Nm
Output: ../optimizer/data/validation_id21.csv
Connected to can2_rs
Motor probe OK: pos=-2.80497 rad, vel=-0.0349132 rad/s
Recording... (Ctrl+C to stop early)
[ 30.0s] tick=30000 missed=0 pos= 1.857 rad
Recording done: 30100 samples, 0 missed (0.0%)
Saved: ../optimizer/data/validation_id21.csv
評価
先ほど実機に与えた指令値をシミュレータにも与えて,その振る舞いを比較します.
パラメータ同定で得られた物理特性の反映の有無で,実機に振る舞いが近づいたら成功です.
まずはID 11のモータから.
cd sysid_ws/optimizer
uv run python sysid/validate.py \
--csv data/validation_id11.csv \
--model ../models/rs02_joint.xml \
--params results/identified_params.json \
--output-png results/validation_id11.png
Loading CSV: data/validation_id11.csv
30100 valid samples, duration=30.10 s
Detected torque_limit: 12.00 Nm (used to match sim clamp to real motor)
Simulating with initial parameters ...
Loading identified params: results/identified_params.json
armature=0.015364 frictionloss=0.001000 damping=0.027940
Simulating with identified parameters ...
=== Validation Results ===
Position RMSE: initial=0.0788 rad → identified=0.0363 rad (improvement: 53.9%)
Velocity RMSE: initial=1.8113 rad/s → identified=0.7499 rad/s (improvement: 58.6%)
Saved: results/validation_id11.png
下段の同定されたモデルパラメータを用いた場合の方が,実機の振る舞いに近づいていますね.良い感じ.
ID 21のモータでもやってみます.
cd sysid_ws/optimizer
uv run python sysid/validate.py \
--csv data/validation_id21.csv \
--model ../models/rs02_joint.xml \
--params results/identified_params.json \
--output-png results/validation_id21.png
Loading CSV: data/validation_id21.csv
30100 valid samples, duration=30.10 s
Detected torque_limit: 12.00 Nm (used to match sim clamp to real motor)
Simulating with initial parameters ...
Loading identified params: results/identified_params.json
armature=0.015364 frictionloss=0.001000 damping=0.027940
Simulating with identified parameters ...
=== Validation Results ===
Position RMSE: initial=0.0781 rad → identified=0.0380 rad (improvement: 51.4%)
Velocity RMSE: initial=1.7809 rad/s → identified=0.7419 rad/s (improvement: 58.3%)
Saved: results/validation_id21.png
こちらでも同様の結果になりました.モデルパラメータを異なる個体間で使い回しも大丈夫そうな雰囲気で一安心です.
再実験
ここで,Claude Codeくんが「"frictionloss": 0.001000...は同定処理の下限に張り付いてるから微妙かも??」と提言してくれたので修正してもらいます.良い時代になりました.
torque(t) = amp × (0.4·cos(2π·0.3f·t) + sin(2π·f·t)
+ 0.6·sin(2π·3.4f·t) + 0.3·sin(2π·7.4f·t))
0.4·cos(2π·0.3f·t)の低周波成分が増えました.これで再実験です.
データの記録
cd sysid_ws/recorder
sudo build/sysid_recorder \
--interface can1_rs \
--motor-id 11 \
--freq 4.5 \
--amp 1.5 \
--duration 30 \
--output ../optimizer/data/recording_sysid_id11_v2.csv
--ampを先ほどと同じ2.5にしておいたところ,可動域を超えて回りすぎるのか動作が途中で止まったので1.5に下げています.これで30sec分ちゃんと取れました.
=== sysid_recorder ===
Mode: SYSID (multi-sine)
Interface: can1_rs
Motor ID: 11
Freq: 4.5 Hz
Amp: 1.5 Nm
Gains: kp=0.0 kd=0.0 (pure torque)
Drift guard: +/- 2pi rad / +/- 30 rad/s
Duration: 30 s
Torque lim: 12 Nm
Output: ../optimizer/data/recording_sysid_id11_v2.csv
Connected to can1_rs
Motor probe OK: pos=-6.09239 rad, vel=-0.100711 rad/s
Recording... (Ctrl+C to stop early)
[ 30.0s] tick=30000 missed=0 pos= -4.156 rad
Recording done: 30100 samples, 0 missed (0.0%)
Saved: ../optimizer/data/recording_sysid_id11_v2.csv
パラメータ最適化
cd sysid_ws/optimizer
uv run python sysid/optimize.py \
--csv data/recording_sysid_id11_v2.csv \
--model ../models/rs02_joint.xml \
--output results/identified_params_v2.json
実行結果
Loading CSV: data/recording_sysid_id11_v2.csv
30100 valid samples, duration=30.10 s
Optimizing (least_squares / TRF, x0=[0.01 0.1 0.05]) ...
iter= 1 armature=0.010000 frictionloss=0.100000 damping=0.050000 cost=0.84936673
iter= 6 armature=0.014158 frictionloss=0.054734 damping=0.030671 cost=0.13219040
iter= 11 armature=0.015488 frictionloss=0.047875 damping=0.026856 cost=0.03318613
iter= 16 armature=0.015406 frictionloss=0.045297 damping=0.025956 cost=0.02627268
iter= 21 armature=0.015352 frictionloss=0.027550 damping=0.029456 cost=0.02590879
iter= 26 armature=0.015365 frictionloss=0.029607 damping=0.029041 cost=0.02589557
iter= 31 armature=0.015349 frictionloss=0.030662 damping=0.028836 cost=0.02589141
iter= 36 armature=0.015350 frictionloss=0.030512 damping=0.028867 cost=0.02589139
iter= 41 armature=0.015350 frictionloss=0.030514 damping=0.028867 cost=0.02589140
iter= 46 armature=0.015350 frictionloss=0.030513 damping=0.028867 cost=0.02589139
iter= 51 armature=0.015350 frictionloss=0.030513 damping=0.028867 cost=0.02589139
iter= 56 armature=0.015350 frictionloss=0.030513 damping=0.028867 cost=0.02589139
iter= 61 armature=0.015350 frictionloss=0.030513 damping=0.028867 cost=0.02589139
iter= 66 armature=0.015350 frictionloss=0.030513 damping=0.028867 cost=0.02589139
iter= 71 armature=0.015350 frictionloss=0.030513 damping=0.028867 cost=0.02589139
iter= 76 armature=0.015350 frictionloss=0.030513 damping=0.028867 cost=0.02589139
=== Identified Parameters ===
armature: 0.015350
frictionloss: 0.030513
damping: 0.028867
Final cost: 0.0258913919
Converged: True
Iterations: 29
Saved: results/identified_params_v2.json
以下のパラメータが得られました.
{
"armature": 0.015349920579792717,
"frictionloss": 0.030512575816449027,
"damping": 0.028866991695064865
}
"frictionloss": 0.030と下限の0.001から上がりましたね.
再評価
まずはID 11で実施.
cd sysid_ws/optimizer
uv run python sysid/validate.py \
--csv data/validation_id11.csv \
--model ../models/rs02_joint.xml \
--params results/identified_params_v2.json \
--kp 8 --kd 0.5 \
--output-png results/validation_id11_v2.png
Loading CSV: data/validation_id11.csv
30100 valid samples, duration=30.10 s
Detected torque_limit: 12.00 Nm (used to match sim clamp to real motor)
Simulating with initial parameters ...
Loading identified params: results/identified_params_v2.json
armature=0.015350 frictionloss=0.030513 damping=0.028867
Simulating with identified parameters ...
=== Validation Results ===
Position RMSE: initial=0.0788 rad → identified=0.0352 rad (improvement: 55.3%)
Velocity RMSE: initial=1.8113 rad/s → identified=0.7477 rad/s (improvement: 58.7%)
Saved: results/validation_id11_v2.png
デグレもなさそうです.
次にID 21で実施.
cd sysid_ws/optimizer
uv run python sysid/validate.py \
--csv data/validation_id21.csv \
--model ../models/rs02_joint.xml \
--params results/identified_params_v2.json \
--kp 8 --kd 0.5 \
--output-png results/validation_id21_v2.png
Loading CSV: data/validation_id21.csv
30100 valid samples, duration=30.10 s
Detected torque_limit: 12.00 Nm (used to match sim clamp to real motor)
Simulating with initial parameters ...
Loading identified params: results/identified_params_v2.json
armature=0.015350 frictionloss=0.030513 damping=0.028867
Simulating with identified parameters ...
=== Validation Results ===
Position RMSE: initial=0.0781 rad → identified=0.0369 rad (improvement: 52.8%)
Velocity RMSE: initial=1.7809 rad/s → identified=0.7392 rad/s (improvement: 58.5%)
Saved: results/validation_id21_v2.png
こちらも問題なさそう.細かい数字で見るとpos RMSEが0.0363→0.0352と3%程度改善しているようです.誤差な気もしますね...まあ悪化していないので修正版を採用することにします.
詳細が気になる方はリポジトリ内の修正レポートをご覧ください.
おわりに
本稿ではRobStride RS02のモデル同定を実施しました.今後はこれを使ってシミュレータで歩容獲得→実機適用に挑戦する予定です.
それでは.



