33
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Python】物理エンジンMuJoCoの紹介&MJCFドキュメント【MuJoCoチュートリアル①】

Last updated at Posted at 2024-03-11

 本記事では、無料のオープンソース物理エンジンであるMuJoCoの解説およびチュートリアルを記述します。記述はバージョン3.1.3(Python: mujoco 3.1.3)に基づきます。

 本記事はXML Referenceに基づくMJCF形式の説明を中心に、具体例や検証などを併せた内容となっています。<actuator>要素およびmjModel&mjData構造体などについてはあまり取り扱いません。別記事を参照してください。

サイズの大きい表はスタイルが崩れやすいため画像として掲載しています。テキストとして見たい場合は、その下の「表をテキスト形式で見る」を押してください。

目次

 内容が膨大なため、大見出し&特に重要な項目のみを以下に示します。詳細はその下の「目次 - 詳細」を参照してください。

【目次 - 詳細】

 本記事はMuJoCo公式ドキュメントのXML Referenceをベースに記述を行っています。当該ぺージに記載された各項目の実装状況は以下を参照してください。

【XML Reference実装状況】

 XML Referenceの全項目は以下の通りです。このうち、記述を行ったものについてはリンクを貼っています。最も低い階層の要素(compilerやsizeなど)は、親にmujocoを持ちます。また、「->」は親子関係を示します。

はじめに & モチベーション

 MuJoCoMulti-Joint dynamics with Contact)は、環境と相互作用する関節構造の高速かつ正確なシミュレーションを行うことのできる物理エンジンです。当初は有料でしたが、2021年にDeepMindにより買収されたことをきっかけに、現在では無料のオープンソース物理エンジンとして使用することができます。制御、状態推定、システム同定、機構設計、逆動力学によるデータ解析、機械学習アプリケーションの並列サンプリングなど様々なモデルベース計算の実装が可能です。

 Pythonにおける物理エンジンとしてはPyBullet(Bullet)が有名です。これと比較しMuJoCoは(特に日本語の)ドキュメントが少なく若干とっつきにくいかと思います。しかし、基本的に多関節ロボットのシミュレーションにおいては計算速度上の利点があり[1]、強化学習等に用いる上で大きな利点を持ちます。詳しくは別記事にて解説しましたが、PyBulletと比較して処理速度が2~3倍速く、また正確性や安定性も高いです。このため、強化学習などで用いる際には大きなアドバンテージとなります。

 名前に多関節動力学を含むところからわかるように、MuJoCoは多関節の力学、すなわちロボット工学等におけるシミュレーションにおいて高速に処理を行うことが可能です[1]。一方でスイカゲームのようなシミュレーションではPyBullet(Bullet)よりも処理が遅くなるため[1]、関節構造を持たないシステムのシミュレーションを行う上での利点はあまりないようです。これは採用している積分方法(ルンゲクッタ積分と半陰的オイラー積分など)による違いです。

 MuJoCoで使用される数値はすべてSI単位系およびSI併用単位(°)に依ります。例えば、長さや変位としての数値の単位は $\mathrm{m}$ です。角度については $\mathrm{rad}$ と ° を両方使用できるケースと、どちらかしか使用できないケースがあります。既定では入力に関して ° が使用されますが、<compiler>angle属性を変更することで $\mathrm{rad}$ を使用できるようになります。

インストール

 現在(24/03/11; PyPI mujoco 3.1.3)、Python 3.8以上の環境であれば、Windows環境であってもpipによるインストールが可能なようです。はじめに、適当な環境でMuJoCoをインストールします。一部環境でpipによるインストールが行えない場合は、GitHubのソースコードを直接ビルドすることもできます。Python Bindings/Building from sourceを参照してください。

pip install mujoco

 以降は通常のモジュールと同様に使用することができます。基盤であるC APIとの互換性のために、Python APIには一部Pythonicではない構造がありますが、基本的には通常のPythonのモジュールと同様に使用することが可能です。

tutorial.py
import mujoco

print(mujoco.__version__)  # 3.1.3

pipによるインストール内容には含まれませんが、通常のダウンロード先から取得したライブラリにはGUIアプリも存在します。アプリケーションはOpenGLでレンダリングされ、インタラクティブな操作が可能です。

【GUIアプリケーションについて】

 上述のダウンロード先からダウンロードしたzipファイルの中にはbin/simulate.exeというアプリケーションが存在します(Windows版)。

 ジョイントの状態等各種の状態値をリアルタイムに見ることができたり、アクチュエータへの制御入力などの各種値をリアルタイムに変更することができたりするなど、MuJoCoの仕組みを理解するのに便利です。

MuJoCo_XML_GUIアプリケーション_スクショ.png

GUIアプリケーション操作画面

GUIの詳細については次の記事なども参考になるかと思います。

https://qiita.com/sunrise_lover/items/58ea54a33ff5114cb8ef

なんかLinuxじゃないとインストールできないとか、Docker使わないとだめとか昔ありませんでしたっけ?すごくインストールが簡単になってますね。…そういうのはmujoco_pyのころの話なのかな?

モデリング (MJCF)

 MuJoCoで読み込むことのできるモデルのファイル形式は2つです。一つがMuJoCo専用のMJCFであり、もう一つがより一般的でROSなどで使用されるURDFです。どちらもXML形式で記述され、ロボットの形状や物理的特性など、様々な要素を詳細にモデリングすることが可能です。

 本記事では、ネイティブの表現であるMJCFについて記述を行います。URDFはMuJoCoだけを使用する場合には必要とならないほか、一応MJCFとURDFの相互変換は可能っぽいので本節では取り扱いません。

URDF:URDF(Unified Robot Description Format)とは、ROSなどで使用される、一般的なロボットの構造を記述するXMLファイル形式です。MuJoCoではURDF拡張機能により、URDFを直接読み込むことが可能です。ただし、多くの場合URDFはあまり編集されないため、一度URDFファイルをMJCFに変換し、これを編集する方が便利なようです。

【MuJoCoのモデルとは?】

 MuJoCoには「モデル」と呼ばれるエンティティが複数あります。その違いは①ファイルとして保存するかメモリ上に保持するか、②高水準/低水準のどちらか、の2点です。

MuJoCoの「モデル」の種類[3]

高水準 低水準
ファイル MJCF/URDF (XML) MJB (バイナリ)
メモリ mjCModel (C++クラス) mjModel (C構造体)

すべてのランタイム計算はmjModelで行われます。mjModelは高水準のモデルがコンパイルされたものであるため処理は高速ですが、バージョン間の互換性は(必ずしも)ありません。mjCModelは内部で使用される、MJCFファイルとほぼ一対一に対応するモデルです。現在はこれを外部から使用することはできません。

 以下にmjModelを取得するためのすべての経路を示します[3]。ただし、いまのところ2番の順序は使用することができません。将来的にmjCModelがCラッパーで記述されれば使用可能となるようです。

  1. (テキストエディタ) → MJCF/URDF ファイル → (MuJoCo パーサー → mjCModel → MuJoCo コンパイラ) → mjModel
  2. (ユーザコード) → mjCModel → (MuJoCo コンパイラ) → mjModel
  3. MJBファイル → (MuJoCoローダ) → mjModel

MJCFとは

概要

 MJCF(おそらくMuJoCo Format)はMuJoCo専用の物理オブジェクト記述用のフォーマットであり、接触ダイナミクスのシミュレーションに優れるモデルです。ロボットの形状や物理的特性を記述できるだけでなく、環境や外観など様々な要素を指定することが可能です。

 以下に、red_boxgreen_sphereをMJCFとして定義し、ロードするコードを示します。ここで、最上位要素が<mujoco>であることMJCFの定義であること、またすべての物理要素は<worldbody>の下に定義されることを覚えてください。

load_mjcf_model.py
import mujoco

xml = """
<mujoco>
  <worldbody>
    <geom name="red_box" type="box" size=".2 .2 .2" rgba="1 0 0 1"/>
    <geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1"/>
  </worldbody>
</mujoco>
"""
model = mujoco.MjModel.from_xml_string(xml)

上のコードではオブジェクトの位置(pos属性)やジオメトリタイプ(type属性)が省略されていたりしますが、これはMJCFの属性にはデフォルト値があるためです。例として、位置(pos属性)はデフォルトで0 0 0です。

上記コードの最終行ではfrom_xml_string()を使用してXMLをコンパイルし、MjModelバイナリインスタンスを生成しています。

定義から、最小のMJCFモデルは<mujoco/>となります。

 モデリングの際は公式のモデルギャラリーなどが一助になるかと思います。二足歩行ロボットや手、ドローンなど様々なモデルについて、そのコードと完成図・可動例が公開されているため大変参考になります。

モデルの読込(&保存)

 モデルの読み込みは次の3つの関数によって行うことが可能です。

関数 読込対象 説明
mujoco.MjModel.from_xml_string コード上文字列 MJCFまたはURDFの文字列を読み込みます
mujoco.MjModel.from_xml_path XMLテキストファイル MJCFまたはURDFファイルを読み込みます
mujoco.MjModel.from_binary_path MJBファイル MJBファイルを読み込みます

 MJCFおよびURDFによるモデルはテキストとして読込・コンパイルされます。このとき、MJCFとURDFの最上位要素はそれぞれ<mujoco><robot>である必要があります。MJBファイルはコンパイル済みのモデルを保存したものですから、他の2つと比較して読み込み速度が若干速いです。それでも長くて1秒程度の差とのことなので、基本的にfrom_binary_pathを使用することはないかと思います(特にPython)。

 モデルの保存は関数mujoco.mj_saveModelおよびmj_saveLastXMLにより行うことが可能です。保存されるファイルは.mjb形式であり、コンパイル済みのモデルの構造や設定値などが保存されます。また、モデルの読み込み後に変更を加えた場合には変更後の値が保存されます。

# XMLファイルとして書き出す
mujoco.mj_saveLastXML("arm.xml", model)
# MJBファイルとして書き出す
mujoco.mj_saveModel(model, "example.mjb", None)

上述のようにバイナリ化に関してはあまり使用する機会はないのですが、大規模な骨格筋モデル(<tendon><muscle>を多く使用したもの)についてはバイナリファイルとして保存することが推奨されています。これは<muscle>のlengthrangeの自動計算にかなり時間がかかるためです。

MJCFモデルの構造

キネマティックツリー

 上の具体例でも示したように、MJCFモデルの主要部分はbody要素による木構造、キネマティックツリー(kinematic tree)です。このうち、最上位のボディのみ<worldbody>と呼称されます。

 各ツリー構造の親要素と子要素はジョイントで接続され、XMLの親要素と子要素の関係がそのままMJCFのそれに対応します。主要なボディ要素は<joint><geom><site><camera>および<light>であり、ボディ内で定義された各要素はそのボディのローカルフレームに固定されます。ただしキネマティックツリーはボディ要素の木構造ですので、<default><option>などはこれに含まれません。また、<actuator><tendon>なども一つのボディに関連付けられないため(スタンドアロン要素)同様です。

 MJCFモデルは1つまたは複数のキネマティックツリーを中心とし、これにオプションとアセット、スタンドアロン要素を加えたものです。モデルの要素を以下にまとめます。

モデル要素 説明
Options mjOption, mjVisual, mjStatisticから構成されます。後述します。
Assets Mesh, Skin, Height field, Texture, Materialから構成されます。
キネマティックツリー Body, Joint (DOF), Geom, Site, Camera, Lightから構成されます。
スタンドアロン要素 Tendon, Actuator, Sensor, Equality, Flex, Contact pair, Contact exclude, Custom numeric, Custom text, Custom tuple, Keyframeから構成されます。

 MuJoCoにおけるジョイントの機能は親子間に運動の自由度を与えることです。このため、子のボディ要素の中にジョイントが定義されていない場合、そのボディは親に溶接されます。一方、これが定義されている場合には子は親から動くことが可能となります。また、MJCFでは1つのボディが複数のジョイントを含むことができるため、ダミーボディを作成せず平面上を移動するボディをモデル化することなども可能です(例: スライダx2とヒンジx1)。

【キネマティックツリーの具体例】

 以下にMJCFコードの具体例を示します(不完全なコードですが)。以下のコードの場合、キネマティックツリーに含まれるのは<worldbody>...</worldbody>の部分となります。

 各ボディについて見ていきます。ボディ"b1"<worldbody>に固定されているため、<worldbody>から動くことができません。一方、ボディ"b2""b3"はどちらもジョイントをもつため、どちらも<worldbody>を基準として特定の自由度のもとで動くことが可能です。

<mujoco>
  <default class="main">
    <geom/>
  </default>

  <worldbody>
    <geom/>
    <body name="b1">
      <geom/>
      <body name="b3">
        <joint/>
        <geom/>
      </body>
    </body>
    <body name="b2">
      <joint/>
      <geom/>
    </body>
  </worldbody>
</mujoco>

上述のように、<worldbody><body>は要素名こそ違うもののどちらもボディとして扱われます。このため、<body>要素を定義していない場合であってもmodel.nbodyは1を示します(MjModelのインスタンスmodelから見たとき、<worldbody>は名前が"world"であるボディ)。

ループを持つ構造を作成したい場合、等式制約(<equality>要素)を設定する必要があります。これは各MJCFモデルがキネマティックツリー、すなわち木構造により構築されるためです。

位置と姿勢の指定 (MJCF)

 3次元空間において体積を持つ物体の位置と姿勢は、それぞれ3ずつ自由度を持ちます。MJCFではこれを表現するために、位置は1通り、姿勢は5通りの方法を使用することができます。キネマティックツリーのように親要素を持つ要素の場合、位置・姿勢ともにローカル座標における位置姿勢を指定します。

MJCFは右手系の座標系です。

 位置は次の1通りの方法で記述することができます。例えばキネマティックツリーの場合、親ボディを基準に<geom><joint><site><camera>および<light>の位置を指定することができます。

既定 属性 デフォルト値 説明
pos real(3)
$(x,y,z)$
"0 0 0" ローカル座標における位置を指定します。

 姿勢は次の5通りの方法で記述することができます。既定ではクォータニオンによる表現(quat属性)が使用されており、姿勢を指定するほかの属性でもクォータニオンを用いることが多いです。例えば<key>qpos属性は位置と姿勢を指定しますが(例;"1 3 0 1 0 0 0")、後半の4つはクォータニオン $(q_1,q_2,q_3,q_4)$ を意味します。

 axisangleおよびeulerでは角度を指定しますが、この際に指定する角度の単位は度([°])です。<compiler>angle属性を"degree"から"radian"に変更することで、ラジアンを使用することも可能です。また、コンパイル後の内部表現としてはすべてラジアンが使用されます。

MJCFおよびMuJoCoで使用可能な姿勢の表現

MuJoCo_XML_表_MJCFおよびMuJoCoで使用可能な姿勢の表現.png

【表をテキスト形式で見る】
既定 属性 デフォルト値 説明
quat real(4)
$(q_1,q_2,q_3,q_4)$
"1 0 0 0" クォータニオンによる表現です。
単位ベクトル $(x,y,z)$ を軸として $\theta$ だけ回転したとき $(q_1,q_2,q_3,q_4)=(\cos\frac{\theta}{2}, x\sin\frac{\theta}{2}, y\sin\frac{\theta}{2}, z\sin\frac{\theta}{2})$
axisangle real(4)
$(x,y,z,\theta)$
optional 回転軸のベクトル $(x,y,z)$ と回転角度 $\theta$ を指定します。
回転軸が単位ベクトルである必要はありません。また、回転は右向きであることに注意してください(右手系)。
euler real(3)
$(\alpha,\beta, \gamma)$
optional オイラー角です
$(\alpha,\beta, \gamma)$ は既定ではおそらくx-y-z系のオイラー角です。<compiler>eulerseqから変更することができます。
xyaxes real(6)
$(x_h,y_h,z_h,x_v,y_v,z_v)$
optional 回転後の座標系のX軸とY軸を指定します。
新しい座標系のx軸は $(x_h,y_h,z_h)$ 、y軸は $(x_v,y_v,z_v)$ により定義され、z軸はその外積として計算されます。
カメラフレームの指定に便利です。
zaxis real(3)
$(x_z,y_z,z_z)$
optional 回転後の座標系のz軸を指定します。
コンパイラが $(0,0,1)$ と $(x_z,y_z,z_z)$ が一致するような最小の回転を見つけ、同時に暗黙的にx,y軸を決定します。
z軸を中心とした回転対称性を持つ<geom>や、フレームのz軸に沿った方向を持つ<light>に便利です。

モデルの使い方

 前節ではMJCFという形式の説明を行いました。この節では、その作成したモデルをPython上でどのようにして扱うかについての説明を行います。

MjModel & MjData (MJCF)

MjModel

 上に書いたようにMJCFのモデルmodelはプログラム上でコンパイル済みの、MjModelのインスタンスとして利用します。MjModel時間の経過とともに変化しないすべての量を保持します。例えば以下のコードでは、geom要素の総数、およびそれぞれの色を確認しています。

# geom要素の総数
print(model.ngeom) # 2
# それぞれジオメトリの色 : numpy.ndarray
print(model.rgba)  # array([[1., 0., 0., 1.],
                   #        [0., 1., 0., 1.], dtype=float32)

model.ngeomの返す数:model内に<geom><body>が一つずつ定義され、その<body>の中に3個<geom>が定義されていている場合、ngeom4を返します。

モデルの各要素についてはname属性やid属性を利用してもアクセスすることが可能です(Name access)。ここで、body要素についてはXML上の定義にかかわらず"world"が0番目の要素として定義されることには注意したいです。

# geom要素の red_box のidを取得
print(model.geom("red_box").id) # 0
# id属性の値からもアクセス可能
print(model.geom(0).name)       # "red_box"

# body要素の world のidを取得
print(model.nbody)              # 1
print(model.body("world").id)   # 0

model.geom("red_box")からは、red_boxジオメトリでアクセス可能なすべてのプロパティを見ることができます。

MjData

 MjModelが時間の経過とともに変化しない量を扱う一方で、MjDataは時間により変化する量を保持します。正確には、時間、一般化位置(generalized position)および一般化速度からなる状態と、それに依存する量MjDataでは扱います。ここで、時間、一般化位置および一般化速度はそれぞれ次のように取得できます。

data = mujoco.MjData(model)

# 左から順に時間、一般化位置、一般化速度
print(data.time, data.qpos, data.qvel) # 0.0 [] []

 また、geom_xposを使用することで、各オブジェクトのデカルト座標も取得することができます。

 ところで、下の出力結果のうち初めの方を見るとどちらも $(0,0,0)$ を指しており、xmlの定義で"green_sphere"pos属性を".2 .2 .2"と定義したのと差異があることがわかります。これは各物体の位置関係に基づく位置変化を明示的に伝播(特に関節を持つ場合)させる必要があるためであり、このためにmj_kinematics関数を使用します。今回は、modelの位置関係をdataの各要素に伝播させています。

# 運動学適用前
print(data.geom_xpos)    # array([[0., 0., 0.],
                         #        [0., 0., 0.]])

# 運動学適用後
mujoco.mj_kinematics(model, data)
# green_sphereの位置
print(data.geom(1).xpos) # array([.2, .2, .2])

入出力値の単位(長さ・角度)

 冒頭でも述べたように、MuJoCoではSI単位系およびSI併用単位(°)を単位とした数値を扱います。このうち長さや変位に関する単位は $\mathrm{m}$ 一択であり、またそれに基づく組立単位も $\mathrm{m}$ に準拠したものとなります。このため、入出力で値を操作する際に混乱することはないといえます。

 一方、混乱が生じやすいのが角度やそれに基づく単位です。角度については弧度法( $\mathrm{rad}$ )と度数法( ° )の両方が使用されているため、指定する/出力された値がどちらで表されているか悩ましいケースがあるかと思います。確認したところ、おそらく以下の規則に従っていることがわかりました。

  • 入力 : 角度のみ基本的に度数法角速度などは弧度法
    • <compiler>angle属性を"radian"にしている場合は角度も $\mathrm{rad}$
      • <light>cutoff属性など、ごく一部の属性は上の場合でも度数法で指定
    • mjDataから書換えを行う場合は弧度法で指定
  • 出力 : すべて弧度法
    • mjModelmjData、関数などから得られる値はすべて弧度法
    • 例 : 角度は $\mathrm{rad}$, 角速度は $\mathrm{rad/s}$ 、など

