macOS에서 MuJoCo 시작하기

0

MuJoCo(Multi-Joint dynamics with Contact)는 Emo Todorov의 주도로 개발되었고 Google DeepMind가 유지·관리하는 물리 엔진이다. 로보틱스, 강화 학습 연구, 생체역학에 널리 사용된다. 이 글은 macOS에서의 설치, 모델 임포트, Python과 JavaScript에서의 시뮬레이션 실행을 다룬다.

MuJoCo는 기계 시스템을 관절로 연결된 강체(rigid body)들의 집합으로 표현한다. 시스템의 상태는 일반화 좌표(generalized coordinate)에서 위치와 속도로 기술된다. 각 타임스텝마다 MuJoCo는 접촉력을 계산하고, 액추에이터 힘을 적용하며, 운동 방정식을 적분하여 위치와 속도를 갱신한다.

설치

Python: 공식 Python 바인딩은 PyPI에 mujoco 패키지로 배포된다. MuJoCo를 설치하려면 아래 명령을 실행한다.

pip install mujoco

또한 설치를 확인하려면 다음을 실행한다.

python -c "import mujoco; print(mujoco.__version__)"

JavaScript: MuJoCo는 google-deepmind/mujoco 저장소에서 공식 WebAssembly(WASM) 바인딩을 제공한다. npm으로 패키지를 설치할 수 있다.

npm install mujoco-js

또한 MuJoCo 버전을 확인하여 설치를 검증할 수 있다. mj_version()은 정수 형태의 버전을 반환한다.

import loadMujoco from 'mujoco-js';
const mujoco = await loadMujoco();
console.log(mujoco.mj_version()); // e.g. 338 for version 3.3.8

모델 불러오기

Python: MjModel은 XML 파일 경로로부터 불러온다.

import mujoco

model = mujoco.MjModel.from_xml_path("double_pendulum.xml")
data  = mujoco.MjData(model)

또는 XML 문자열로부터 불러올 수 있다.

import mujoco

xml_string = `
<mujoco model="{model_name}">
	...
</mujoco>
`
model = mujoco.MjModel.from_xml_string(xml_string)
data  = mujoco.MjData(model)

JavaScript: WASM 모듈은 호스트 파일시스템에 접근할 수 없다. 대신, 가상 파일시스템 mujoco.FS에 MJCF XML을 작성한 뒤 MjModel.loadFromXML로 불러온다.

import loadMujoco from 'mujoco-js';
const mujoco = await loadMujoco();

xml_string = `
<mujoco model="{model_name}">
	...
</mujoco>
`
mujoco.FS.mkdir("/working");
mujoco.FS.mount(mujoco.MEMFS, { root: "." }, "/working");
mujoco.FS.writeFile("/working/scene.xml", xml_string);

const model = mujoco.MjModel.loadFromXML("/working/scene.xml");
const data  = new mujoco.MjData(model);

불러오기 API가 준비되면, 기존 라이브러리에서 미리 만들어진 MJCF 모델을 가져오거나 MJCF로 직접 구성할 수 있다.

오픈소스 모델 임포트

MuJoCo Menagerie는 Google DeepMind가 유지 및 관리하는 표준 모델 라이브러리다. 로봇 팔, 휴머노이드, 사족 보행 로봇, 모바일 매니퓰레이터, 드론, 생체역학 시스템에 대해 물리적으로 정확한 MJCF 모델을 포함하고 있다. 각 모델 폴더에는 <model>.xml(기구학적·물리적 기술), scene.xml(지면, 조명, 카메라), 그리고 assets/의 메시 자산이 들어 있다.

git clone https://github.com/google-deepmind/mujoco_menagerie.git
python -m mujoco.viewer --mjcf mujoco_menagerie/unitree_go2/scene.xml

Robosuite는 ARISE Initiative가 개발한, MuJoCo 위에 구축된 매니퓰레이션 중심 시뮬레이션 프레임워크다. 표준화된 벤치마크 작업(Lift, Stack, NutAssembly, PickPlace, Door 등)과, 로봇 모델 및 객체로부터 새로운 환경을 구성하는 모듈식 API를 제공한다. 각 환경은 Gym 스타일 인터페이스를 이용한다. macOS에서는 정책상 passive 뷰어를 사용하여야 하므로, mjpython 명령어를 이용하여 실행하여야 한다.

# pip install robosuite
import robosuite as suite

env = suite.make("Lift", robots="Panda", has_renderer=True)
obs = env.reset()
for _ in range(1000):
    action = env.action_space.sample()
    obs, reward, done, info = env.step(action)
    env.render()

Robot Descriptions는 MuJoCo Menagerie 모델과 기타 MJCF/URDF 로봇 모델에 접근할 수 있는 API를 제공한다.

import mujoco
# pip install robot_descriptions
from robot_descriptions.loaders.mujoco import load_robot_description

model = load_robot_description("panda_mj_description")
data  = mujoco.MjData(model)

DeepMind Control Suite는 보상 함수와 MJCF 모델을 포함한 벤치마크 환경(cartpole, cheetah, humanoid 등)을 제공한다.

# pip install dm_control
from dm_control import suite

env = suite.load(domain_name="cheetah", task_name="run")
time_step = env.reset()
print(time_step.observation)

커스텀 모델 설계