MuJoCoは内部的に角度をすべてラジアンとして扱っています。このためMJCFソース上でのみ、例外的に度数法を使える、と考えた方がよいかと思います。

コンパイル後のモデルのオプション (mjOption, mjVisual, mjStatistic)

 コンパイル後のモデルのオプション値は、以下の3組にすべて保存されます。ここでいうオプションとは<option>で指定可能なものだけではなく、モデル上のすべての設定値(経時変化しないすべての値)を意味します。これらの値はシミュレーションを進める前であればいつでも変更可能です。ただし、一度シミュレーションを開始した後はどのオプションも変更してはいけません(おおよそmj_step実行後)。

コンパイル後のモデルのオプション.modelmjModelのインスタンスを示します.

構造体 インスタンス 説明
mjOption model.opt 物理シミュレーションに影響するすべてのオプションが含まれます。
アルゴリズムの選択、パラメタの設定、シミュレーションパイプラインの有効/無効設定、重力等システムレベルの物理特性などが含まれます。
mjVisual model.vis すべての可視化オプションが含まれます。ただし、追加のOpenGLレンダリングオプションはこれに含まれません。
mjStatistic model.stat コンパイラによって計算されるモデルに関する統計情報が含まれています。

レンダリングとシミュレーション

静止画のレンダリング

 レンダリングはRendererのインスタンスからrender関数を使用して行います。ここで、レンダリングの前に、XMLの定義の<worldbody>...</worldbody>の中に、光源<light name="top" pos="0 0 1"/>を追加しています。

 運動学の適用(mj_kinematics)と同様に、レンダリングにおいてはMjDataMjModelに適用する必要があります。これを行う関数がmj_forwardであり、以下のように書けます。

import mujoco
from PIL import Image

xml = """
<mujoco>
  <worldbody>
    <light name="top" pos="0 0 1"/>
    <geom name="red_box" type="box" size=".2 .2 .2" rgba="1 0 0 1"/>
    <geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1"/>
  </worldbody>
</mujoco>
"""
# ライトを適用したMJCFモデルの作成
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model)

# 順動力学の適用 (data -> model)
mujoco.mj_forward(model, data)
# シーンを保持する MjvScene を更新する
renderer.update_scene(data)

# 画像の表示
Image.fromarray(renderer.render()).show()

上記のコードにより、次の画像を描画することができました。基本的に、位置の変化があった場合にはmujoco.mj_forwardの行以降を、色などの変化のみの場合はrenderer.update_sceneの行以降を再度実行することで画像を更新することができます。

mujoco_レンダー_赤い立方体と緑の球.png

レンダリング結果

<light name="top" pos="0 0 1"/>を追加する:光源を追加しないと、ほとんど真っ黒な画像が出力されることになります。光源を追加することで、上のようにしっかりと色と形がわかる画像を出力することができます。

シミュレーション

自由度の追加

 前節では静止画をレンダリングしました。次に、今節ではアニメーションを作成する方法を見ていきます。

 はじめに、上で使用したMJCFモデルは自由度(DOF)を持たないため、動画を再生しても動きを確認することができません。このため、ここではヒンジジョイントを持つボディを使用することにしましょう。また、MjOptionを使用して、ジョイントを可視化することにします。本節ではxml, model, data, rendererはいずれも以下のMJCFモデルに基づくものとします。

render_video.py
import mujoco

xml = """
<mujoco>
  <worldbody>
    <light name="top" pos="0 0 1"/>
    <body name="box_and_sphere" euler="0 0 -30">
      <joint name="swing" type="hinge" axis="1 -1 0" pos="-.2 -.2 -.2"/>
      <geom name="red_box" type="box" size=".2 .2 .2" rgba="1 0 0 1"/>
      <geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1"/>
    </body>
  </worldbody>
</mujoco>
"""
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model)

# ジョイントの可視化オプションを有効化
scene_option = mujoco.MjvOption()
scene_option.flags[mujoco.mjtVisFlag.mjVIS_JOINT] = True

ここで、"box_and_sphere"は、euler="0 0 -30"属性を使用して、z軸を中心に30°回転されています。これは、アニメーションを見やすくするための操作であり、またキネマティックツリー(kinematic tree)のすべての要素の姿勢が、常に親ボディを基準にしていることを示すためでもあります。すなわち、親ボディである"box_and_sphere"を回転させることで、その子要素である"red_box""green_sphere"も回転しました。

 自由度(Degree of freedom; DOF)について:3次元の世界において、すべての体積を持つ物体は6つの自由度を持ちます(並進3自由度、回転3自由度)。ジョイントは拘束として機能するため、ジョイントで2つの物体を結合すると相対的な自由度が減ぜられます。ただし、MuJoCoでは効率的なシミュレーションを行うために、ジョイントを使用して明示的に追加されない限りオブジェクトは自由度を持ちません

 私たちはジョイントを通してモデルに自由度を設定しますが、ジョイントと自由度は一対一対応するわけではありません("ball""free"は複数の自由度を持つため)。

 ジョイントは位置情報を指定するものであり、自由度は速度と力の情報を指定するものといえます。すなわち、各自由度は摩擦損失、減衰、アーマチュアの慣性のような速度に関連する特性を持ちます。また、システムに作用するすべての一般化力は、自由度の空間(space of DOFs)で表現されます。一方、ジョイントは限界やバネ剛性などの位置に関連する特性を持ちます。

アニメーションの作成

 さて、可動部位を持つオブジェクトを追加できたので、これを描画していきたいと思います。

 MuJoCoにはアニメーションを描画するための関数が存在しません。このため、動画を生成する場合にはmujoco.mj_stepを利用して時間を進めつつ、各タイムステップにおいて前節と同様に画像を生成することになります。これを繋げることで、結果として動画を生成することができます。

render_video.py
duration = 3.8  # (seconds)
framerate = 60  # (Hz)

frames = []
mujoco.mj_resetData(model, data)
while data.time < duration:
  # 時間を進める
  mujoco.mj_step(model, data)

  if len(frames) < data.time * framerate:
    # シーンを更新し、画像としてレンダリングする
    renderer.update_scene(data, scene_option=scene_option)
    pixels = renderer.render()
    frames.append(pixels)

# 動画を保存する(任意の関数)
save_video(frames, "output.mp4", framerate=framerate)

保存された動画を以下に示します。赤い立方体と緑の球が、ヒンジジョイントを中心に回転していることがわかります。

mujoco_レンダー_ジョイントを持つ赤い立方体と緑の玉.gif

ジョイントを持つ赤い立方体と緑の球

【save_video関数について】

 save_video関数:numpy.ndarrayによる画像のリストを動画として保存できる関数であればなんでもよいと思います。以下には、OpenCVを使用したsave_video関数の例を記述します。

video_utils.py
import os

import cv2
import numpy as np
from moviepy.editor import ImageSequenceClip

def save_video(frames, videopath:str, framerate=60, allow_fps_reducing=True):
    """動画を保存する
- frames: ndarrayのlistか、ndarray
- framerate : 動画のfps
- videopath: 保存先のファイルパス、gif/mp4形式のみ
- allow_fps_reducing: gifとして保存する際にfpsを減少(frames枚数を減らす)させることを許すか"""
    # videopathがmp4のものかを確認
    ext = os.path.splitext(videopath)[1]
    assert ext in [".mp4",".gif"]

    # 動画のサイズを取得
    if type(frames)==np.ndarray:
        _, height, width, _ = frames.shape
    else:
        height, width, _ = frames[0].shape
    
    if ext == ".mp4":
        _save_video_as_mp4(frames, videopath, framerate, width, height)
    else:
        save_video_as_gif(frames, videopath, framerate, allow_fps_reducing=allow_fps_reducing)

def _save_video_as_mp4(frames, videopath:str, framerate:int, width:int, height:int):
    # 動画をファイルとして保存
    video_writer = cv2.VideoWriter(videopath, 
                                   cv2.VideoWriter_fourcc(*'mp4v'), 
                                   framerate, 
                                   (width, height))
    for frame in frames:
        # 色の並びをOpenCV用に変換
        image = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
        video_writer.write(image)
    video_writer.release()

def save_video_as_gif(frames, videopath:str, fps:int, gif_fps:int=6, *,
                       slow_down_fps:float=1, 
                       decreased_colors:int=-1,
                       allow_overwrite=False, allow_fps_reducing:bool=False):
    """動画をgifとして保存する
- frames: ndarrayのlistか、ndarray
- videopath: 保存先のファイルパス、gif形式のみ
- fps     : 元動画のfps
- gif_fps : 保存するgifのfps
- slow_down_fps     : 1より大きい場合、動画の速度を1/nにする (2の場合、gifの動画fpsを1/2にする)
- decreased_colors  : 1以上の場合、指定された数に減色する (16未満くらいが望ましい; K-Meansのクラスタ数を指定するため、数が大きいほど時間がかかる)
- allow_overwrite   : Falseの場合、名前の後ろに数字 ("_n") を付加する
- allow_fps_reducing: gifとして保存する際にfpsを減少 (frames枚数を減らす) させることを許すか

推奨設定は以下の通り

- gif_fps = 6
- slow_down_fps = 2 (1などでもよい。1未満は非推奨)
- decreased_colors = 12
- allow_overwrite = True
- allow_fps_reducing = True"""
    assert os.path.splitext(videopath)[1] == ".gif"
    print("gifに変換中...")
    fps /= slow_down_fps

    # フレーム数を減らす
    if allow_fps_reducing and (fps > gif_fps):
        step = int(fps / gif_fps)
        frames = frames[::step]
    else:
        gif_fps = min(int(fps), gif_fps)
    
    # 減色する
    if decreased_colors >= 1:
        frames = decrease_colors_by_k_means(frames, decreased_colors)

    # ファイル名を決定する
    newname = os.path.splitext(videopath)[0]+".gif"
    if not allow_overwrite:
        newname = append_index_num_to_avoid_duplication(newname)
    # 保存する
    clip = ImageSequenceClip(frames, fps=gif_fps)
    clip.write_gif(newname)

def decrease_colors_by_k_means(frames:list[np.ndarray], clusters:int=8):
    """K-Means法により画像群の減色を行う
- clusters : クラスター数 (処理時間を考えると16未満くらいが望ましい)"""
    # ndarray(y,x,[R,G,B]) を ndarray(y*x, [R,G,B]) に変形
    totalframe = np.concatenate([x.reshape((-1,3)) for x in frames])
    totalframe = np.float32(totalframe)

    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
    _, label, center=cv2.kmeans(totalframe, clusters, None,
                                  criteria=criteria, 
                                  attempts=10, flags=cv2.KMEANS_RANDOM_CENTERS)

    center = np.uint8(center)
    n_shift = 0
    for i,frame in enumerate(frames):
        n_pixels = frame.shape[0] * frame.shape[1]
        res = center[label[n_shift:n_shift+n_pixels].flatten()]
        frames[i] = res.reshape((frame.shape))
        n_shift += n_pixels

    return frames

def append_index_num_to_avoid_duplication(filename:str) -> str:
    """ファイル名の重複を避けるために、ファイル名の末尾に数字を付加する"""
    basename, ext = os.path.splitext(filename)
    newname = filename

    i = 1
    while True:
        if not os.path.exists(newname):
            return newname
        # 数字を変更
        newname = basename + f"_{i}" + ext
        i += 1

MJCFの要素 : geom (body)

 ここまでで、MJCFでモデリングを行う際の概要とそれをPythonで使用する方法について見てきました。ここからは、数節にわたってMJCFボディの基本要素の詳細を記述します。

概要 : geom (MJCF/body)

 <geom><body>に取り付けて使用する、幾何学形状・特性を決定するためのオブジェクトです。MuJoCoでは凸の<geom>(とそれ同士の衝突)しかサポートしていないため、非凸のオブジェクトを作成するには凸のジオムの結合として表現する必要があります。

 <body>内で1つまたは複数の<geom>が定義されたとき、その<geom>(群)はボディの外観と衝突のプロパティを決定します。また、inertiafromgeomが"false"でないときには慣性特性の計算にも使用されます。この際には、<geom>の①形状と②密度または質量、および③一様密度の仮定が用いられます。

 シミュレーションにおいて、実は必ずしも<geom>が必要となるわけではありません。接触力こそ計算されませんが、ボディとジョイントのみからシミュレーションを行うことも可能です。

inertiafromgeom : <compiler>inertiafromgeom属性を意味し、geomから質量と慣性の自動推論を行うかを決定します。既定では"auto"であり、質量および慣性要素が指定されていないものについては自動推論を行うようになっています。質量に比べ慣性は大きくなりがちであるので、特にURDFモデルを取得する際などは"true"に設定することが推奨されるようです。

属性 : geom (MJCF/body)

 以下に代表的な属性を示します。すべての属性については、その下の「使用可能な全属性の一覧」を参照してください。

主要な属性

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>のクラス名です。
type 型:選択式〈 "sphere"
["plane","hfield","sphere","capsule","ellipsoid", "cylinder","box","mesh","sdf"]から選択
幾何形状を決定します。後述します。
size 型:real(3)〈 "0 0 0"
サイズを決定します。後述するように、type属性により必要なパラメタの数は変動します。ただし、メッシュを参照しない<geom>でのみ有効となります
rgba 型:real(4)〈 "0.5 0.5 0.5 1"
色と透明度を設定します。
friction 型:real(3)〈 "1 0.005 0.0001"
接触ペアの接触摩擦パラメタです。solmixpriorityにより適用されるかが決定されます。数値の意味は前から順に次の通りです。
(1) 滑り摩擦(接平面の両軸沿い)
(2) ねじり摩擦(接触法線周り)
(3) 転がり摩擦(接平面の両軸周り)
💡friction="0.5"のように指定すると、後ろの値を変えずに前の値だけを変更することができます。例の場合、摩擦係数は.5 .005 .0001として設定されます。
mass 型:real〈 optional
質量を決定し、慣性特性を計算するために使用されます。density属性に優先され、指定された場合は質量から密度が計算されるようになります。
density 型:real〈 "1000"
密度を決定し、慣性特性を計算するために使用されます。既定の"1000"は水の密度にほぼ同じです。
fromto 型:real(6)〈 optional
後述するように、長さとフレーム位置、方向の指定に使用することが可能です。"capsule""box""cylinder""elllipsoid"でのみ指定可能です。
pos 型:real(3)〈 "0 0 0"
<geom>の位置を指定します。基準点は後述します。
quat
axisangle
euler
xyaxes
zaxis
型:real(n)〈 "1 0 0 0" - (quat)
<body>のローカル座標の姿勢を指定します。位置と姿勢の指定節を参照してください。
【使用可能な全属性の一覧】
属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>のクラス名です。
type 型:選択式〈 "sphere"
["plane","hfield","sphere","capsule","ellipsoid", "cylinder","box","mesh","sdf"]から選択
幾何形状を決定します。
contype 型:int〈 "1"
次の属性と合わせて、接触ペアのフィルタリングに使用します。衝突判定を参照してください。
conaffinity 型:int〈 "1"
接触フィルタリング用のビットマスクです。
condim 型:int〈 "3"
接触ペアの接触空間の次元を決定します(ペアの最大値が適用されます)。許容される値とその意味は次の通りです。
"1" - 摩擦のない接触
"3" - 規則的な摩擦接触 + 接平面で滑りが対向
"4" - ソフトコンタクト("3"に加え接触法線周りの回転に対向)
"6" - 物体の不定転がりを防ぐ("4"に加え接平面の2軸回りの回転)
group 型:int〈 "0"
属するグループを指定します。グループ値に基づき、コンパイル時に質量と慣性が計算されます。
priority 型:int〈 "0"
2つの衝突する<geom>のプロパティの組み合わせ方を決定します。接触パラメタを参照してください。
size 型:real(3)〈 "0 0 0"
サイズを決定します。後述するように、type属性により必要なパラメタの数は変動します。ただし、メッシュを参照しない<geom>でのみ有効となります
material 型:string〈 optional
指定された場合、(色を除く)視覚的なプロパティを決定するマテリアルを適用します。
rgba 型:real(4)〈 "0.5 0.5 0.5 1"
色と透明度を設定します。
friction 型:real(3)〈 "1 0.005 0.0001"
接触ペアの接触摩擦パラメタです。solmixpriorityにより適用されるかが決定されます。数値の意味は前から順に次の通りです。
(1) 滑り摩擦(接平面の両軸沿い)
(2) ねじり摩擦(接触法線周り)
(3) 転がり摩擦(接平面の両軸周り)
💡friction="0.5"のように指定すると、後ろの値を変えずに前の値だけを変更することができます。例の場合、摩擦係数は.5 .005 .0001として設定されます。
mass 型:real〈 optional
質量を決定し、慣性特性を計算するために使用されます。density属性に優先され、指定された場合は質量から密度が計算されるようになります。
density 型:real〈 "1000"
密度を決定し、慣性特性を計算するために使用されます。既定の"1000"は水の密度にほぼ同じです。
shellinertia 型:選択式〈 "false"
["false","true"]から選択
"true"の場合、慣性特性はすべての質量が境界に集中していると仮定して計算されます。この場合、密度は表面密度として計算されます。
solmix 型:real〈 "1"
接触パラメタの平均化に使用される重みを指定します。接触パラメタを参照してください。
margin 型:real〈 "0"
接触を検知する距離の閾値です。あくまでmjData.contactに記録されるだけであり、接触力が生じるか否かとは関係がありません。
gap 型:real〈 "0"
アクティブでない接触、すなわち制約ソルバーによって無視されるが、カスタム計算のためにmjData.contactに含まれる接触を有効にするために使用されます。
fromto 型:real(6)〈 optional
後述するように、長さとフレーム位置、方向の指定に使用することが可能です。"capsule""box""cylinder""elllipsoid"でのみ指定可能です。
pos 型:real(3)〈 "0 0 0"
<geom>の位置を指定します。基準点は後述します。
quat
axisangle
euler
xyaxes
zaxis
型:real(n)〈 "1 0 0 0" - (quat)
<body>のローカル座標の姿勢を指定します。位置と姿勢の指定節を参照してください。
hfield 型:string〈 optional
type属性が"hfield"の場合に指定し、高さフィールドアセットの参照先(<asset><hfield>で読込先パスfileを指定)を指定します。
mesh 型:string〈 optional
type属性が"mesh"の場合に必ず指定し、メッシュアセットの参照先(<asset><mesh>で読込先パスfileを指定)を指定します。このとき読み込めるファイルの形式はSTL、OBJおよびMSHです。
プリミティブ"sphere""capsule""cylinder""ellipsoid"または"box")の場合も指定可能であり、形状寸法がメッシュにフィットするように決定されます。この場合もコンパイル後のmjModelでは通常のプリミティブと同様に扱われ、メッシュへの参照は記録されません。
fitscale 型:real〈 "1"
プリミティブがメッシュにフィットされる場合にのみ使用されます。
"1" - 自動フィッティング後の結果は変更されません。
"2" - フィッティング後の<geom>のサイズを2倍にします。
fluidshape 型:選択式〈 "none"
["none","ellipsoid"]から選択
形状の楕円体近似に基づく<geom>レベルの流体相互作用モデルを有効化します。
fluidcoef 型:real(5)〈 "0.5 0.25 1.5 1.0 1.0"
流体相互作用モデルの無次元係数 $(C_{D,blunt},C_{D,slender},C_{D,angular},C_K,C_M)$ です。流体相互作用モデルを参照してください。
user 型:real(n)〈 "0 0 ..."
nuser_geomと同じ要素数を持ちます。ユーザーパラメタを参照してください。

他にsolrefsolimpなる制約ソルバのパラメタも存在するようです。

body>geom詳説 : type

 プリミティブ5種については、その中心とposの座標が一致します。"plane""sphere""caplule"および"box"のみから構成されたモデルは、衝突判定の面で最も効率的です。

 "box"を除いたプリミティブは、レンダリングの際にメッシュに分割されて表示されます。このメッシュの密度は<visual><quality>から調整することが可能です。