MJCF 모델은 <mujoco>를 태그로 하는 XML 문서다. 주요 요소는 다음과 같다:

  • <worldbody>: 기구학적 트리의 루트. 모든 body가 여기에 포함된다.
  • <body>: 강체. <geom>, <joint>, 자식 <body> 요소를 포함한다.
  • <geom>: body에 부착된 충돌 및/또는 시각 geometry.
  • <joint>: body를 부모에 연결하는 관절 부분.
  • <actuator>: 관절에 부착된 액추에이터. 일반적인 타입은 motor, position, velocity가 있다.

예를 들어, 다음 MJCF는 이중 진자(double pendulum)를 정의한다.

<mujoco model="double_pendulum">
  <option timestep="0.002" integrator="RK4"/>

  <worldbody>
    <light pos="0 0 3" dir="0 0 -1"/>
    <geom name="floor" type="plane" size="1 1 0.1" rgba=".9 .9 .9 1"/>

    <body name="link1" pos="0 0 1">
      <joint name="hinge1" type="hinge" axis="0 1 0"/>
      <geom type="capsule" fromto="0 0 0  0 0 -0.5" size="0.04" rgba=".8 .2 .2 1"/>

      <body name="link2" pos="0 0 -0.5">
        <joint name="hinge2" type="hinge" axis="0 1 0"/>
        <geom type="capsule" fromto="0 0 0  0 0 -0.5" size="0.04" rgba=".2 .4 .8 1"/>
      </body>
    </body>
  </worldbody>

  <actuator>
    <motor name="torque1" joint="hinge1" gear="50" ctrllimited="true" ctrlrange="-1 1"/>
  </actuator>
</mujoco>

option 태그의 timestep은 적분 스텝을 초 단위로 설정한다. integrator="RK4"는 4차 룽게-쿠타(Runge–Kutta)를 선택하며, 기본값은 반-암시적 오일러(semi-implicit Euler)다. 관절 축은 body의 지역 프레임으로 표현된다. gear는 제어 입력을 스케일링한다. 자세한 내용은 MuJoCo 문서를 참고하길 바란다.

시뮬레이션 실행

스크립트 작성

핵심 시뮬레이션 루프는 세 객체를 사용한다. mujoco.MjModel(컴파일된 모델, 런타임에 읽음), mujoco.MjData(가변 시뮬레이션 상태 — 위치, 속도, 힘), mujoco.mj_step(시뮬레이션을 model.opt.timestep초만큼 전진).

import mujoco
import numpy as np

model = mujoco.MjModel.from_xml_path("double_pendulum.xml")
data  = mujoco.MjData(model)

# Set initial joint angles (radians)
data.qpos[0] = np.pi / 4   # hinge1
data.qpos[1] = np.pi / 6   # hinge2

# Run for 5 seconds and record joint angles
duration   = 5.0
trajectory = []

while data.time < duration:
    mujoco.mj_step(model, data)
    trajectory.append(data.qpos.copy())

trajectory = np.array(trajectory)
print(f"Simulated {len(trajectory)} steps")
print(f"Final qpos: {trajectory[-1]}")

data.qpos는 길이 model.nq(일반화 위치 좌표의 수)인 NumPy 배열이다. 위 예제에서 nq == nv == 1이다. data.timemj_step을 호출할 때마다 model.opt.timestep만큼 자동으로 증가한다. 자세한 내용은 MuJoCo 문서를 참고하길 바란다.

뷰어 실행

MuJoCo는 인터랙티브 GUI 뷰어를 포함한다.

Standalone: 명령줄에서 직접 관리형 뷰어를 독립 앱으로 실행한다.

python -m mujoco.viewer --mjcf double_pendulum.xml

Managed 뷰어: launch는 자체 물리 루프를 실시간 속도로 실행한다. 매 루프에서 커스텀 함수를 실행시킬 필요가 없을 때, 사용한다.

import mujoco
import mujoco.viewer

model = mujoco.MjModel.from_xml_path("double_pendulum.xml")
data  = mujoco.MjData(model)

mujoco.viewer.launch(model, data)

Passive 뷰어: passive 뷰어는 사용자 코드가 루프를 제어할 수 있게 한다. macOS에서 메인 스레드가 렌더링을 담당하여야 하며, 이 때는 passive 뷰어(mujoco.viewer.launch_passive)를 실행시켜야 한다. passive 뷰어를 사용하려면, 패키지와 함께 설치되는 mjpython을 사용해야 한다.

# run with: mjpython script.py
import mujoco
import mujoco.viewer
import time

model = mujoco.MjModel.from_xml_path("double_pendulum.xml")
data  = mujoco.MjData(model)

with mujoco.viewer.launch_passive(model, data) as viewer:
    start = time.time()
    while viewer.is_running() and time.time() - start < 10:
        mujoco.mj_step(model, data)
        viewer.sync()

viewer.sync()는 갱신된 data 상태를 렌더러로 보낸다. with 블록은 종료 시 뷰어 창이 닫히도록 보장한다. 뷰어는 아래와 같이 나타난다.

정리하며

이 글은 macOS에서 MuJoCo를 설정하는 과정을 다루었다. Python과 JavaScript 설치로 시작하여, 온라인에서 미리 만들어진 모델을 가져오는 방법을 살펴보았다. 또한 커스텀 MJCF 모델을 처음부터 설계하고, 시뮬레이션 루프와 뷰어 사용으로 마무리했다. 다음 글에서는 MuJoCo 모델을 이용하여 강화 학습 에이전트를 훈련시키는 방법에 대해 다룰 예정이다.