以上の型のうち "sphere""capsule""cylinder""ellipsoid"および"box"についてはプリミティブと呼ばれることがあります。MuJoCoはCSG(Constructive Solid Geometry)ベースなんですかね?

以下にボディの要素として定義することのできる<geom>の型を示します。

いずれの<geom>についても、pos属性は原点 $(0,0,0)$ に設定しています。カメラに関しては、正面から見ているものについてはカメラ位置を $(0,-5,0)$ から原点方向に配置しています。

type sizeの型 説明 画像
plane real(3)
$(w_x, w_y, s)$
XY平面です(またはそれに平行な平面)。ワールド本体かその静的な子要素にのみ配置可能です。
sizeパラメタは平面のx,y軸方向の幅の半分 $w_x, w_y$ と、レンダリングのための正方格子線の間隔 $s$ を指定します。 $w_x$ または $w_y$ が0の場合、要素が0の軸方向の幅は無限大として描画されます。
正方格子線の役割は照明と影を改善することです。地面に格子を描画するためには<texture>機能を使用してください。

平面; size="1.5 1.5 1"
hfield 無効 グレースケール画像に基づく高さフィールドです。ワールド本体かその静的な子要素にのみ配置可能です。
<geom>size属性は無視され、<asset><hfield>size(real(4); $(w_x,w_y,h,t)$ )パラメタが使用されます。パラメタはx,y軸方向の幅の半分 $w_x,w_y$ と最大高さ $h$ 、および底面厚み $t$ からなります。ただし、底面厚み $t$ は最小標高点より下の部分の厚みを意味します。例えばposが原点を指し $h=1$ の場合、底面は $z=-1$ 上に形成されます。

size="1 1 0.3 0.05"(上)グレイスケール画像(下)高さフィールド
sphere real(1)
$(r)$
球です。
sizeパラメタは半径 $r$ を指定します。

球; size="1"
capsule real(2)
$(r,h)$
or
real(1)
$(r)$
カプセル(二つの半球で覆われた円柱)です。
sizeパラメタは半球の半径 $r$ と円柱部分の高さの半分 $h$ を指定します。
fromto属性を使用した場合は半径 $r$ のみを指定することになります。詳細は後述します。

カプセル; size="1 1"
ellipsoid real(3)
$(r_x,r_y,r_z)$
or
real(1)
$(r)$
楕円体です。
ローカル座標系のX,Y,Z軸に沿ってスケーリングされた球であり、sizeパラメタは3軸ごとの半径 $(r_x,r_y,r_z)$ を指定します。
fromto属性を使用した場合は半径 $r$ のみを指定することになります。詳細は後述します。
平面との衝突を除き楕円体の接触判定は汎用の凸型コライダー(collider)により行われます。このため、なめらかであるものの上述したように衝突判定の面で若干効率が劣ります。

楕円体; size="1 0.5 1.5"
cylinder real(2)
$(r,h)$
or
real(1)
$(r)$
円柱です。
sizeパラメタは半径 $r$ と、高さの半分 $h$ を指定します。また、円柱はZ軸に沿って形成されます。
fromto属性を使用した場合は半径 $r$ のみを指定することになります。詳細は後述します。

円柱; size="1 1"
box real(3)
$(w_x,x_y,w_z)$
or
real(1)
$(w)$
直方体です。
sizeパラメタは各軸方向の幅の半分 $(w_x,x_y,w_z)$ を指定します。
fromto属性を使用した場合は横幅の半分 $w$ のみを指定することになります。詳細は後述します。

立方体; size="1 1 1"
mesh 無効 メッシュファイルに基づくメッシュです。
<geom>size属性は無視され、メッシュファイルのデータがそのまま出力されます。また、<asset><mesh>scale(real(3); $(r_x,r_y,r_z)$ )パラメタを使用して拡大/縮小を行うことも可能です。scaleに負の値を指定した場合、メッシュはその方向について反転されます。
生成されるメッシュの中心は、既定ではメッシュファイルの原点となります。
省略
sdf 無効 符号付距離フィールド(signed distance field; SDF)を定義します。使用するためには、SDFのプラグインを定義する必要があります。 省略

 このうち"mesh"および"hfield"は、その形状データを外部から取得する必要があります(vertex属性で指定した場合を除く)。ただし、ボディの<geom>には参照先を指定する属性がないため、下記のように<asset><mesh>/<hfield>file属性を指定することになります。

 メッシュとして使用できるファイルの種類はSTLMSHおよびOBJです。また、<mesh>vertex属性から直接頂点を指定することも可能です(例: car.xml)。メッシュを使用する際には、はじめに<asset>でファイルを指定し、これを<geom>"mesh"属性として呼び出す必要があります。これは高さフィールドの場合も同様です(ファイルの種類はPNGまたはカスタムバイナリファイル)。以下に簡易で不完全なコードを示します。

<asset>
  <mesh name="forearm" file="forearm.stl" scale=".5 .5 .5"/>
  <hfield name="terrain" file="terrain.png" size="50 50 10 1"/>
</asset>

<worldbody>
  <geom type="hfield" hfield="terrain"/>
  <body pos="0 0 1"/>
    <joint type="hinge" axis="1 0 0"/>
    <geom type="mesh" mesh="forearm"/>
  </body>
</worldbody>

上記のファイルでは、terrain.pngをもとに高さフィールドを定義し、forearm.stlをもとにメッシュを定義しています。また、メッシュについてはx,y,z軸方向にそれぞれ0.5倍の縮小を行っています。

body>geom詳説 : fromto

 fromto属性は"capsule""box""cylinder""elllipsoid"でのみ指定可能な、形状を決定するのに使用される寸法の一つです。sizeのみを使用して形成した場合、上の4ジオメトリはz方向にしか伸ばすことができません。fromto属性を使用することで、始点と終点による形状の決定が可能となります。

 fromto属性の型はreal(6): $(x_s,y_s,z_s,x_e,y_e,z_e)$ です。fromtoを使用した場合、ジオメトリは始点 $(x_s,y_s,z_s)$ から終点 $(x_e,y_e,z_e)$ に向かって押し出しのような形で形成されることになります。また、size要素で指定できるのは2端点を結ぶ線分に垂直な方向の、半径 $r$ または横幅の半分 $w$ となります。以下にカプセルの場合の例を示します。

カプセル; size=".25" fromto="-0.8 0 -0.6 0.8 0 0.6"

MJCFの要素 : site (body)

 <site>接触判定と質量をもたない<geom>です。質量がないため慣性特性の計算にも使用されません。レンダリングはされます。主な役割はボディ上の相対的な位置を指定することであり、センサ位置の指定や腱の接続点指定、アクチュエータのスライダクランクトランスミッションの構築などに利用されます。

 <geom>の一種であるため属性も似通っています。定義可能な属性は以下の通りです。このうち、type属性については<geom>と比べ使用可能な型の種類が少ないです(["sphere","capsule","ellipsoid","cylinder","box"]の5種)。

  • 使用可能な属性 : name, class, type, group, size, material, rgba, fromto, pos, user, およびquat, axisangle, xyaxes, zaxis, euler
【使用可能な全属性の詳細】

ほぼ再掲です。

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>のクラス名です。
type 型:選択式〈 "sphere"
["sphere","capsule","ellipsoid","cylinder","box"]から選択
幾何形状を決定します。前述のとおりです。
group 型:int〈 "0"
属するグループを指定します。ビジュアライザによりグループ全体のレンダリングを有効/無効化することが可能です。
size 型:real(3)〈 "0.005 0.005 0.005"
サイズを決定します。前述したように、type属性により必要なパラメタの数は変動します。
material 型:string〈 optional
指定された場合、(色を除く)視覚的なプロパティを決定するマテリアルを適用します。
rgba 型:real(4)〈 "0.5 0.5 0.5 1"
色と透明度を設定します。
fromto 型:real(6)〈 optional
前述したように、長さとフレーム位置、方向の指定に使用することが可能です。"capsule""box""cylinder""elllipsoid"でのみ指定可能です。
pos 型:real(3)〈 "0 0 0"
<geom>の位置を指定します。基準点は前述のとおりです。
quat
axisangle
euler
xyaxes
zaxis
型:real(n)〈 "1 0 0 0" - (quat)
<body>のローカル座標の姿勢を指定します。位置と姿勢の指定節を参照してください。
user 型:real(n)〈 "0 0 ..."
nuser_siteと同じ要素数を持ちます。ユーザーパラメタを参照してください。

MJCFの要素 : joint (body)

概要 : joint (MJCF/body)

 <body>内で<joint>が定義されたとき、その<joint>は親のボディと当該ボディの間に動きの自由度を作ります。1つの<body>に対して<joint>は複数定義することができ、この場合定義された順で空間変換が適用されます。一方<joint>が定義されていない場合、そのボディは親ボディに固定されます。

 ジョイントには<joint><freejoint>の2種類があります。前者は"free"型、"ball"型、"slide"型および"hinge"型の4種類に分類されます。一方<freejoint>は内部的に"free"型の<joint>として扱われます。いずれもフリージョイントは<worldbody>を親に持つボディの下でのみ定義可能です。

 モデルで定義されたすべてのジョイントの位置と方向はmjData.qposから取得できます。(直線)速度と角速度はmjData.qvelから取得できます。どちらもキネマティックツリーで記述した順番にデータが格納されます。ただし、これら2つのベクトルの次元は、フリージョイントまたはボールジョイントが使用されている場合には異なります。

<worldbody>に対して<joint>を定義することはできません。

属性 : joint (MJCF/body)

 下の表に代表的な属性を示します。すべての属性については、その下の「使用可能な全属性の一覧」を参照してください。

 <freejoint>ではname属性とgroup属性の2つのみを指定可能です。これは、<freejoint>は以下のデフォルト値を持つ<joint>として扱われるためです。

<joint type="free" stiffness="0" damping="0" frictionloss="0" armature="0"/>

主要な属性

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>のクラス名です。
type 型:選択式〈 hinge
["free","ball","slide","hinge"]から選択
動きの自由度と動き方を決定します。後述します。
group 型:int〈 "0"
属するグループの番号です。カスタムタグや、グループごとの可視化/非可視化の選択に使用することができます。
pos 型:real(3)〈 "0 0 0"
<joint>が定義されたボディの座標系におけるジョイントの位置です。
フリージョイントの場合無視されます。
axis 型:real(3)〈 "0 0 1"
"hinge"の回転軸と"slide"の移動方向を指定します。ただし、ベクトルの長さが $10^{-14}$ 以上となるように値を設定してください。
フリージョイントおよびボールジョイントでは無視されます。
limited 型:選択式〈 "auto"
["false","true","auto"]から選択
ジョイントの可動範囲に制限をかけるかを決定します。可動範囲はrange属性で指定します。
"false" - 制限は無効になります。
"true" - 関節の制限は有効になります。
"auto" - range属性が定義され、<compiler>autolimits属性が"true"であれば制限は有効になります(既定ではautolimitsは有効)。
range 型:real(2)〈 "0 0"
関節の可動範囲です。
refによる指定がない場合、基準(0)は初期状態です。フリージョイントでは無効になります。
"ball" - (二つ目の値で)基準構成に対する最大回転角度[°]を指定します。一つ目の値は0に設定する必要があります。
"slide" - 基準に対する可動量の下限と上限を指定します。
"hinge" - 基準に対する可動量の下限と上限を指定します(単位:度)
ref 型:real〈 "0"
関節の基準位置/角度です。
設定した基準に対する変位量はmjModel.qpos0に格納されます。
"slide""hinge"でのみ有効です。詳細は後述します。
frictionloss 型:real〈 "0"
乾性摩擦による摩擦損失です。この<joint>のつくるすべての自由度に対して同じ値となります。
正の値を設定することで有効になりますが、一応フリージョイントでも有効にすることが可能です。
【使用可能な全属性の一覧】
属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>のクラス名です。
type 型:選択式〈 hinge
["free","ball","slide","hinge"]から選択
動きの自由度と動き方を決定します。後述します。
group 型:int〈 "0"
属するグループの番号です。カスタムタグや、グループごとの可視化/非可視化の選択に使用することができます。
pos 型:real(3)〈 "0 0 0"
<joint>が定義されたボディの座標系におけるジョイントの位置です。
フリージョイントの場合無視されます。
axis 型:real(3)〈 "0 0 1"
"hinge"の回転軸と"slide"の移動方向を指定します。ただし、ベクトルの長さが $10^{-14}$ 以上となるように値を設定してください。
フリージョイントおよびボールジョイントでは無視されます。
springdamper 型:real(2): $(\tau,\zeta)$ 〈 "0 0"
ジョイントの質量-ばね-ダンパ系の時定数 $\tau$ と減衰比 $\zeta$ を指定します。
両方の値が正の場合、指定された値から剛性と減衰の値が計算され、stiffness属性とdamping属性を自動的に上書きします。
limited 型:選択式〈 "auto"
["false","true","auto"]から選択
ジョイントの可動範囲に制限をかけるかを決定します。可動範囲はrange属性で指定します。
"false" - 制限は無効になります。
"true" - 関節の制限は有効になります。
"auto" - range属性が定義され、<compiler>autolimits属性が"true"であれば制限は有効になります(既定ではautolimitsは有効)。
actuatorfrclimited 型:選択式〈 "auto"
["false","true","auto"]から選択
作用するアクチュエータ力をクランプするかを決定します。力をクランプする範囲はactuatorfrcrange属性で指定します。ただし"slide"および"hinge"でのみ有効です。
"false" - クランプは無効になります。
"true" - クランプは有効になります。
"auto" - actuatorfrcrange属性が定義され、<compiler>autolimits属性が"true"であれば制限は有効になります(既定ではautolimitsは有効)。
stiffness 型:real〈 "0"
関節の硬さ(剛性)を指定します。
値が正の場合、springref属性で与えられる平衡位置を持つばねが作成されます。ばねの力は他の受動的な力と合わせて計算されます。
range 型:real(2)〈 "0 0"
関節の可動範囲です。
refによる指定がない場合、基準(0)は初期状態です。フリージョイントでは無効になります。
"ball" - (二つ目の値で)基準構成に対する最大回転角度[°]を指定します。一つ目の値は0に設定する必要があります。
"slide" - 基準に対する可動量の下限と上限を指定します。
"hinge" - 基準に対する可動量の下限と上限を指定します(単位:度)
actuatorfrcrange 型:real(2)〈 "0 0"
アクチュエータの総力をクランプする範囲の下限と上限を指定します。(関節にかかるアクチュエータの力の下限と上限を指定)
"slide""hinge"でのみ有効です。
margin 型:real〈 "0"
制限が有効になる距離の閾値です。制約ソルバが力を生成する基準です。
ref 型:real〈 "0"
関節の基準位置/角度です。
設定した基準に対する変位量はmjModel.qpos0に格納されます。
"slide""hinge"でのみ有効です。詳細は後述します。
springref 型:real〈 "0"
関節のばねが平衡となる関節位置または角度を指定します。
基準に対する変位量はmjModel.qpos_springに格納されます(上記mjModel.qpos0と同様)。詳細は後述します。
armature 型:real〈 "0"
この<joint>によりつくられるすべての自由度のamature inertia(またはrotor inertia / reflected intertia)を指定します。
一般化座標の慣性行列の対角に追加される定数であり、ローター(モータの可動部)の慣性を増幅させます。
damping 型:real〈 "0"
この<joint>により生成されるすべての自由度に適用される減衰です。
制約ソルバにより計算される摩擦損失とは異なり、単に速度に線形な受動力です。減衰の値が大きいと、数値積分器が不安定になることがあります。
frictionloss 型:real〈 "0"
乾性摩擦による摩擦損失です。この<joint>のつくるすべての自由度に対して同じ値となります。
正の値を設定することで有効になりますが、一応フリージョイントでも有効にすることが可能です。
user 型:real(n)〈 "0 0 ..."
ユーザパラメタです。次元はnjnt_userに同じです。

他に制約ソルバに関するパラメタであるsolreflimitsolimplimitsolreffrictionおよびsolimpfrictionがあります。詳細はソルバーパラメタを参照してください。

body>joint詳説 : type

 以下にボディの要素として定義することのできる<joint>の型を示します。ただし、概要節にも記述したように、<freejoint>"free"として定義済みの<joint>に等しいです。このため、下記の"free"の説明は<freejoint>にも当てはまります。

type 自由度 説明
free 並進 $\times 3$
回転 $\times 3$
 他のボディに全く固定されていない、自由な「ジョイント」を定義します。このため、<worldbody>の子ボディでのみ定義可能です。
 ジョイントの位置はボディの中心に一致し、ジョイントの位置と姿勢はボディのグローバルな位置と姿勢に一致します。
※この<joint>を持つボディには、他の<joint>を定義することはできません。
ball 回転 $\times 3$  ボールジョイントを定義します。
 回転中心は上記pos属性に指定された点となります。
※この<joint>を持つボディには、他の回転自由度を持つ<joint>を定義することはできません。
slide 並進 $\times 1$  スライドジョイントを定義します。
 位置posとスライド方向axisにより定義されます。
hinge 回転 $\times 1$  ヒンジジョイントを定義します。
 位置posと回転軸axisにより定義されます。回転は指定された軸を中心に、指定された位置を通るように行われます。

関節角度・角速度等の取得(mjData.qposmjData.qvel

ジョイントタイプと取得できる値

 前述のように、関節位置/角度はdata.qposで、速度/角速度はdata.qvelで取得します(data = mujoco.MjData(model))。取得できるジョイントの位置/角度と速度/角速度は、ジョイントの型により以下のように変化します。

各ジョイントのタイプとqpos/qvelで取得できる値

種類 説明
"free" qpos : real(7)
$(x,y,z,q_1,q_2,q_3,q_4)$
qvel : real(6)
$(v_x,v_y,v_z,\omega_x,\omega_y,\omega_z)$
qposでは各軸方向の位置 $(x,y,z)$ と、クォータニオンによる姿勢表現 $(q_1,q_2,q_3,q_4)$ を取得できます。
qvelでは各軸方向の速度 $(v_x,v_y,v_z)$ と、各軸方向の角速度 $(\omega_x,\omega_y,\omega_z)$ を取得できます。
位置と姿勢の指定節でもふれたように、MuJoCoでの既定の姿勢表現は、1つめの要素を $\cos\frac{\theta}{2}$ とするクォータニオンです。
"ball" qpos : real(4)
$(q_1,q_2,q_3,q_4)$
qvel : real(3)
$(\omega_x,\omega_y,\omega_z)$
qposではクォータニオンによる姿勢表現 $(q_1,q_2,q_3,q_4)$ を取得できます。
qvelでは各軸方向の角速度 $(\omega_x,\omega_y,\omega_z)$ を取得できます。
"slide" qpos : real(1)
$(x)$
qvel : real(1)
$(v)$
qposでは基準位置からの変位 $x$ を取得できます。
qvelでは移動軸方向への移動速度 $v$ を取得できます。
"hinge" qpos : real(1)
$(\theta)$
qvel : real(1)
$(\omega)$
qposでは基準位置からの変位角 $\theta$ を取得できます。ラジアンによる表現です
qvelでは回転軸まわりの角速度 $\omega$ を取得できます。
"free" "ball" "slide" "hinge"

<joint>と可視化時の形状.

 mjData.qposなどを使用する方法のほかに、センサを使用することでもジョイントの角度や角速度などを取得することが可能です。センサを使用した方法では出力値に白色ノイズをかけたり、カットオフを設定したりすることも可能です。このため、現実世界への応用を考えた最適化などの際にはこちらを使用するとよいかもしれません。

ジョイントが複数ある場合のqposqvel

 mjData.qposmjData.qvelから取得できる値は、すべてのジョイントの状態値を1次元の配列に格納したものとなります。このとき、格納される順番はMJCFコード上で<joint>宣言された順となります。

 ここでは、シンプルな腕のモデルを使用し、どのように値が取得されるのかについて見ていきます。モデルに含まれる関節について、定義順にこれを以下に示します。ここで、例えば"hinge"(x軸)は、x軸を回転軸とするヒンジジョイントを指すことにします。

肘(1) 肘(2) 手首(1) 手首(2)
種類 "ball" "hinge"
(y軸)
"hinge"
(x軸)
"hinge"
(y軸)
"hinge"
(z軸)
"free"
qpos要素数 4 1 1 1 1 7
qvel要素数 3 1 1 1 1 6
【詳細 : MJCFモデルとコード】

MJCFモデルはMuJoCoドキュメントのOverviewページにあるexamle.xml[3]をベースにしています。

arm_and_tendon.xml
<mujoco model="arm-and-tendon">
  <visual>
    <scale jointlength="1" jointwidth=".05"/>
    <rgba joint="0 1 0 1"/>
  </visual>

  <default>
    <geom rgba=".8 .6 .4 .3"/>
  </default>

  <asset>
    <texture type="skybox" builtin="gradient" rgb1="1 1 1" rgb2=".6 .8 1" width="256" height="256"/>
  </asset>

  <worldbody>
    <camera name="side" pos="0 -2.3 .2" xyaxes="1 0 0 0 .02 .97"/>
    <camera name="above" pos=".2 .9 1.25" xyaxes="-1 0 0 0 -1 1.5"/>
    <light pos="0 1 1" dir="0 -1 -1" diffuse="1 1 1"/>
    <body pos="0 0 1">
      <joint type="ball"/>
      <geom type="capsule" size="0.06" fromto="0 0 0  0 0 -.4"/>
      <body pos="0 0 -0.4">
        <joint axis="0 1 0"/>
        <joint axis="1 0 0"/>
        <geom type="capsule" size="0.04" fromto="0 0 0  .3 0 0"/>
        <body pos=".3 0 0">
          <joint axis="0 1 0"/>
          <joint axis="0 0 1"/>
          <geom pos=".1 0 0" size="0.1 0.08 0.02" type="ellipsoid"/>
          <site name="end1" pos="0.2 0 0" size="0.01"/>
        </body>
      </body>
    </body>

    <body pos="0.3 0 0.1">
      <joint type="free"/>
      <geom size="0.07 0.1" type="cylinder"/>
      <site name="end2" pos="0 0 0.1" size="0.01"/>
    </body>
  </worldbody>

  <tendon>
    <spatial limited="true" range="0 0.6" width="0.005">
      <site site="end1"/>
      <site site="end2"/>
    </spatial>
  </tendon>
</mujoco>
model = mujoco.MjModel.from_xml_path("arm_and_tendon.xml")
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model, 480, 300)
mujoco.mj_forward(model, data) # 0.0 sの際にモデルを描画する用 

n_seconds = 1
fps_video = 30  # 動画自体のfps
fps_frame = 150 # 動画を1/5倍速にする
n_frames = int(n_seconds * fps_frame)
option = mujoco.MjvOption()
option.flags[mujoco.mjtVisFlag.mjVIS_JOINT] = True # ジョイントを可視化

frames = []
sim_time, qposs, qvels = np.zeros(n_frames), [], [] # matplotlibで描画する用
for i in range(n_frames):
    while data.time * fps_frame < i:
        mujoco.mj_step(model, data)
    # 描画
    renderer.update_scene(data, camera="side", scene_option=option)
    frames.append(renderer.render())
    # グラフ描画用
    sim_time[i] = data.time
    qposs.append(data.qpos.copy())
    qvels.append(data.qvel.copy())

save_video(frames, "output.mp4", fps_video)
qposs, qvels = np.stack(qposs), np.stack(qvels)
# グラフ描画...

 このモデルについて、0秒時点での状態とその挙動を描画したものを以下に示します。<geom>を半透明に設定しているため、すべての<joint>の位置と向きを見ることができます。上に述べたように、<joint>の定義順は肩の根本から肘、手先、缶です。また<tendon>も定義されていますが、これはmjData.qposおよびmjData.qvelには格納されません。

初期状態 動作図

MJCFモデル(左)0秒時点(右)1秒経過時点までの挙動.半透明な部分が<geom>であり、緑の部分は<joint>を可視化したもの

mujoco_腕と腱と缶?_関節角度.png

1秒経過時点までの各関節の角度(左)肩関節のクォータニオン(中)肘関節の角度(右)手首関節の角度

 さて、1秒経過時点でのmjData.qposmjData.qvelに格納された値は次のようになっています。(見やすさのために値の切り捨てを行っています。また上の記述も含めて、正確には1秒までの描画は行っていません)

data.time # 0.994
data.qpos # [ 0.995  0.     0.094  0.     1.523   0.
          #   0.187  0.    -0.458  0.    -0.522  -0.999
          #   0.    -0.010  0.     ]
data.qvel # [ 0.     1.077  0.    -2.250  0.      2.201
          #   0.    -0.854  0.    -1.174  0.     23.316
          #   0.    ]

qposの方は要素数15の1次元配列に、qvelの方は要素数13の1次元配列となっていることがわかると思います。また、qposの最初の4要素は肩のクォータニオン、5~6番目は肘、7~8番目は手首、残りは缶の位置姿勢となっています。

 このように、mjData.qposmjData.qvelの結果はすべての結果を結合したものですので、実際に使用する際にはこれを分割することになります(例えば肘関節の角度はdata.qpos[4:6])。

基準位置/角度など(qpos0など)

 ジョイントには2種類の基準位置/角度を設定することができます。一つがrefで設定する関節の基準位置/角度であり、関節位置/角度を $0$ とする点を決定します。もう一つがspringrefで設定する関節のばねが平衡となる関節位置/角度であり、すべての関節と腱のバネが静止した長さを達成する点を決定します。

 はじめにrefによる基準位置/角度について記述します。先述の腕のモデルの場合、肘が90°曲がった状態で作成されていますが、デフォルトではこの状態が基準(数値 $0$ )として扱われます。本来は肘が伸びた状態を $0$ として設定したいので、初期構成が90°に対応するように、ジョイントのref属性を使用して指定することができます。

【refによる基準位置/角度変更の詳細と具体例】

 refの値を変更すると、実行時に関節位置ベクトルは基準ポーズに対して相対的に解釈されます。特に、ジョイントによって適用される空間変換の量は、mjData.qpos - mjModel.qpos0です。ref属性はスカラージョイントスライドとヒンジにのみ適用されます。ボールジョイントの場合、mjModel.qpos0に保存されるクォータニオンは常に $(1,0,0,0)$ で、これはヌル回転に対応します。フリージョイントの場合、フローティングボディの初期グローバル3D位置とクォータニオンがmjModel.qpos0に保存されます。

 以下は肘を伸ばした状態を $0$ とし、肘を曲げる方向を正方向とした腕のモデルです。肘のY軸ジョイントの向きを逆転させ、refを $90$ に設定しています。以下のモデルを使用すると、0秒時点でのmjData.qposが $1.571\space\mathrm{rad}$ となっており、また肘を伸ばした状態で $0\space\mathrm{rad}$ が出力されるようになっています(mjModel.qpos0は約 $1.57$ )。refが規定値の場合のmjData.qposの値は前節のグラフを参照して下さい。

arm_and_tendon_v2.xml
<mujoco model="arm-and-tendon">
  <default>
    <geom rgba=".8 .6 .4 1"/>
  </default>

  <asset>
    <texture type="skybox" builtin="gradient" rgb1="1 1 1" rgb2=".6 .8 1" width="256" height="256"/>
  </asset>

  <worldbody>
    <camera name="side" pos="0 -2.3 .2" xyaxes="1 0 0 0 .02 .97"/>
    <light pos="0 1 1" dir="0 -1 -1" diffuse="1 1 1"/>
    <body pos="0 0 1">
      <joint type="ball"/>
      <geom type="capsule" size="0.06" fromto="0 0 0  0 0 -.4"/>
      <body pos="0 0 -0.4">
        <joint axis="0 -1 0" ref="90"/>
        <joint axis="1 0 0"/>
        <geom type="capsule" size="0.04" fromto="0 0 0  .3 0 0"/>
        <body pos=".3 0 0">
          <joint axis="0 1 0"/>
          <joint axis="0 0 1"/>
          <geom pos=".1 0 0" size="0.1 0.08 0.02" type="ellipsoid"/>
        </body>
      </body>
    </body>
  </worldbody>
</mujoco>

 次にspringrefによる基準位置/角度について説明します。この属性ではすべての関節と腱のスプリングが静止した長さになる状態を指定します。バネの力は関節の構成がバネの基準状態から外れたときに発生し、外れた量に対して線形になります。基準状態はmjModel.qpos_springに保存されます。"slide""hinge"についてはspringrefを設定可能ですが、"ball"とフリージョイントについてはこの値はモデルの初期設定に対応します。

MJCFの要素 : body

概要 : body (MJCF)

 MJCFモデルにおいて<body>は主要素であり、キネマティックツリーを構成するために使用されます。最上位の要素<worldbody>と、それより下の要素<body>に分類されます。

 ボディは質量と慣性特性を持ちますが、幾何学的特性は持ちません(幾何学的特性はボディに取り付けた<geom>から決定されます)。また、各ボディはそれぞれ2つの座標系を持ちます。一つはボディの定義と他の要素の相対位置を決定する座標系であり、もう一つは慣性フレームです。慣性フレームについてはinertial節を参照してください。

 各時間ステップで、MuJoCoは順運動学(forward kinematics)を再帰的に計算し、グローバルなデカルト座標(global Cartesian coordinates)ですべての体の位置と向きを得ます。これらは、以降のすべての計算の基礎となります。

属性 : body (MJCF)

 下表に<body>および<worldbody>で設定可能なすべての属性を示します。

すべての属性(<body>および<worldbody>

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
<worldbody>では既定で"world"です。
childclass 型:string〈 optional
すべての子孫要素に適用する<default>のクラス名を指定します。ただし、子孫が独自にクラス名を指定した場合には適用外となります。詳しくはdefault節を参照してください。
mocap 型:選択式〈 "false"
["false","true"]から選択
<body>をmocapボディとして扱うかを指定します。
 この属性が"true"に設定されたボディは、mjData.mocap_posおよびmjData.mocap_quatから位置と姿勢を変更することが可能になります。また、インタラクティブビジュアライザーのマウス操作でボディを動かすことも可能となります。flagmocap属性も参照してください。
ジョイントを持たない<worldbody>の子ボディにのみ設定可能です。
pos 型:real(3)〈 optional
<body>のローカル座標の原点位置を指定します。指定されない場合、"0 0 0"(親ローカル座標の原点位置)に設定されます。
quat
axisangle
euler
xyaxes
zaxis
型:real(n)〈 "1 0 0 0" - (quat)
<body>のローカル座標の姿勢を指定します。位置と姿勢の指定節を参照してください。
gravcomp 型:real〈 "0"
<body>の重量に対する重力補正力の割合を指定します。
重力補正力は<body>の重心に働きます。
"1"に設定した場合、重量に等しい上向きの力が働き重力と打消しあいます。
"1"より大きい値の場合、浮力を生み出します。
user 型:real(n)〈 "0 0 ..."
nbody_userと同じ要素数を持ちます。ユーザーパラメタを参照してください。

MJCFの要素 : inertial (body)

 <body>に対して<inertial>要素を定義することで、明示的にボディの質量と慣性特性を指定することができます。この要素が指定されていないボディの質量および慣性特性は、定義された<geom>から自動的に計算されます。また、この要素は一つの<body>に対して最大一つまで定義可能です。

下表に<inertial>で設定可能なすべての属性を示します。

すべての属性(<inertial>

属性 型:〈デフォルト値等〉
説明
pos 型:real(3)〈 required
慣性フレームの原点位置を指定します。
慣性フレームの定義から、posは質量中心位置でもあります。
<inertial>要素を定義すると自動推論が無効化されるため、<geom>から推測可能な場合でもこの属性は必須です。
quat
axisangle
euler
xyaxes
zaxis
型:real(n)〈 "1 0 0 0" - (quat)
慣性特性のための座標系の向きを指定します。
位置と姿勢の指定節を参照してください。
mass 型:real〈 required
<body>の質量を指定します。負の値を指定することはできません
MJCFでは複数のジョイントを設定するためのダミーボディは不要であり、また接合のためには<site>要素が存在します。このため、MJCFにおいて質量が0の<body>を定義する意味はありません
diaginertia 型:real(3): $(I_{11},I_{22},I_{33})$ 〈 optional
対角慣性行列で、慣性フレームに対するボディの慣性を指定します。
対角慣性行列 $I$ の対角要素 $I_{11},I_{22},I_{33}$ を指定します(?)。この属性または次の属性のどちらかについて、必ず定義する必要があります
fullinertia 型:real(6): $(I_{11},I_{22},I_{33},$ $I_{12},I_{13},I_{23})$ 〈 optional
慣性行列の対角要素 $I_{11},I_{22},I_{33}$ および右上の要素 $I_{12},I_{13},I_{23}$ を指定します。
コンパイル時に慣性行列 $I$ から慣性フレームの向きと対角慣性をが計算されます。慣性行列 $I$ が正定値でない場合、エラーが発生します。
【慣性フレームと慣性行列について】

 何らかの座標系に対して、 $x,y,z$ 軸周りの慣性モーメント $I_{xx},I_{yy},I_{zz}$ と、慣性乗積(product of inertia)モーメント $I_{xy},I_{xz},I_{yz}$ を用いて、慣性行列 $I$ は次のように表されます。

I=\left[\begin{array}{ccc}
  I_{xx}&I_{xy}&I_{xz}\\
  I_{xy}&I_{yy}&I_{yz}\\
  I_{xz}&I_{yz}&I_{zz}
\end{array}\right]

 ここで、例えば $I_{xx}=\int_V \rho(y^2+z^2)dV$ であり、 $I_{xy}=-\int_V \rho xy dV$ です。上から、慣性行列は対称行列であることがわかります。このため、fullinertia属性では対角要素 $I_{xx},I_{yy},I_{zz}$ と右上の要素 $I_{xy},I_{xz},I_{yz}$ を指定することで完全な慣性行列を指定しています。

 座標系の取り方によっては慣性行列 $I$ が対角行列となることがあります。このような座標系を慣性フレーム(inertia frame; 原点が質量中心位置に一致し、系の軸がボディの慣性主軸に一致する)といいます。このため、diaginertia属性では対角要素 $I_{xx},I_{yy},I_{zz}$ により対角慣性行列を指定しています。

I=\begin{bmatrix}
  I_{xx}&0&0\\
  0&I_{yy}&0\\
  0&0&I_{zz}
\end{bmatrix}

 ですから、diaginertia属性で慣性行列を指定した場合、posquat等から指定した座標系は慣性フレームとして扱われ(ると思われ)ます。一方、fullinertia属性で慣性行列を指定した場合、posquat等から指定した座標系は慣性フレームとは別のものとして扱われ、コンパイラが別途慣性フレームを計算することになります。

 geomの概要でも書きましたが、<inertial>を使用すると、次のように<geom>を一切含まないボディを定義することも可能になります。…あんまり用途が思いつかないですが、<geom>を含まないボディについて接触力の計算を省略することで、計算速度を向上させたりするんでしょうか?

<mujoco>
  <worldbody>
    <body pos="0 0 0" name="mybody">
      <inertial pos="0 0 0" mass="1" diaginertia=".01 .01 .01"/>
      <joint pos="0 0 -.8"/>
    </body>
  </worldbody>
</mujoco>

MJCFの要素 : camera (body)

概要 : camera (MJCF/body)

 <camrea>要素はレンダリングを行うために用いられるカメラを作成します(定義しない場合にもカメラ自体は存在します)。カメラは主にフレームの原点位置と姿勢の2点を調整することになります。下図に示すように、カメラのフレームの原点はレンダリング画像の中央に位置し、右方向がX軸、上方向がy軸となります。また、カメラの方向はZ軸の負の向きであることに注意してください。

レンダリング結果と<camera>のフレーム.中央に示した座標系は<camera>のフレームのもの

不完全な光学系を持つ実際のカメラをモデリングする場合、水平方向と垂直方向に別々の焦点距離を指定したり、非中心主点を指定したりすることが可能です。

明示的に追加せずに使用できるカメラは、GUIアプリケーションでマウスを使用して自由に動かすことができます。GUIアプリケーションについてはインストール節を参照してください。

属性 : camera (MJCF/body)

 下表に<camera>の代表的な属性を示します。すべての属性については、その下の「使用可能な全属性の一覧」を参照してください。

代表的な属性(<camera>

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>のクラス名です。
mode 型:選択式〈 "fixed"
["fixed","track","trackcom","targetbody","targetbodycom"]から選択
カメラが何を見るかを指定します。
より正確には、ワールド座標における<camera>の位置と姿勢が、順運動学でどのように計算されるかを指定します。後述します。
target 型:string〈 optional
カメラのターゲットとなるボディのname属性を指定します。
mode"targetbody""targetbodycom"の場合に必要です。
fovy 型:real〈 "45"
カメラの垂直方向の視野を指定します。optionにかかわらず、単位は度です。
水平方向の視野はこの値と画面サイズから自動的に計算されます。
pos 型:real(3)〈 "0 0 0"
カメラのフレームの原点位置を指定します。
quat
axisangle
euler
xyaxes
zaxis
型:real(n)〈 "1 0 0 0" - (quat)
<camera>のローカル座標の姿勢を指定します。位置と姿勢の指定節を参照してください。
カメラの場合はxyaxesが特に便利であり、レンダリング画面の横軸と縦軸をそれぞれ直接指定することが可能です。
【使用可能な全属性の一覧】
属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>のクラス名です。
mode 型:選択式〈 "fixed"
["fixed","track","trackcom","targetbody","targetbodycom"]から選択
カメラが何を見るかを指定します。
より正確には、ワールド座標における<camera>の位置と姿勢が、順運動学でどのように計算されるかを指定します。後述します。
target 型:string〈 optional
カメラのターゲットとなるボディのname属性を指定します。
mode"targetbody""targetbodycom"の場合に必要です。
fovy 型:real〈 "45"
カメラの垂直方向の視野を指定します。optionにかかわらず、単位は度です。
水平方向の視野はこの値と画面サイズから自動的に計算されます。
resolution 型:int(2): $(res_w,res_h)$ 〈 "1 1"
ピクセル単位でのカメラの解像度です。
これらの値はレンダリングには使用されません。レンダリングコンテキストを作成するときに必要な解像度を保存します。
focal 型:real(2)〈 "0 0"
長さ単位でのカメラの焦点距離です。
fovy属性と競合します。詳細はカメラを参照してください。
focalpixel 型:int(2)〈 "0 0"
ピクセル単位でのカメラの焦点距離です。
fovy属性と競合します。またfocalfocalpixelの両方が指定された場合、focalpixelの値が使用されます。
principal 型:real(2)〈 "0 0"
長さ単位でのカメラの主点(principal point)です。
fovy属性と競合します。
principalpixel 型:real(2)〈 "0 0"
ピクセル単位でのカメラの主点です。
fovy属性と競合します。またprincipalprincipalpixelの両方が指定された場合、principalpixelの値が使用されます。
sensorsize 型:real(2)〈 "0 0"
長さ単位でのカメラセンサのサイズです。
fovy属性と競合します。またこの属性を指定した場合、resolutionfocalを必ず指定する必要があります。
ipd 型:real〈 "0.068"
瞳孔間距離(Inter-pupilary distance)です。
ステレオコピックレンダリングの際のみ有効となります。距離はX軸方向に沿って、原点から指定した距離÷2だけ左右に離れます。
pos 型:real(3)〈 "0 0 0"
カメラのフレームの原点位置を指定します。
quat
axisangle
euler
xyaxes
zaxis
型:real(n)〈 "1 0 0 0" - (quat)
<camera>のローカル座標の姿勢を指定します。位置と姿勢の指定節を参照してください。
カメラの場合はxyaxesが特に便利であり、レンダリング画面の横軸と縦軸をそれぞれ指定することができます。
user 型:real(n)〈 "0 0 ..."
nuser_camと同じ要素数を持ちます。ユーザーパラメタを参照してください。

body>camera詳説 : mode

 以下に<camrea>mode属性として定義できるすべてのモードについて示します。

mode 説明
fixed  <camera>の定義されているボディに対して、posxyaxes等が固定されるモードです。
track  カメラの位置がワールド座標でボディからある程度オフセットされ、姿勢はワールド座標上で固定されるモードです。
 例えばカメラをボディの上に配置してボディの移動や回転に合わせてカメラを追尾させたときに、カメラの向きを一定にするために使用することができます。
trackcom  trackにほぼ同じですが、オフセットが<camera>の定義されているボディの重心を基準として行われるモードです。
<worldbody>に定義した場合にはモデル全体の質量中心を基準とするため、モデル全体を追尾することが可能です。
targetbody  カメラ位置がボディに固定され、カメラの向きが常にtargetで指定したボディに向くモードです。
 動く物体を定点から観測する際に役立ちます。
targetbodycom  targetbodyにほぼ同じですが、カメラがターゲットの重心を向くモードです。

MJCFの要素 : light (body)

概要 : light (MJCF/body)

 <light>要素はライトを作成します。定義されたボディに合わせて動くため、固定するためには<worldbody>で定義する必要があります。またライトの効果は可算型であり、追加するほどシーンが明るくなります。ただし、同時に有効化できるのは最大8個までです。

 ライトの種類にはスポットライトと指向性ライトの2種類があります。スポットライトdirectional="false")はpos点を中心に全方向に光を放射するライトであり、既定ではこちらに設定されています。指向性ライトdirectional="true")はdirで指定された方向へ前面に光を放つライトであり、太陽のような光です。後者でもposは指定可能ですが、実質無意味です。

ライト なし スポット 指向性
$(0,0,-1)$
指向性
$(1,0,-1)$

ライトの種類と光・影の形状.上方向が $z$ 軸、右方向が $x$ 軸、原点は球の下の黒点.平面は $z=0$ .ライト位置は $(0,0,3)$ 、球は半径 $0.25$ で中心位置 $(0,0,0.5)$ . 指向性の欄の数字は光の指向性のベクトル

属性 : light (MJCF/body)

 下表に<camera>の代表的な属性を示します。すべての属性については、その下の「使用可能な全属性の一覧」を参照してください。

代表的な属性(<camera>

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>のクラス名です。
mode 型:選択式〈 "fixed"
["fixed","track","trackcom","targetbody","targetbodycom"]から選択
ワールド座標系におけるライトの位置と姿勢が、順運動学でどのように計算されるかを指定します。カメラのmode属性と同様です。mode属性についての節を参照してください。
target 型:string〈 optional
追尾ターゲットとなるボディのname属性を指定します。
mode"targetbody""targetbodycom"の場合に必要です。
directional 型:選択式〈 "false"
["false","true"]から選択
"true"の場合、光は指向性を持ちます。
pos 型:real(3)〈 "0 0 0"
ライトの位置を定義します。
スポットライトの場合、この点を中心に光が放射されます。
dir 型:real(3)〈 "0 0 -1"
ライトの方向ベクトル $(x,y,z)$ です。
【使用可能な全属性の一覧】
属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>のクラス名です。
mode 型:選択式〈 "fixed"
["fixed","track","trackcom","targetbody","targetbodycom"]から選択
ワールド座標系におけるライトの位置と姿勢が、順運動学でどのように計算されるかを指定します。カメラのmode属性と同様です。mode属性についての節を参照してください。
target 型:string〈 optional
追尾ターゲットとなるボディのname属性を指定します。
mode"targetbody""targetbodycom"の場合に必要です。
directional 型:選択式〈 "false"
["false","true"]から選択
"true"の場合、光は指向性を持ちます。
castshadow 型:選択式〈 "true"
["false","true"]から選択
物体の影を描画するかをしていします。
物体に光が当たった際に影を作るのはライト側の機能ですが、これはレンダリングの手間が増えるため使用には注意が必要です。
スポットライトの場合は錐状に影が広がり、一方指向性ライトの場合は影の範囲は拡大しません。
active 型:選択式〈 "true"
["false","true"]から選択
ライトのオンオフを設定します。
実行時にライトのオンオフを切り替えるために使用できます。
pos 型:real(3)〈 "0 0 0"
ライトの位置を定義します。
スポットライトの場合、この点を中心に光が放射されます。
dir 型:real(3)〈 "0 0 -1"
ライトの方向ベクトル $(x,y,z)$ です。
attenuation 型:real(3)〈 "1 0 0"
OpenGLの定数減数係数、線形減衰係数、二次減衰係数です。
デフォルトでは減衰なしです。
cutoff 型:real〈 "45"
スポットライトのカットオフ角度です。
optionに関わらず単位は度です。
exponent 型:real〈 "10"
スポットライトの指数です。
カットオフの柔らかさをコントロールします。
ambient 型:real(3)〈 "0 0 0"
スポットライトの周囲の色です。
diffuse 型:real(3)〈 "0.7 0.7 0.7"
光の拡散色です。
specular 型:real(3)〈 "0.3 0.3 0.3"
ライトの鏡面反射色です。

MJCFの要素 : sensor

概要 : sensor (MJCF)

 MuJoCoでは加速度センサやジャイロセンサなど、約40種類のセンサをシミュレートすることが可能です。加えて、コールバック関数を使用することで、ユーザ定義のセンサを作成することも可能です。また、いずれのセンサの値も内部計算では使用されません。

 MJCFでは、センサーは親要素の<sensor>と、子要素(<accelerometer><gyro>などなど)から構成されます。ただし、<sensor>要素は以下の下位要素をまとめる役割しか持たず(グルーピング要素)、センサの具象は子要素で定義することになります。

 自由落下する物体と、ワールド座標系にそれぞれ固定したセンサを題材にして、以下にコード例を示します。Pythonではセンサデータの取得をmjData.sensor("sensorname").dataから行うことができます。np.ndarrayとして返されますが、ミュータブルなオブジェクトですので.copy()して使用することをお勧めします。また、C++ネイティブな方法としてmjData.sensordataを使用することも可能ですが、その利便性は大きく劣ります。

mjData.sensor("sensorname").dataは1次元の値についても(floatなどではなく)np.ndarrayとして返します。

 また、加速度センサなどのセンサはローカル座標系の値を取得します。ワールド座標系のセンサworldaccとローカル座標系のセンサinneraccを比較するとこれがわかりやすいです。このようなセンサの場合、センサを接続する対象も重要となります。

two_acc_sensors.xml
<mujoco>
  <worldbody>
    <body pos="0 0 0">
      <freejoint/>
      <geom size="1"/>
      <site name="inner"/>
    </body>
    <site name="world"/>
  </worldbody>

  <sensor>
    <accelerometer name="inneracc" site="inner"/>
    <accelerometer name="worldacc" site="world"/>
  </sensor>
</mujoco>
model = mujoco.MjModel.from_xml_path("two_acc_sensors.xml")
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)

# pythonでのセンサデータ取得
print(data.sensor('inneracc').data) # [0. 0. 0.]
print(data.sensor('worldacc').data) # [0. 0. 9.81]
# C++ベースのセンサデータ取得
print(data.sensordata)              # [0. 0. 0. 0. 0. 9.81]

属性 : sensor (MJCF)

 上述のように<sensor>要素は以下の下位要素をまとめる役割しか持たないため、定義可能な属性が存在しません。具象は<accelerometer><gyro>などの子要素で定義しますが、その種類は膨大であり、またその属性も様々です。このうち、全てのセンサ型に共通の属性は以下の4つです。

すべてのセンサ型に共通の属性

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
noise 型:real〈 "0"
センサ出力に付加されるゼロ平均ガウスノイズの標準偏差です。
ノイズの乗ったセンサデータもその型は変化しません。例えばクォータニオンや単位ベクトルは正規化されたままですし、非負数は非負のままです。
cutoff 型:real〈 "0"
この値が正の場合、センサ出力の絶対値を制限します。
user 型:real(n)〈 "0 0 ..."
nuser_sensorと同じ要素数を持ちます。ユーザーパラメタを参照してください。

 また、各センサは接続可能な対象が限定されています。センサにより、接続可能な対象が<site><body><joint><tendon>または<actuator>のいずれか1種類に限定されるものと、いくつかの対象に接続可能なものが存在します。代表的なものについては下表に記述しますが、これらの詳細はすべてのセンサ節を参照してください。また、全センサは合計40種類ありますが、それらすべてについてもすべてのセンサ節を参照してください。

  • 接続対象が<site>のもの(センサ (site)を参照)
    • touch : タッチセンサ
    • accelerometer : 3軸加速度センサ
    • velocimeter : 3軸速度センサ
    • gyro : 3軸角速度センサ
    • rangefinder : 距離センサ
    • camprojection : 光学式モーションキャプチャ(のマーカー、カメラは<camera>を使用)
  • 接続対象が<joint>のもの(センサ (joint)
    • jointpos : ジョイントの位置/角度センサ("hinge""slide"対象)
    • jointvel : ジョイントの速度/角速度センサ("hinge""slide"対象)
  • そのほかのセンサ
    • clock : シミュレータ上の時刻を出力するセンサ

 いくつかのセンサはmjDataから取得可能な値を測定値として出力します。そのような測定量に対してもわざわざ<sensor>要素を利用する理由としては、noiseで簡単に白色ノイズをかけられる点や、cutoffを設定できる点なのかな、と思います。

MuJoCo にはオフスクリーンレンダリング機能があり、カラーカメラと深度カメラの両方のセンサをシミュレートすることができます。これは標準のセンサーモデルには含まれておらず、コードサンプルsimulate.ccに示されているように、プログラムで行う必要があります。

使用可能なすべてのセンサの一覧

【使用可能なセンサ型の一覧(対象:site)】

siteにのみ接続可能なセンサ

下表に、本項目のすべてのセンサが持つ属性を示します。

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
noise 型:real〈 "0"
センサ出力に付加されるゼロ平均ガウスノイズの標準偏差です。
ノイズの乗ったセンサデータもそのデータ型や値の範囲は変化しません。例えば正の値だけを返すセンサであれば、ノイズ付与後も正の値を返します。
cutoff 型:real〈 "0"
この値が正の場合、センサ出力の絶対値を制限します。
user 型:real(n)〈 "0 0 ..."
nuser_sensorと同じ要素数を持ちます。
site 型:string〈 required
センサを接続する<site>の名前を指定します。

 <site>にのみ接続可能なセンサは以下の9つです。説明欄に「固有の属性」が記述されているセンサ型については、上の属性に加えてその属性を指定する必要があります。

センサ 戻り値の型 説明
touch real タッチセンサです。
センサーの感知領域は<site>の体積内であり、その中で生じた接触力(法線力)の合計値[N]を取得します。測定値は非負のスカラーです。詳しくは後述します。
💡MuJoCoでは面接触は存在しないため、<site>体積/面積はある程度大きくする必要があります。こちらも後述します。
accelerometer real(3)
$(a_x,a_y,a_z)$
3軸加速度センサです
取り付けた<site>のローカル座標系に基づき直線加速度 $(a_x,a_y,a_z)$ を出力します。測定値には重力を含みます
センサの取り付け位置は<site>の中心です。
velocimeter real(3)
$(v_x,v_y,v_z)$
3軸速度センサです。
取り付けた<site>のローカル座標系に基づき直線速度 $(v_x,v_y,v_z)$ を出力します。
センサの取り付け位置は<site>の中心です。
gyro real(3)
$(\omega_x,\omega_y,\omega_z)$
3軸角速度センサ(ジャイロセンサ)です。
取り付けた<site>のローカル座標系に基づき角速度 $(\omega_x,\omega_y,\omega_z)$ を出力します。
センサの取り付け位置は<site>の中心です。
💡慣性計測ユニット(IMU)をシミュレートするため、同じ<site>に取り付けられた加速度計とよく組み合わされて使用されます。
force real(3)
$(f_x,f_y,f_z)$
3軸力センサです。
取り付けた<site>のローカル座標に基づき、子ボディ-親ボディ間の相互作用力 $(f_x,f_y,f_z)$ を出力します。子から親の方向の力を正とし、親ボディに取り付けた場合の符号は逆になります。詳細は後述します。
センサの取り付け位置は<site>の中心です。
torque real(3)
$(\tau_x,\tau_y,\tau_z)$
3軸トルクセンサです。
取り付けた<site>のローカル座標系に基づきトルクを測定します。詳細はforceに同じです。
💡既定ではねじり摩擦と転がり摩擦が計算されないため、常に出力は[0 0 0]となります。有効にするためには対象の<geom>condim属性を4以上に設定してください。
magnetometer real(3)
$(\Phi_x,\Phi_y,\Phi_z)$
3軸磁気センサです。
取り付けた<site>のローカル座標系に基づき、磁束[Wb]を測定します。
センサの取り付け位置は<site>の中心です。
rangefinder real 距離センサです。
最も近い<geom>との距離を測定します。測定方向は<site>のZ軸正方向です。測定方向に<geom>存在しない場合は $-1$ を出力します。
<site>中心が<geom>内に入り込んだ場合はその<geom>の表面までの距離を出力します(<geom>内部を密なものとしては測定しない)。ただし、センサの<site>同じボディの<geom>は測定対象から除外されます。
camprojection real(2)
$(x_{res},y_{res})$
カメラ画像上の<site>の位置を取得できるセンサです。光学式モーションキャプチャのいちカメラのようなものです。
カメラ上での<site>の位置 $(x_{res},y_{res})$ を出力します。原点 $(0,0)$ はカメラ画像左上であり、<site>がカメラ外の場合も値は測定されます。詳細は後述します。
固有の属性
- site : モーションキャプチャのマーカーです。
- camera : 使用する<camera>を指定します。測定値は当該カメラのresolution属性に依存します。
【使用可能なセンサ型の一覧(対象 : body)】

bodyにのみ接続可能なセンサ

下表に、本項目のすべてのセンサが持つ属性を示します。

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
noise 型:real〈 "0"
センサ出力に付加されるゼロ平均ガウスノイズの標準偏差です。
ノイズの乗ったセンサデータもその型は変化しません。例えば正の値だけを返すセンサであれば、ノイズ付与後も正の値を返します。
cutoff 型:real〈 "0"
この値が正の場合、センサ出力の絶対値を制限します。
user 型:real(n)〈 "0 0 ..."
nuser_sensorと同じ要素数を持ちます。
body 型:string〈 required
センサを接続する<body>の名前を指定します。ただし、当該ボディは運動学的サブツリー(kinematic subtree)のルートボディである必要があります(指定したボディの先のツリー要素を計測対象とする)。

<body>にのみ接続可能なセンサは以下の3つです。

センサ 戻り値の型 説明
subtreecom real(3) 指定されたボディをルートボディとするkinematic subtreeの重心を、グローバル座標のもとで取得します。
subtreelinvel real(3) 指定されたボディをルートボディとするkinematic subtreeの重心速度を、グローバル座標のもとで取得します。
subtreeangmom real(3) 指定されたボディをルートボディとするkinematic subtreeの重心まわり角運動量を、グローバル座標のもとで取得します。
【使用可能なセンサ型の一覧(対象 : joint)】

jointにのみ接続可能なセンサ

下表に、本項目のすべてのセンサが持つ属性を示します。

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
noise 型:real〈 "0"
センサ出力に付加されるゼロ平均ガウスノイズの標準偏差です。
ノイズの乗ったセンサデータもその型は変化しません。例えば正の値だけを返すセンサであれば、ノイズ付与後も正の値を返します。
cutoff 型:real〈 "0"
この値が正の場合、センサ出力の絶対値を制限します。
user 型:real(n)〈 "0 0 ..."
nuser_sensorと同じ要素数を持ちます。
joint 型:string〈 required
センサを接続する<joint>の名前を指定します。
jointlimit~系を除き取り付け可能なジョイント型には制限があります。また、すべての測定値はmjData.qposmjData.qvelなどからのコピー、またはその合成値です。

<joint>にのみ接続可能なセンサは以下の8つです。

センサ 戻り値の型 説明
jointpos real ジョイントの位置/角度を計測するセンサです。スカラージョイント("slide"/"hinge")にのみ接続可能です。
jointvel real ジョイントの速度/角速度を計測するセンサです。スカラージョイント("slide"/"hinge")にのみ接続可能です。
jointactuatorfrc real 力センサです。ジョイントに付加されたアクチュエータによる一般化力を測定します。重力等は考慮されず、また複数のアクチュエータが動作に関与する場合でも単一のスカラー量を取得します。
スカラージョイント("slide"/"hinge")にのみ接続可能です。
ballquat real(4) ボールジョイントの姿勢をクォータニオンで測定するセンサです。
ballangvel real(3) ボールジョイントの角速度センサです。各軸回りの角速度[rad/s]を返します。
jointlimitpos real ジョイントの制限(range)に違反した量を取得します(取得される値はmjData.efc_pos - mjData.efc_marginに等しい)。
rangeでは下限値と上限値の2つの値を設定しますが、ジョイントの状態がrange内のとき測定値は $0$ となります。どちらかに違反した場合、その超過量を負数として取得します。また、両方同時に違反した場合は、最初の値に対する超過量を取得します。
基本的に超過量はごくわずか(例 : -0.0003 )です。
jointlimitvel real 速度に関するjointlimitposです。出力はmjData.efc_velからコピーされます。違反していない場合の測定値 $0$ です。
jointlimitfrc real 拘束力に関するjointlimitposです。出力はmjData.efc_forceからコピーされます。違反していない場合の測定値 $0$ です。
【使用可能なセンサ型の一覧(対象 : tendon)】

tendonにのみ接続可能なセンサ

下表に、本項目のすべてのセンサが持つ属性を示します。

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
noise 型:real〈 "0"
センサ出力に付加されるゼロ平均ガウスノイズの標準偏差です。
ノイズの乗ったセンサデータもその型は変化しません。例えば正の値だけを返すセンサであれば、ノイズ付与後も正の値を返します。
cutoff 型:real〈 "0"
この値が正の場合、センサ出力の絶対値を制限します。
user 型:real(n)〈 "0 0 ..."
nuser_sensorと同じ要素数を持ちます。
tendon 型:string〈 required
センサを接続する<tendon>の名前を指定します。

<tendon>にのみ接続可能なセンサは以下の5つです。

センサ 戻り値の型 説明
tendonpos real 腱の長さセンサです。
tendonvel real 腱の速度センサです。
tendonlimitpos real 腱の制限に違反した量を取得します(取得される値はmjData.efc_pos - mjData.efc_marginに等しい)。
限界制約に対する違反量を負数として取得し、腱の状態が制約内の場合の測定値は $0$ となります。
tendonlimitvel real 速度に関するtendonlimitposです。出力はmjData.efc_velからコピーされます。違反していない場合の測定値 $0$ です。
tendonlimitfrc real 拘束力に関するtendonlimitposです。出力はmjData.efc_forceからコピーされます。違反していない場合の測定値 $0$ です。
【使用可能なセンサ型の一覧(対象 : actuator)】

actuatorにのみ接続可能なセンサ

下表に、本項目のすべてのセンサが持つ属性を示します。

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
noise 型:real〈 "0"
センサ出力に付加されるゼロ平均ガウスノイズの標準偏差です。
ノイズの乗ったセンサデータもその型は変化しません。例えば正の値だけを返すセンサであれば、ノイズ付与後も正の値を返します。
cutoff 型:real〈 "0"
この値が正の場合、センサ出力の絶対値を制限します。
user 型:real(n)〈 "0 0 ..."
nuser_sensorと同じ要素数を持ちます。
actuator 型:string〈 required
センサを接続する<actuator>の名前を指定します。
すべてのセンサ出力値はmjData.actuator_lengthmjData.actuator_velocityまたはmjData.actuator_forceからのコピーです。

<actuator>にのみ接続可能なセンサは以下の3つです。

センサ 戻り値の型 説明
actuatorpos real 長さセンサです。
各アクチュエータの長さを計測します。すべてのアクチュエータに取り付け可能です。
actuatorvel real 速度センサです。
各アクチュエータの速度を計測します。すべてのアクチュエータに取り付け可能です。
actuatorfrc real 力センサです。
アクチュエータによる力のスカラー量を取得します。アクチュエータによる一般化力(スカラー力×モーメントアームベクトル)ではありません。すべてのアクチュエータに取り付け可能です。
【使用可能なセンサ型の一覧(対象 : 複数)】

いくつかの対象に接続可能なセンサ

下表に、本項目のすべてのセンサが持つ属性を示します。

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
noise 型:real〈 "0"
センサ出力に付加されるゼロ平均ガウスノイズの標準偏差です。
ノイズの乗ったセンサデータもその型は変化しません。例えば正の値だけを返すセンサであれば、ノイズ付与後も正の値を返します。
cutoff 型:real〈 "0"
この値が正の場合、センサ出力の絶対値を制限します。
user 型:real(n)〈 "0 0 ..."
nuser_sensorと同じ要素数を持ちます。
objtype 型:選択式〈 required
["body","xbody","geom","site","camera"]から選択
センサを接続するオブジェクト型を選択します。このうち、前二つの違いは以下の通りです。
"body" : 指定したボディの慣性系を参照します
"xbody" : 指定したボディの座標系を使用します。通常は親ボディに接続したジョイント原点を原点とする座標系です
objname 型:string〈 required
センサを取り付けるオブジェクトの名前を指定します。
reftype 型:選択式〈 optional
["body","xbody","geom","site","camera"]から選択
使用する参照座標系(frame-of-reference)が設定されているオブジェクトの型を指定します。
reftypeおよびrafname指定された場合、センサはその座標系に従い測定値を出力します。一方、指定されない場合はグローバル座標系に従いセンサの測定値が出力されます。
framelinaccおよびframeangaccはこの属性を持ちません
refname 型:string〈 optional
使用する参照座標系(frame-of-reference)が設定されているオブジェクトの名前を指定します。
framelinaccおよびframeangaccはこの属性を持ちません

本項目のセンサは以下の9つです。

センサ 戻り値の型 説明
framepos real(3) オブジェクトの位置を、グローバル座標または参照座標系のもとで取得するセンサです。
framequat real(4) オブジェクトの姿勢を、グローバル座標または参照座標系のもとで取得するセンサです。
framexaxis real(3) オブジェクトの座標系のX軸ベクトルを、グローバル座標または参照座標系のもとで取得するセンサです。
frameyaxis real(3) オブジェクトの座標系のY軸ベクトルを、グローバル座標または参照座標系のもとで取得するセンサです。
framezaxis real(3) オブジェクトの座標系のZ軸ベクトルを、グローバル座標または参照座標系のもとで取得するセンサです。
framelinvel real(3) オブジェクトの速度ベクトルを、グローバル座標または参照座標系のもとで取得するセンサです。
frameangvel real(3) オブジェクトの角速度ベクトルを、グローバル座標または参照座標系のもとで取得するセンサです。例えばX軸回りに $3 \space\mathrm{rad/s}$ で回転しているとき、測定値は [3 0 0] となります。
framelinacc real(3) オブジェクトの加速度ベクトルを、グローバル座標のもとで取得するセンサです。
frameangacc real(3) オブジェクトの角加速度ベクトルを、グローバル座標のもとで取得するセンサです。
【使用可能なセンサ型の一覧(そのほか)】

そのほかのセンサ

 上の6種類に当てはまらないセンサとして、<clock><user><plugin>の3種類が存在します。

  • <clock>
    • シミュレータ上の時刻を出力します
    • namenoisecutoffおよびuser属性を持ちます。各属性は他のセンサと同様です
  • <user>
    • ユーザが詳細を設定可能なセンサです
    • 詳細はsensor/userを参照してください
  • <plugin>

各センサの詳細

 上に紹介した40近くのセンサのうち、その効果や測定のロジックがわかりにくいものについて詳細の記述を行います。ただし、使用頻度が低そうなものについては記述していないこともあります。

 記事の量が膨大になるため、すべての記述は折りたたんでいます。

詳説 : タッチセンサ(touch)

【タッチセンサの詳細】

 タッチセンサは<site>の体積内にアクティブなセンサ領域を持つセンサです。センサ領域内に生じたすべての接触力(法線力)の総和[N]を取得します。測定値は非負のスカラーです。接触点が<site>と同じボディの<geom>に生じている場合もその接触力は測定値に含まれます。また、接触点が体積の外側にあるが接触力のベクトルがセンサ領域と公差している場合、これも測定値に含まれます。

 幅 $1\space\mathrm{m}, 10^3\space\mathrm{kg}$ の立方体を $0.5\space\mathrm{m}$ から落下させ、タッチセンサの検証を行いました。模式図は下図を参照してください。下図左ではセンサ領域幅と立方体の幅を等しく、下図右ではセンサ領域幅を半分にしています。MuJoCoでは面接触が存在しないため、下図右では測定値は0のままとなっています(MuJoCo立方体の接触力は、垂直に落下した場合四隅に働くため)[7]。このため、<site>の体積/面積はある程度大きくする必要があります

 また、上記の例において安定後の測定値は $9810$ ですが、これは立方体が地面から受ける反力

$${(1 \space\mathrm{m})}^3 \times 1000 \space\mathrm{kg/m^3} \times 9.81\space\mathrm{m/s^2} = 9810\space\mathrm{N}$$

に等しいことがわかります。<site>のサイズを変更せずに中心座標pos=".5 0 -.9"とすると測定値は $4905$ となり、接触点の半分がセンサ領域内から消えた分だけ測定値が減少します。ここから、体積内の力の総和を測定値として出力していることがわかります。

赤が落下する物体、緑がタッチセンサの感知領域.感知領域は衝突判定を持ちません.赤い立方体が垂直に落下した場合に接触力は立方体の四隅に働くため、右は接触力を検知できません

<mujoco>
  <worldbody>
    <camera name="fixed" pos="0 -5 -.5" xyaxes="1 0 0 0 0 1"/>
    <light pos="0 0 3"/>
    <geom type="plane" size="10 10 10" pos="0 0 -1"/>
    <site type="box" name="touch" size=".5 .5 .1" pos="0 0 -.9" rgba="0 1 0 1"/>

    <body pos="0 0 0">
      <freejoint/>
      <geom type="box" size=".5 .5 .5" pos="0 0 0" rgba="1 0 0 1"/>
    </body>
  </worldbody>

  <sensor>
    <touch name="touch" site="touch"/>
  </sensor>
</mujoco>
【付録:検知領域が狭い場合の障害】

 上に述べたように、<touch>の面積は検査対象の面と比べてある程度大きくする必要があります。具体例として、接触面積に対する<site>の断面積を変化させたときの変化を下図に示します。<site>の断面積を大きくした場合と比べると、<site>の断面積が小さい方は謎の乱れが現れていることがわかります。このような障害を防ぐためにも、<site>の断面積は接触面積よりも大きくした方がよいです。

同じ条件下で<site>の面積を変化させた場合の変化.(左) 接触面積と<site>の面積が同じ(右)<site>の直径が接触面直径の1.5倍

 ちなみに上図左の青い領域は以下のようになっています。以下を見ると大体50Hzくらいで振動してることがわかります。なんでですかね?

上図左、青い領域の拡大図

詳説 : 力センサ・トルクセンサ(force, torque)

【力/トルクセンサの詳細】

 <force>,<torque>は子ボディと親ボディとの相互作用を計測する3軸力/トルクセンサです。取り付けた<site>のローカル座標に基づき、子ボディ-親ボディ間の相互作用力/トルクを出力します。子から親の方向を正とし、<site>を親ボディに取り付けた場合の符号は逆になります。

一つの親に対して複数の子が定義可能であるため、基本的にこのセンサは子ボディに接続した方が便利です。

 接触する<geom>condim属性により取得できる値が変化します。力センサで取得できるのは接触における垂直抗力と接線摩擦力(x2)であり、トルクセンサで取得できるのはねじり摩擦と転がり摩擦(x2)です。既定ではcondim="3"であり、ねじり摩擦と転がり摩擦は計算されません。このため、常に<torque>の測定値はゼロとなります。トルクセンサでの測定を行うためには、condimの値を最低でも4(ねじり摩擦を有効化)、もしくは6(全摩擦を有効化)とする必要があります。

重力等による力はととんど計測されず、アクチュエータにより加えられた力を計測するっぽい…?

詳説 : 距離センサ(rangefinder)

【距離センサの詳細】

 <rangefinder>は最も近い<geom>との距離を測定する距離センサです。測定方向は<site>のZ軸正方向であり、測定方向に<geom>存在しない場合は $-1$ を出力します。

 このセンサは<geom>を中空の物体として扱うため、<site>中心が<geom>内に入り込んだ場合はその<geom>の表面までの距離を出力します。ただし、センサの<site>同じボディの<geom>は測定対象から除外されます。また、不可視の(alpha=0の?)<geom>も感知対象となります。

 このセンサは可視化すると分かりやすいので、以下に距離センサを接続した振り子の例を示します。この例では、約 $1\space\mathrm{m}$ の回転する棒の先に距離センサが取り付けられており、センサは回転中心の反対を向いています。既定で距離センサのラインは可視化されるようで、以下の動画では黄緑色の線が測定線です。

緑の立方体がセンサを接続した<site>.センサの測定値が $-1$ でないときには黄緑の線が描画されている

swinging_rangefinder.xml
<mujoco>
  <worldbody>
    <camera name="fixed" pos="0 -5 -.5" xyaxes="1 0 0 0 0 1"/>
    <light pos="0 0 3"/>
    <geom type="plane" size="0 0 10" pos="0 0 -1"/>
    <body pos="0 0 0">
      <joint type="hinge" axis="0 1 0"/>
      <geom type="cylinder" size=".01" fromto="0 0 0 .707 0 .707" rgba="1 0 0 1"/>
      <site type="box" name="f" size=".1 .1 .1" pos=".707 0 .707" zaxis="1 0 1" rgba="0 1 0 1"/>
    </body>
  </worldbody>

  <sensor>
    <rangefinder name="f" site="f"/>
  </sensor>
</mujoco>

詳説 : 光学式モーションキャプチャのマーカー (camprojection)

【camprojectの詳細】

 <camprojection>はカメラ画像上の<site>の位置を取得できるセンサです。光学式モーションキャプチャのいちカメラ(camera)とマーカー(site)のようなものです。site属性でモーションキャプチャのマーカー位置を指定し、camera属性で使用するカメラを指定します。

 センサはカメラ上での<site>の位置 $(x_{res},y_{res})$ を出力します。原点 $(0,0)$ はカメラ画像左上であり、<site>がカメラ外の場合も値は測定されます。測定値は当該<camera>resolution属性に依存します。既定では"1 1"ですので、画面上の位置と測定値の関係は以下のようになります。また、カメラ画面の外に<site>がある場合も座標は出力されるため、例えば<site>が画面外左に位置する場合には $x_{res}<0$ となります。

 また、カメラの後方にある点も投影されるため、必要に応じてこれをフィルタリングするべきかもしれません。この場合、カメラを参照フレームとするframeposを使用するのが便利です。このとき、z座標の正の値がカメラの後方位置を意味します。

<camprojection>におけるカメラ上の位置と測定値の関係

 振り子を使用した<camproject>の例を以下に示します。カメラのresolutionは既定のまま("1 1")計測しました。

<camprojection>の出力.値は抜粋です

時刻 [s] 0 0.37 0.63 0.90
座標 (0.784, 0.014) (0.902, 0.293) (0.479, 0.701) (0.099, 0.267)

<site>は緑の立方体.

pendulum_tracking.xml
<mujoco>
  <worldbody>
    <camera name="fixed" pos="0 -3 -.5" xyaxes="1 0 0 0 0 1"/>
    <light pos="0 0 3"/>
    <geom type="plane" size="0 0 10" pos="0 0 -1"/>

    <body pos="0 0 0">
      <joint type="hinge" axis="0 1 0"/>
      <geom type="cylinder" size=".01" fromto="0 0 0 .707 0 .707" rgba="1 0 0 1"/>
      <site type="box" name="tip" size=".1 .1 .1" pos=".707 0 .707" zaxis="1 0 1" rgba="0 1 0 1"/>
    </body>
  </worldbody>

  <sensor>
    <camprojection name="f" site="tip" camera="fixed"/>
  </sensor>
</mujoco>

MJCFの要素 : tendon

概要 : tendon (MJCF)

 MuJoCoの特徴的な要素の一つが<tendon>であり、(基本的に当たり判定のない)腱や紐をシミュレートすることができます。<tendon><sensor><actuator>などと同じく子要素で具象を定義する(役割をグルーピングとする)MuJoCo要素です。子要素には<site>を最短パスで結び、ばね力などもシミュレートすることのできる<spatial>と、<joint>の角度/変位(の和)を仮想的な"腱の長さ"として扱える<fixed>の2種類が存在します。

<spatial>による腱の例[8].モデルはtendon.xmlを参照

 <tendon><sensor><actuator>などと同じくキネマティックツリーの外で定義を行う要素です。<tendon>自体は属性を持たず、その<spatial><fixed>を子要素としてまとめる役割を持ちます。

 以下に簡単な<tendon>を使用したモデルの例を示します。以下の腱<spatial>は固定された"end1"サイトと立方体"floating""end2"サイト)をつないでおり、下図のようにひもでつないだような挙動を実現することができます。またstiffness属性を設定することでゴムのような挙動も実装することができます(下図右)。

ばね力なし ばね力あり

<tendon>を使用したモデル(springlength="0 .1" stiffness="200"を設定)

box_and_tendon.xml
<mujoco>
  <worldbody>
    <site name="end1" size=".01"/>
    <body name="floating" pos="0 0 -.2">
      <freejoint/>
      <geom type="box" size=".1 .1 .1"/>
      <site name="end2" size=".01"/>
    </body>
  </worldbody>

  <tendon>
    <spatial limited="true" range="0 0.6" width="0.005">
      <site site="end1"/>
      <site site="end2"/>
    </spatial>
  </tendon>
</mujoco>

本項目では<spatial>のみ扱います。<fixed>の方についてはXML Reference/tendon/fixedを参照してください。<fixed>では<joint>の角度/変位の和を仮想的な"腱の長さ"として扱えます。このため、例えばプルバックカーを作ることなども可能です(car.xml; 車を後ろに引きタイヤを逆回転させると、その<joint>の回転分だけ弾性エネルギーが溜まる)。

属性 : spatial (MJCF/tendon)

 <spatial>は指定された2つの<site>を結ぶ(当たり判定を持たない)ひも状の要素です。障害物や経由点を指定した場合、腱はそれらを満たす最小のパスを通ります。本項目では<tendon>の子要素の一つである<spatial>の持つ属性について説明します。また本項目では取り扱いませんが、もう一つの<tendon>の子要素である<fixed>についてもその属性の大部分を共有しています。

 下表に<spatial>の代表的な属性を示します。すべての属性については、その下の「使用可能な全属性の一覧」を参照してください。

代表的な属性(<spatial>

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>のクラス名です。
range 型:real(2)〈 "0 0"
許容される腱の長さの下限と上限を指定します。
width 型:real〈 "0.003"
レンダリングに使用される<tendon>の断面半径です。ただし、障害物の周囲では幅を縮小してレンダリングされます。
rgba 型:real(4)〈 "0.5 0.5 0.5 1"
色と透明度を設定します。値が指定された場合、materialは無効になります。
springlength 型:real(2)〈 "-1 -1"
 ばねが静止する位置を指定します。stiffness等が指定されていない場合意味を持ちません。
 1つまたは2つの値を設定することが可能です。設定する場合、正の値とする必要があります。
・未設定 - 腱の静止時長さはmjModel.qpos0から決定されます。
・1つの値 - 静止時の腱長を指定します
・2つの値 - deadband範囲を指定します。腱長がこの範囲内にあるとき、作用する力は0になります
stiffness 型:real〈 "0"
剛性です。
正値が設定された場合、腱に沿ってばね力が作用するようになります。このとき、力は腱の伸びに対して線形です。
【使用可能な全属性の一覧】
属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>のクラス名です。
group 型:int〈 "0"
属するグループの番号です。カスタムタグや可視化の有効/無効の切り替えに使用可能です。
limited 型:選択式〈 "auto"
["false","true","auto"]から選択
range属性による長さの制限の適用可否を決定します。
"true" - rangeによる長さの制限を強制します
"false" - 長さの制限を無効にします
"auto" - range属性が指定されている場合にのみ長さの制限を設けます
range 型:real(2)〈 "0 0"
許容される腱の長さの下限と上限を指定します。
margin 型:real〈 "0"
rangeで指定した下限/上限と腱長との差の絶対値が、rangeを下回らないように制約ソルバが処理を行います。詳細はソルバーパラメタを参照してください。
frictionloss 型:real〈 "0"
正値に設定した場合、乾性摩擦による摩擦ロスが生じるようになります。
width 型:real〈 "0.003"
レンダリングに使用される<tendon>の断面半径です。ただし、障害物の周囲では幅を縮小してレンダリングされます。
material 型:string〈 optional
外観を設定するために使用する<material>を指定します。
rgba 型:real(4)〈 "0.5 0.5 0.5 1"
色と透明度を設定します。値が指定された場合、materialは無効になります。
springlength 型:real(2)〈 "-1 -1"
 ばねが静止する位置を指定します。stiffness等が指定されていない場合意味を持ちません。
 1つまたは2つの値を設定することが可能です。設定する場合、正の値とする必要があります。
・未設定 - 腱の静止時長さはmjModel.qpos0から決定されます。
・1つの値 - 静止時の腱長を指定します
・2つの値 - deadband範囲を指定します。腱長がこの範囲内にあるとき、作用する力は0になります
stiffness 型:real〈 "0"
剛性です。
正値が設定された場合、腱に沿ってばね力が作用するようになります。このとき、力は腱の伸びに対して線形です。
damping 型:real〈 "0"
減衰係数です。
正値が設定された場合、腱に沿って減衰力が作用されるようになります。このとき、力は腱の速度に線形です。
<joint>の減衰と異なり<tendon>の減衰は暗黙的に積分されないため、可能な限り<joint>の減衰を使用するべきです。
user 型:real(n)〈 "0 0 ..."
nuser_tendonと同じ要素数です。ユーザパラメタを参照してください。

また、制約ソルバに関連したパラメタであるsolreflimitsolimplimitsolreffrictionおよびsolimpfrictionもあるようです。詳細はソルバーパラメタを参照してください。

spatialの子要素(site, geom, pulley)

site (MJCF/tendon/spatial)

 <site>要素は<tendon>が通過/接続する点を指定するために使用します。<tendon>の両端はどちらも<site>を設定する必要があります<site>要素は以下の1属性のみを持ちます。

属性 型:〈デフォルト値等〉
説明
site 型:string〈 required
<tendon>が通過/接続する<site>を指定します。

geom(MJCF/tendon/spatial)

 <geom>要素は腱の障害物を設定するために使用します。障害物に対し、<tendon>のパスは回り込むように設定されます。ただし、最小長のパスが<geom>に接触しない場合には効果を持ちません。障害物としては円柱と球の2種類の<geom>を使用することが可能ですが、このうち円柱については無限の長さを持つものとして扱われます。

 複数の障害物を使用した場合、腱を<site>で分割する必要があります(そうしないと腱のレベルで反復ソルバが必要となるため)。以下の具体例における"s5"がこれに相当します。

 <geom>要素は以下の2属性を持ちます。

属性 型:〈デフォルト値等〉
説明
geom 型:string〈 required
障害物として設定する<geom>の名前です。
"sphere""cylinder"<geom>のみを参照可能です。
sidesite 型:string〈 optional
 指定した場合、実行時に指定された<site>に近いパスが自動的に選択されます。
 動作時に腱のパスが「反対側へ移動する」のを防ぐために使用されます。<site><geom>の内側にある場合、腱のパスは<geom>の内部を通過するように設定されます。詳細は後述します。
 <geom>の実装を行う際、この属性の指定が必要になることがよくあります。

pulley(MJCF/tendon/spatial)

 この要素は腱のパスに分岐を作成します。腱に複数の分岐が存在する場合、各分岐の長さが分岐を開始した滑車要素のdivisor属性で除算され、合計値を腱長として扱います。<pulley>要素は以下の1属性のみを持ちます。

属性 型:〈デフォルト値等〉
説明
divisor 型:real〈 required
<pulley>を始点とする各分岐の長さはこの値で除算されます。分岐先でまた分岐が生じる場合、その除数は各分岐のdivisorの値を掛けたものとなります。

1つのパスから2つの平行な分岐を生成する<pulley>の場合、根本のパスの除数値は $1$ になり、<pulley>に続く2つの分岐先の除数値は $2$ になります。そのうちの1つが別の<pulley>によってさらに分割される場合、それぞれの新しい分岐先はの除数は $4$ になります(すべてのdivisorを $2$ に設定していた場合)。

1つの腱に分岐が存在しない場合、その腱全体の除数は $1$ となります。

使用例(site、geom、pulley)

 tendon.xmlをもとに、<site><geom>および<pulley>の使用方法を見てみましょう。

 下図の例では、"s1"を始点として以下のようなパスが形成されていることがわかります。このように、<spatial>の子要素は<pulley>を区切りとしたブロックを構成し、各ブロックは上下端の<site>が末端として選択されます。分岐を作成する場合、<pulley>以前に分岐点("s3")を定義し、<pulley>の真下でこれを始端としてそれぞれ宣言することになります。

  • 本流 : (始端)"s1""s2""s3"(末端; 除数 $1$ )
    • 分岐1 : (始端)"s3""s4"(末端; 除数 $2$ )
    • 分岐2 : (始端)"s3""s5""s6"(末端; 除数 $2$ )

 また、障害物節でも述べたように、複数の障害物を使用した場合には腱を<site>で分割する必要があります。すなわち、<site>の間に2つ以上の<geom>が挿入されないように<site>を挟む必要があります(今回の例では"s5"がこれに相当; "g2""g3"が隣り合わないようにしている)。

<spatial>による腱の例[8].このうち<tendon>部分を以下に示す

<mujoco>
  ...
  <tendon>
    <spatial range="0 .33" limited="true" width=".002" rgba=".95 .3 .3 1">
      <!-- 本流 -->
      <site site="s1"/>
      <site site="s2"/>
      <geom geom="g1"/>
      <site site="s3"/>

      <!-- 分岐1 -->
      <pulley divisor="2"/>
      <site site="s3"/>
      <geom geom="g2" sidesite="side2"/>
      <site site="s4"/>

      <!-- 分岐2 -->
      <pulley divisor="2"/>
      <site site="s3"/>
      <geom geom="g2" sidesite="side2"/>
      <site site="s5"/>
      <geom geom="g3" sidesite="side3"/>
      <site site="s6"/>
    </spatial>
  </tendon>
</mujoco>

MJCFの要素 : actuator

内容が多岐にわたるため、詳細は別記事にて紹介します。

概要 : actuator (MJCF)

 アクチュエータもMuJoCoの特徴的な要素のうちの一つです。<sensor><tendon>などと同様に、アクチュエータもグルーピング要素<actuator>とその下の具象から構成されます。各アクチュエータはすべて単一入力単一出力(SISO)です。PD制御などで2つ以上の制御入力をとるアクチュエータを必要とする場合、2つ以上のアクチュエータを同じ対象に接続する必要があります。

アクチュエータの種類

 MuJoCoには非常に柔軟ですべての種類のアクチュエータを作成可能な<general>と、これを扱いやすくしたアクチュエータショートカット<motor>等9種)が存在します。前者はあまりにも設定項目が多く扱いにくので、基本的には後者を使用することになると思います。

【MuJoCoで使用可能なアクチュエータ要素】
要素 説明
general 接続対象: <joint>,<tendon>,<site>,<body>
すべての種類のアクチュエータを作成可能なアクチュエータ要素です。
これを除いた他のアクチュエータ要素はすべてショートカットであり、dynprmbiastypedynprmbiasprmの6種パラメタ(等)を自動で調整したものとなります。
motor 接続対象: <joint>,(+α?)
DD(ダイレクトドライブ)モータです。
(DDモータ : 減速機構を使用せず駆動するモータです。一般的なサーボに比べ低速・高トルクです。ただしMuJoCoではこれが最も「一般的な」モータです。)
position 接続対象: <joint>,<site>,(+α?)
ポジションサーボです。
物体の把持など、位置決めが重要なケースに使用します。ジョイントを所望の位置へ駆動させるように、現在位置と目標位置の差に基づき、トルクを連続的に調整します。また、slider-crankトランスミッション(簡易なスライダークランク機構)にも使用されます。
velocity 接続対象: <joint>,(+α?)
速度サーボです。
ホイールの回転制御など、関節速度の維持が重要なケースに使用します。所望の関節速度を達成するように、現在のジョイント速度と目標速度の差に合わせてトルクを連続的に調整します。
intvelocity 接続対象: <joint>,(+α?)
積分速度サーボです。
<velocity>は対象の速度に直接フィードバックを返すアクチュエータですが、<intvelocity>は積分器と位置フィードバックを統合したアクチュエータです。
制御性が失われることを防ぐため、actraneを設定することが望ましい(ことが多い)です。
damper 接続対象: <joint>,(+α?)
アクティブダンパーです。
出力 $F_i$ は速度 $v_i$ と制御入力 $u_i>0$ に負比例( $F_i=-k_v\times v_i\times u_i$ )します。 $k_v>0$ は速度フィードバックゲインです。
💡使用する場合、optionのintegrator属性をimplicitまたはimplicitfastにすることが推奨されます。
cylinder 接続対象: <joint>
空気圧シリンダまたは油圧シリンダのモデリングに使用されます。
muscle 接続対象: <tendon>,(<joint>)
筋肉です。
adhesion 接続対象: <body>
吸着力を発生させるアクチュエータです。ヤモリの足や吸盤などをシミュレートできます。
吸着力は接触面積に比例しません。
plugin 接続対象: 様々
engine pluginに関連したアクチュエータ要素です。ユーザ定義のアクチュエータを使用することができます。詳細はリンクを参照してください。

実はMuJoCoはアクチュエータの型として<general>以外を持たないため、ほかの要素は単なるショートカットにすぎません。各ショートカットには独自の属性が存在しますが、内部的には<general>に変換された時点で共通属性へと変換されます。例えば<position>には固有のkp属性がありますが、これはgainprmbiasprmを調整するのに使用されます。このため、<general>の各属性を適切に設定することですべてのショートカットアクチュエータを作成することも可能です。

アクチュエータの制御

 アクチュエータへの制御入力はmjData.ctrlを通して行います。mjData.ctrlには各アクチュエータへの制御入力がMJCF上で定義された順に格納されています。プログラム上ではこれらの値を書き換えることでアクチュエータを制御することになります。デフォルトではすべての制御入力は0です。

 例として1つの<motor>を持つモデルを以下に示します。その下のPythonコードでは、1秒以降にこの"hmotor"に対して制御入力 $-100$ を加えています。

モータの接続された回転体.1秒以降は制御入力 $u_i=-100$

"hinge"ジョイントの軸ベクトルが $(0,1,0)$ なので、反時計回り方向への力を与えたいときの制御入力は負となります。

m_pendulum.xml
<mujoco>
  <asset>
    <texture type="skybox" builtin="gradient" rgb1="1 1 1" rgb2=".6 .8 1" width="256" height="256"/>
  </asset>

  <worldbody>
    <camera name="fixed" pos="0 -5 0" xyaxes="1 0 0 0 0 1"/>
    <body>
      <joint name="hinge" type="hinge" axis="0 1 0"/>
      <geom type="capsule" size=".03" fromto="0 0 0 1 0 1"/>
    </body>
  </worldbody>

  <actuator>
    <motor name="hmotor" joint="hinge" gear="1"/>
  </actuator>
</mujoco>
model = mujoco.MjModel.from_xml_path("m_pendulum.xml")
renderer = mujoco.Renderer(model, 640, 480)
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)

# 初期化
n_frames, fps = 180, 60
frames = []
mujoco.mj_resetData(model, data)

# レンダリング
for i in range(n_frames):
    while data.time < i/fps:
        mujoco.mj_step(model, data)

    # 1秒経過時点で"hmotor"に-100の制御入力を与える
    if i == fps:
        data.ctrl[0] = -100

    renderer.update_scene(data, "fixed")
    frames.append(renderer.render())

save_video(frames, "output.mp4", fps)

属性 : actuator (MJCF)

 <sensor>要素と同様に<actuator>はグルーピング要素であり、具象は<motor><position>などの子要素で定義します。以下には、全種類のアクチュエータ要素で指定可能な属性を示します。ただし、下表には代表的なもののみを示しています。すべての属性については、その下の「全アクチュエータ共通の属性の一覧」を参照してください。

以下に示す属性はすべてのアクチュエータショートカットに共通する属性であると同時に、<general>アクチュエータで指定可能な属性です。上述のようにMuJoCoは内部的に<general>以外のアクチュエータタイプを持ちません。このため、ショートカットのみで指定可能な属性(kpkvなど)はすべて以下の属性に置換されます。

代表的な全アクチュエータ共通の属性(<general>の属性)

属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>の名前です。
ctrllimited 型:選択式〈 "auto"
["false","true","auto"]から選択
アクチュエータへの制御入力をクランプするか指定します。
"true" - 制御入力は実行時自動的にctrlrangeにクランプされます。
"false" - 制御入力はクランプされません。
"auto" - <compiler>autolimitsが有効かつctrlrangeが定義されている場合にクランプされます。
制御入力のクランプはoption/flagのclampctrl属性からグローバルに無効化することも可能です。
forcelimited 型:選択式〈 "auto"
["false","true","auto"]から選択
アクチュエータの力出力をクランプするか指定します。
"true" - 制御入力は実行時自動的にforcerangeにクランプされます。
"false" - 制御入力はクランプされません。
"auto" - <compiler>autolimitsが有効かつforcerangeが定義されている場合にクランプされます。
ctrlrange 型:real(2)〈 "0 0"
制御入力をクランプする範囲を指定します。
1番目の値が最小値、2番目の値が最大値です。
forcerange 型:real(2)〈 "0 0"
力出力をクランプする範囲を指定します。
1番目の値が最小値、2番目の値が最大値です。
gear 型:real(6)〈 "1 0 0 0 0 0"
アクチュエータの長さをスケールします。
出力トルク/力の倍率を指定します。一部のトランスミッションタイプの場合は、出力が働く軸の指定も兼ねます。詳細は後述します。
joint
jointparent
site
refsite
body
tendon
cranksite
slidersite
型:string〈 optional
アクチュエータを接続するオブジェクトを指定します。選択された属性によりトランスミッションのタイプが決定されます。この8属性のうち1つ(種類によっては2つを設定する必要があります。詳細は後述します。
joint - jointトランスミッションを構築します。大体のアクチュエータで指定可能です。
jointparent - jointとほとんど同じです。
site(&refsite) - siteトランスミッションを構築します。<position>などで指定可能です。
body - bodyトランスミッションを構築します。<adhesion>などで指定可能です。
tendon - tendonトランスミッションを構築します。<muscle>などで指定可能です。
cranksite&slidersite - slider-crankトランスミッションを構築します。両方の指定が必要であり、<position>などで指定可能です。
【全アクチュエータ共通の属性の一覧(generalの属性)】
属性 型:〈デフォルト値等〉
説明
name 型:string〈 optional
名前です。
class 型:string〈 optional
適用したい<default>の名前です。
group 型:int〈 "0"
属するグループを指定します。カスタムタグおよび可視化時に使用できます。
ctrllimited 型:選択式〈 "auto"
["false","true","auto"]から選択
アクチュエータへの制御入力をクランプするか指定します。
"true" - 制御入力は実行時自動的にctrlrangeにクランプされます。
"false" - 制御入力はクランプされません。
"auto" - <compiler>autolimitsが有効かつctrlrangeが定義されている場合にクランプされます。
制御入力のクランプはoption/flagのclampctrl属性からグローバルに無効化することも可能です。
forcelimited 型:選択式〈 "auto"
["false","true","auto"]から選択
アクチュエータの力出力をクランプするか指定します。
"true" - 制御入力は実行時自動的にforcerangeにクランプされます。
"false" - 制御入力はクランプされません。
"auto" - <compiler>autolimitsが有効かつforcerangeが定義されている場合にクランプされます。
actlimited 型:選択式〈 "auto"
["false","true","auto"]から選択
アクチュエータに関連する内部状態(activation; 活性化状態)をクランプするか指定します。
"true" - 制御入力は実行時自動的にactrangeにクランプされます。
"false" - 制御入力はクランプされません。
"auto" - <compiler>autolimitsが有効かつactrangeが定義されている場合にクランプされます。
ctrlrange 型:real(2)〈 "0 0"
制御入力をクランプする範囲を指定します。
1番目の値が最小値、2番目の値が最大値です。
forcerange 型:real(2)〈 "0 0"
力出力をクランプする範囲を指定します。
1番目の値が最小値、2番目の値が最大値です。
actrange 型:real(2)〈 "0 0"
内部状態(activiation; 活性化状態)をクランプする範囲を指定します。
1番目の値が最小値、2番目の値が最大値です。詳細は活性化クランピングを参照してください。
lengthrange 型:real(2)〈 "0 0"
アクチュエータのトランスミッションの実現可能な範囲を指定します。後述します。
gear 型:real(6)〈 "1 0 0 0 0 0"
アクチュエータの長さをスケールします。
出力トルク/力の倍率を指定します。一部のトランスミッションタイプの場合は、出力が働く軸の指定も兼ねます。詳細は後述します。
cranklength 型:real〈 "0"
コネクティングロッド(コンロッド)の長さを指定します。
slider-crankトランスミッションの場合にのみ使用し、値は正である必要があります。
joint
jointparent
site
refsite
body
tendon
cranksite
slidersite
型:string〈 optional
アクチュエータを接続するオブジェクトを指定します。選択された属性によりトランスミッションのタイプが決定されます。この8属性のうち1つ(種類によっては2つを設定する必要があります。詳細は後述します。
joint - jointトランスミッションを構築します。大体のアクチュエータで指定可能です。
jointparent - jointとほとんど同じです。
site(&refsite) - siteトランスミッションを構築します。<position>などで指定可能です。
body - bodyトランスミッションを構築します。<adhesion>などで指定可能です。
tendon - tendonトランスミッションを構築します。<muscle>などで指定可能です。
cranksite&slidersite - slider-crankトランスミッションを構築します。両方の指定が必要であり、<position>などで指定可能です。
user 型:real(n)〈 "0 0 ..."
nuser_actuatorと同じ要素数
詳細はユーザーパラメタを参照してください。
actdim 型:real〈 "-1"
活性化状態の次元です。
"-1" - dyntypeに従い次元を設定します
1より大きい値 - ユーザ定義の活性化ダイナミクスのみで設定可能です。
dyntype 型:選択式〈 "none"
["none","integrator","filter","filterexact","muscle","user"]から選択
アクチュエータの活性化ダイナミクスを指定します。"none"の場合、アクチュエータは活性化状態を持ちません。
詳細は後述します。
gaintype 型:選択式〈 "fixed"
["fixed","affine","muscle","user"]から選択
 力生成メカニズムの出力(のゲイン(係数))を決定します。MuJoCoのアクチュエータモデルでは次のように出力トルク/力が決定されます。ここで $w_i,u_i$ はそれぞれ活性化状態と制御入力です。また、 $Gain, Bias$ はそれぞれゲイン(係数)とバイアス項ですが、実際の値はgaintype/biastypeにより変化します。詳細は後述します。
   $F_i = Gain\times (w_i \space or\space u_i) + Bias$
biastype 型:選択式〈 "none"
["none","affine","muscle","user"]から選択
力生成メカニズムの出力(のバイアス項)を決定します。詳細は後述します。
dynprm 型:real(10)〈 "1 0 ... 0"
活性化ダイナミクスのパラメタです。組み込みのアクチュエータショートカットの場合、筋肉を除き1番目の値のみ使用します。"muscle"の場合は3つ目までの値が使われます。
gainprm 型:real(10)〈 "1 0 ... 0"
ゲインのパラメタです。組み込みのアクチュエータショートカットの場合、筋肉を除き1番目の値のみ使用します。"muscle"の場合は9つめまでの値が使われます。
biasprm 型:real(10)〈 "1 0 ... 0"
バイアス項のパラメタです。バイアス型が"affine"の場合は3つめまでの値を使用します。"none"の場合値は使用されず( $Bias=0$ )、"muscle"の場合は9つ目まで使用されます。
actearly 型:選択式〈 "false"
["false","true"]から選択
"true"を指定すると、力の計算は現在の値ではなく次の活性化変数の値を使用するようになります。このフラグを設定すると、制御と加速の間の遅延が1タイムステップ短くなります。

各アクチュエータショートカットにおいてactdimbiasprmの7属性は自動的に設定されます。したがって、通常はこれ以外の属性ショートカット固有の属性のみを指定することになります。

MJCFの要素 : default

概要 : default (MJCF)

 MJCFにおいて、<default>要素は変更を全体に伝播させる機能を持ちます。特定の要素の初期値(例えば色)を変更したい場合に使用することができます。

 下記では<default>を使用して、<geom>の色の初期値を設定しています(不完全なコードですが)。

下記MJCFコードによる各<geom>の色

type属性 rgba属性
"box" "1 0 0 1"
"ellipsoid" "0 1 0 1"
"sphere" "0 0 1 1"
"cylinder" "1 0 0 1"
multiple_defaults.xml
<mujoco>
    <default class="main">
        <geom rgba="1 0 0 1"/>
        <default class="sub">
            <geom rgba="0 1 0 1"/>
        </default>
    </default>

    <worldbody>
        <geom type="box"/>
        <body childclass="sub">
            <geom type="ellipsoid"/>
            <geom type="sphere" rgba="0 0 1 1"/>
            <geom type="cylinder" class="main"/>
        </body>
    </worldbody>
</mujoco>

 上記のように、<default>は入れ子構造に定義することが可能です。このような<default>はツリー構造をなしますが、各<default>一意の名前をつける必要があります(上記の"main","sub"など; 最上位の<default>を除く)。デフォルト値は大体以下のルールにより適用されているようです。

  • 属性の値が個別に定義済みのものはそれを適用("sphere"
    • <default>名を個別に指定されているものも含む("cylinder"
  • 最上位(class="main")の<default>の値は全体に適用("box"
  • 親要素が<default>名を指定した場合、子要素はそれを適用(<body>下、"ellipsoid")

最上位の<default>の値(<geom>rgba="1 0 0 1")は全体に適用されるため、最上位で定義した場合にその属性を未定義にすることはできません。ボディの慣性など(?)一部の属性は未定義としたいこともありますので、そういった属性についてはアクティブな<default>では未定義にする必要があります(下位分類で既定すべき(?))。

属性 : default (MJCF)

 <default>要素自体には設定可能な属性は下記の一つしかありません。

属性 型:〈デフォルト値等〉
説明
class 型:string〈 required
名前です。最上位を除き必須です。
すべてのデフォルトについて一意な名前を付ける必要があります。
各要素でデフォルト値を有効にする際に使用されます。

次に<default>の中に定義される要素について、デフォルト値を設定することのできる属性を下に示します。下表からわかるように、要素ごとにデフォルト値を設定可能な属性は異なります。例えば<geom><joint><light>などはname属性とclass属性を除きすべてのデフォルト値を設定可能ですが、<mesh>scale属性以外のデフォルト値は設定不可能です。

【デフォルト値を設定可能な属性】

alphabetical order

要素 設定可能な属性
adhesion nameclassbody以外すべて
camera nameclassmodetarget以外すべて
cylinder nameclassjointjointinparentsiterefsitetendonslidersitecranksite以外すべて
dumper nameclassjointjointinparentsiterefsitetendonslidersitecranksite以外すべて
equality activesolrefsolimpのみ
サブ要素にかかわらず、すべての等式制約に共通の属性を設定します。
general nameclassjointjointinparentsiterefsitetendonslidersitecranksite以外すべて
geom nameclass以外すべて
intvelocity ※ nameclassjointjointinparentsiterefsitetendonslidersitecranksite以外すべて
joint nameclass以外すべて
light nameclass以外すべて
material nameclass以外すべて
mesh scaleのみ
motor ※ nameclassjointjointinparentsiterefsitetendonslidersitecranksite以外すべて
muscle nameclassjointjointinparentsiterefsitetendonslidersitecranksite以外すべて
pair nameclassgeom1geom2以外すべて
position ※ nameclassjointjointinparentsiterefsitetendonslidersitecranksite以外すべて
site nameclass以外すべて
tendon nameclass以外すべて
すべてのタイプに共通する、全ての腱のサブ要素の属性を設定可能です。
velocity ※ nameclassjointjointinparentsiterefsitetendonslidersitecranksite以外すべて

※ : アクチュエータショートカットを使用して一般要素の属性を設定します。これらはすべて同じ基本属性を設定し、以前の設定を書き換えます。このため、同じ<default>内でこれらを複数を使用する意味はありません

MJCFの要素 : option

概要 : option (MJCF)

 <option>要素はシミュレーション時のオプションを設定するために使用されます。MjModel.optは構造体mjOptionですが、<option>の要素はこれと一対一で対応しています。実行時(プログラミングコード)にMjOptionを使用して設定を変更することも可能ですが、もう一つの選択肢としてこちらを使用することも可能です。

 オプションのうち、"enable""disable"の2値で表されるものとしてFlagオプションが存在します。この要素はシミュレーションパイプラインの特定の部分を有効化/無効化します。以下に、flag型のオプションと通常のオプションの2パターンについて、その具体例を示します。

<mujoco>
  <option timestep=".001">
    <flag energy="enable" contact="disable"/>
  </option>
</mujoco>

使用可能なオプションの一覧

 以下に代表的なオプションを示します。すべてのオプションについては、その下の「使用可能な全オプションの一覧」を参照してください。

主要なオプション

属性 型:〈デフォルト値等〉
説明
timestep 型:real〈 "0.002"
シミュレーションのタイムステップ幅 $\mathrm{[s]}$ です。小さいほど精度と安定性が向上しますが、1ステップのシミュレーションに必要なCPU時間よりも大きい必要があります(積分器が"RK4"の場合はCPU時間の4倍; リアルタイム性能のため)。
gravity 型:real(3)〈 "0 0 -9.81"
重力加速度のベクトルを設定します。既定のワールド方向ではZ軸は上向きです。他の機能にも影響するため、基本的に変更することは推奨されません
wind 型:real(3)〈 "0 0 0"
風の速度ベクトルを設定します。この値はボディに作用する粘性力、揚力、抗力を計算するために使用されます。
density 型:real〈 "0"
媒体(medium; 空気等)の密度を設定します(水の場合約 $1.2$ です)。ボディの(幾何学的)密度とは異なります。揚力や抗力を計算するために使用されます。
integrator 型:選択式〈 "Euler"
["Euler","RK4","implicit","implicitfast"]から選択
使用する数値積分器を選択します。前から順に「半陰的オイラー法(semi-implicit Euler method)」、「固定ステップ4次ルンゲクッタ法(fixed-step 4-th order Runge Kutta method)」、「陰解法速度オイラー法(Implicit-in-velocity Euler method)」、「コリオリ項と遠心項を無視する陰解法速度オイラー法」です。
【使用可能な全オプションの一覧】
属性 型:〈デフォルト値等〉
説明
timestep 型:real〈 "0.002"
シミュレーションのタイムステップ幅 $\mathrm{[s]}$ です。小さいほど精度と安定性が向上しますが、1ステップのシミュレーションに必要なCPU時間よりも大きい必要があります(積分器が"RK4"の場合はCPU時間の4倍; リアルタイム性能のため)。
apirate 型:real〈 "100"
API rate $\mathrm{[Hz]}$. 外部APIが更新関数の実行を許可するレートです。
impratio 型:real〈 "1"
摩擦力を実際の値よりも大きくするかを決定する値です。1より大きい場合、摩擦力は本来の値よりも大きくなります。
(楕円摩擦円錐の摩擦と、通常の高速インピーダンスの比率を決定します)
gravity 型:real(3)〈 "0 0 -9.81"
重力加速度のベクトルを設定します。既定のワールド方向ではZ軸は上向きです。他の機能にも影響するため、基本的に変更することは推奨されません
wind 型:real(3)〈 "0 0 0"
風の速度ベクトルを設定します。この値はボディに作用する粘性力、揚力、抗力を計算するために使用されます。
magnetic 型:real(3)〈 "0 -0.5 0"
地磁気のベクトルを設定します。磁力計センサにより計測が可能です。
density 型:real〈 "0"
媒体(medium; 空気等)の密度を設定します(水の場合約 $1.2$ です)。ボディの(幾何学的)密度とは異なります。揚力や抗力を計算するために使用されます。
viscosity 型:real〈 "0"
媒体の粘度を設定します(空気は約 $2e-5$ 水は約 $9e-4$ です)。デフォルトの積分器"Euler"では、減衰を表現する際にジョイントの減衰を設定する方が安定します。
o_margin 型:real〈 "0"
接触オーバーライドが有効な場合、接触ペアのmarginパラメタを置き換えます。
integrator 型:選択式〈 "Euler"
["Euler","RK4","implicit","implicitfast"]から選択
使用する数値積分器を選択します。前から順に「半陰的オイラー法(semi-implicit Euler method)」、「固定ステップ4次ルンゲクッタ法(fixed-step 4-th order Runge Kutta method)」、「陰解法速度オイラー法(Implicit-in-velocity Euler method)」、「コリオリ項と遠心項を無視する陰解法速度オイラー法」です。後ろの選択肢ほど計算速度は速いですが、一部の正確性は失われます。
cone 型:選択式〈 "pyramidal"
["pyramidal", "elliptic"]から選択
接触摩擦円錐の型を選択します。"elliptic"の方がより現実的ですが、"pyramidal"の方が高速で安定的です。
jacobian 型:選択式〈 "auto"
["dense", "sparse", "auto"]から選択
制約ヤコビアンとそこから計算される行列のタイプを選択します。"auto"の場合、自由度が60未満の場合は"dense"として、60以上の場合には"sparse"として計算されます。
solver 型:選択式〈 "Newton"
["PGS","CG","Newton"]から選択
制約ソルバのアルゴリズムを選択します。
iterations 型:int〈 "100"
制約ソルバの最大反復回数を決定します。大規模な系で多くの相互作用が発生する場合、反復回数はより多くする必要があります。
tolerance 型:real〈 "1e-8"
反復ソルバの早期終了に使用される許容閾値を設定します。"0"に設定すると早期終了が無効になります。詳細については制約ソルバを参照してください。
ls_iterations 型:int〈 "50"
"CG"/"Newton"制約ソルバーが実行するラインサーチの最大反復回数です。
ls_tolerance 型:real〈 "0.01"
ラインサーチアルゴリズムの早期終了に使用される許容閾値です。
noslip_iterations 型:int〈 "0"
Noslipソルバーの最大反復回数です。"0"に設定された場合、後処理ステップが無効化されます。
noslip_tolerance 型:real〈 "1e-6"
Noslipソルバーの早期終了に使用される許容閾値です。
mpr_iterations 型:int〈 "50"
凸メッシュの衝突に使用されるMPRアルゴリズムの最大反復回数です。geomのアスペクト比が非常に大きい場合を除き、ほとんど調整する必要はありません。
mpr_tolerance 型:real〈 "1e-6"
MPRアルゴリズムの早期終了に使用される許容閾値です。
sdf_iterations 型:int〈 "10"
符号付き距離フィールド(signed distance field)衝突に使用される、初期…あたりの反復回数です。
sdf_initpoints 型:int〈 "40"
符号付き距離フィールドの衝突で衝突点を見つけるために使用される開始点の数です。
actuatorgroupdisable 型:int(31) optional
無効にするアクチュエータグループのリストです。この動画に詳しいです。

他に o_solref, o_solimp, o_frictionも存在します。ソルバーパラメタのようですが、デフォルト値は設定されていません。

 flag型のオプションについて、以下に代表的なものを示します。すべてのオプションについては、その下の「使用可能な全オプションの一覧 (flag)」を参照してください。以下の表においては、値をデフォルトから変更した際に何が起こるかの説明を記述しています。

主要なオプション (flag)

オプション デフォルト値 デフォルト値から変化させた場合
gravity "enabled" 重力(mjOption.gravity)を"0 0 0"に設定した場合と同様になります。
energy "disabled" 運動エネルギーと位置エネルギーの計算を有効化します。エネルギーはmjData.energyに保存されます。mjData.energy[2]のデータは位置エネ、運動エネの順で格納されます。
fwdinv "disabled" 順動力学と逆動力学の自動比較を有効化します。mj_forwardまたはmj_stepの後、自動的に逆動力学が計算され、適用された力の差がmjData.solver_fwdinv[2]に格納されます。最初の値が関節空間での不一致の相対ノルムで、次の値は拘束空間での値です。
sensornoise "disabled" センサーノイズのシミュレーションを有効にします。ノイズは平均0のガウスノイズであり、標準偏差は各センサのノイズパラメタにより決定されます。
【使用可能な全オプションの一覧 (flag)】
オプション default デフォルト値から変化させた場合
constraint "enabled" 制約ソルバに関連するすべての標準計算(以下の4つ)が無効になります。このため、拘束力は発生しません。
equality "enabled" 等式制約に関するすべての標準計算が無効になります。
frictionloss "enabled" 摩擦損失の制約に関するすべての標準計算が無効になります。
limit "enabled" ジョイントと腱の制限制約に関するすべての標準計算が無効になります。
contact "enabled" 衝突検出と接触拘束に関連するすべての標準計算が無効になります。
passive "enabled" 受動的な力が無効になります。
(ジョイントと腱のスプリングダンパー、流体力学的な力、およびmjcb_passiveコールバックによるカスタム受動力)
gravity "enabled" 重力(mjOption.gravity)を"0 0 0"に設定した場合と同様になります。
clampctrl "enabled" アクチュエータへの制御入力のクランピングを無効にします(有効にするよう設定しているものを含め)。
warmstart "enabled" 制約ソルバのウォームスタートを無効にします。
filterparent "enabled" 2つのgeomが親と子であるコンタクトペアのフィルタリングを無効にします。
actuation "enabled" アクチュエータに関するすべての力を無視します。
refsafe "enabled" シミュレーションタイムステップに対してsolref[0]が小さすぎることによる不安定性を防ぐ安全メカニズムを有効にします。
solref[0]は拘束の安定化に使用される仮想的なスプリングダンパの剛性)
sensor "enabled" センサに関するすべての計算を無効化します。シミュレーション開始前に無効にした場合はセンサの値がすべてゼロを、実行時に無効にした場合は最後に計算された値を保持します。
midphase "enabled" 衝突が許可されているすべてのgeomのペアが衝突チェックされるようになります(計算速度の低下)。
eulerdamp "enabled" オイラー積分器のジョイント減衰に関する暗黙積分を無効にします。詳細はMuJoCo 数値積分を参照してください。
override "disabled" Contact overrideを有効化します。
energy "disabled" 運動エネルギーと位置エネルギーの計算を有効化します。エネルギーはmjData.energyに保存されます。mjData.energy[2]のデータは位置エネ、運動エネの順で格納されます。
fwdinv "disabled" 順動力学と逆動力学の自動比較を有効化します。mj_forwardまたはmj_stepの後、自動的に逆動力学が計算され、適用された力の差がmjData.solver_fwdinv[2]に格納されます。最初の値が関節空間での不一致の相対ノルムで、次の値は拘束空間での値です。
invdiscrete "disabled" "RK4"以外のすべての積分器に対して、mj_inverseによる離散時間逆動力学を有効にします。
sensornoise "disabled" センサーノイズのシミュレーションを有効にします。ノイズは平均0のガウスノイズであり、標準偏差は各センサのノイズパラメタにより決定されます。
multiccd "disabled" (実験的な機能です)
libccdベースの多重接触衝突検出を有効にします。凸凹な物体の衝突の安定化につながる可能性があります。
island "disabled" 制約の島(constraint islands)の検出を有効化します。物理には影響せず、可視化にのみ使用することが可能です。

option詳説 : timestep

 timestepオプションはシミュレーション時のタイムステップ幅を決定します。タイムステップ幅はシミュレーション全体に影響を与える重要なパラメタです。値が小さいほど精度と安定性が向上する一方で、シミュレーションにかかる時間や発散の可能性が増大するなどの欠点も存在します。特に、多くの浮動オブジェクトが存在する場合は接触の量も増大するため、その計算負荷も増大します。また、安定性はソルバのパラメタにも依存するため、モデルによっては両方の設定の組み合わせを調整する必要があります。

 また、リアルタイム性能を達成するためには、時間ステップを1ステップあたりのCPU時間より大きくする必要があります(RK4積分器を使用する場合は4倍)。シミュレーションによってCPU時間は変化するため、時間ステップを変更する際には前もって検証を行う必要があります。

 実際に検証した結果については、タイムステップ幅のシミュレーション結果への影響節にも記述しています。

最適化関連のシミュレーションでは、タイムステップ幅を可能な限り大きくすることが求められます。許容範囲は要求によっても変化するので、値を決めるのは結構難しいと思いますが…

MJCFの要素 : keyframe

概要 : keyframe (MJCF)

 Keyframeは、シミュレーション状態変数のスナップショットであり、システムを特定の状態へリセットする際に役立ちます。関節位置のベクトル、ジョイントの速度、アクチュエータの起動、シミュレーション時間の指定が可能です。ただし、(標準では)軌跡データを保存することはできません。そもそもKeyframeは軌跡データを保存するためのものではないため、そのように使用しない方がよいと思います。

 未定義のキーフレーム属性はqpos属性を除きすべてのデータが0に設定されます。また、プログラムの実行時にmjModel.key_timeなどのメンバを通してKeyframeのデータを設定することも可能です。

【mjModelのキーフレーム関連メンバ】

 プログラム実行時に以下のmjModelメンバからキーフレームの値を取得・変更することが可能です。name_keyadrを除いたすべてのメンバは、Pythonで実行した場合いずれもfloat64np.ndarrayとして使用されます。したがって、例えばキーフレームを3つ定義した場合にkey_qposのサイズは(3,7)となります。

メンバ名 説明
name_keyadr int(n)
(nkey × 1)
各keyframeの名前のポインタです。名前本体はmjModel.names\x00区切りのバイナリとして格納されています。下に取得方法の例を示します。
key_time mjtNum(n)
(nkey × 1)
各keyframeのtimeです。
key_qpos mjtNum(n)
(nkey × nq)
各keyframeのqposです。
key_qvel mjtNum(n)
(nkey × nv)
各keyframeのqvelです。
key_act mjtNum(n)
(nkey × na)
各keyframeのactです。
key_ctrl mjtNum(n)
(nkey × nu)
各keyframeのctrlです。
key_mpos mjtNum(n)
(nkey × 3*nmocap)
各keyframeのmposです。
key_mquat mjtNum(n)
(nkey × 4*nmopcap)
各keyframeのmquatです。

mjtNum : doubleまたはfloattypedef定義です。基本的に倍精度(double)を前提にすればよいと思います。したがって、Pythonで使用する場合には基本的にfloat64として扱われます。

model = mujoco.MjModel.from_xml_path("input.xml")

# modelから、0番目のkeyの名前を取得する
name = mujoco.id2name(model,
                      mujoco.mjtObj.mjOBJ_KEY,
                      0)
【keyframeの使用例】

 キーフレームの使用例を以下に示します。キーフレームはプログラム実行の際に、関数mujoco.mj_resetDataKeyframeを用いて適用します。

 以下MJCFモデルの場合、モデル内には浮遊体が1つあるだけですので、qposで指定する値は位置 $(x,y,z)$ と姿勢 $(q_1,q_2,q_3,q_4)$ だけとなります。また、同様にqvelは速度 $(v_x,v_y,v_z)$ と各軸回りの角速度 $(\omega_x,\omega_y,\omega_z)$ だけを指定することになります。qposおよびqvelの詳細は、ジョイントが複数ある場合のqpos、qvel節を参照してください。

spinning_sphere.xml
<mujoco>
  <worldbody>
    <body name="top" pos="0 0 .02">
      <freejoint/>
      <geom name="ball" type="sphere" size=".02" />
    </body>
  </worldbody>

  <keyframe>
    <key name="spinning" qpos="0 0 0.02 1 0 0 0" qvel="0 0 0 0 1 200" />
  </keyframe>
</mujoco>
model = mujoco.MjModel.from_xml_path("spinning_sphere.xml")
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)

# キーフレームを使用して初期位置と初速度を与える
mujoco.mj_resetDataKeyframe(model, data, 0)  # 0番目のkeyを使用して初期化

# シミュレーションを進める
...

属性 : keyframe (MJCF)

 Keyframeは子要素に<key>を持ち、各<key>が個別の初期化用データとなります。<key>の持つ属性は以下の8つであり、すべて任意です。このうち、qposおよびqvelの詳細はジョイントが複数ある場合のqpos、qvel節を参照してください。

すべての属性(<key>

属性 説明
name string "foobar" キーフレームの名前
time real "0" シミュレーションの時刻を指定し、mjData.timeにコピーされます
qpos real(nq) "0 0 1 1 0 0 0"
(浮遊体×1の場合)
ジョイント状態を指定し、mjData.qposにコピーされます
qvel real(nv) "0 0 0 1 1 0"
(浮遊体×1の場合)
ジョイント速度を指定し、mjData.qvelにコピーされます
act real(na) "0 0 ..." アクチュエータを指定された値で起動します。mjData.actにコピーされます
ctrl real(nu) "0 0 ..." コントロールを指定します。mjData.ctrlにコピーされます
mpos real(n)
(3*nmocap)
mjModel.body_posに同 ボディ位置 $(x,y,z)$ を指定し、mjData.mocap_posにコピーされます
mquat real(n)
(4*nmocap)
mjModel.body_quatに同 ボディ姿勢のクォータニオン $(q_1,q_2,q_3,q_4)$ を指定し、mjData.mocap_quatにコピーされます

MJCFの要素 : asset

概要 : asset (MJCF)

 assetは(それ自体はモデル要素ではありませんが)モデル要素が参照することのできるプロパティの一つです。メッシュやスキン、高さフィールド(Height field)、テクスチャ、マテリアルから構成されます[3]。

 メッシュスキンはどちらもOBJファイルやSTLファイルから読み込むことのできるものであり、このうちスキンは実行時に形状を変形することができます。(おそらく)メッシュは物理的性質を持ち、一方スキンは外観にのみ影響を持つようです。高さフィールドは(グレースケール化された)PNGファイル、またはバイナリファイルから読み込むことができます。地図の標高画像のようにして地形を生成できるものと思われます。テクスチャはPNGファイルや既定のものから読み込むことができます。マテリアルgeom, site, tendonの外見を制御するために使用することができます。

詳細はXML Reference/assetを参照してください。

よく使うasset集

 以下によく使うアセットをいくつか提示します。<mesh><hfield>に関してはgeom>type節を参照してください。

チェック柄のアセットです。地面に張り付けて使用されることが多いです。

【チェック柄のアセット】
<asset>
  <texture name="grid" type="2d" builtin="checker" rgb1=".1 .2 .3"
   rgb2=".2 .3 .4" width="300" height="300"/>
  <material name="grid" texture="grid" texrepeat="8 8" reflectance=".2"/>
</asset>

 ビルトインのチェック柄テクスチャを、"grid"というマテリアルとして定義しています。地面に適用する場合、以下のように使用します。

<geom size="1 1 .5" type="plane" material="grid"/>

空(背景色)を青くするアセットです。

【青空のアセット】
<asset>
  <texture type="skybox" builtin="gradient" rgb1="1 1 1" rgb2=".6 .8 1" width="256" height="256"/>
</asset>

 空を青くするアセットです。これをMJCFモデルに記述するだけで空の色が変わります。rgb2属性を変更することで色を青から変更することも可能です。

参考文献

[0] google-deepmind, mujoco, GitHub

[1] Tom Erez, Yuval Tassa and Emanuel Todorov. "Simulation Tools for Model-Based Robotics: Comparison of Bullet, Havok, MuJoCo, ODE and PhysX"

[2] Intuitive Explanation of Tippe Top Effect?

[3] MuJoCo Documentation, Overview

[4] tutorial.ipynb - Colaboratory

[5] カオス理論

[6] MuJoCo Documentation, Modeling

[7] Touch Sensor not able to capture contact force During Collision. #228

[8] MuJoCo Documentation, XML Reference

33
26
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?