tinysim 0.0.2__py3-none-any.whl → 0.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tinysim/frogger/tk.py CHANGED
@@ -21,6 +21,8 @@ class FroggerTkFrontend(_tk_base.TkBaseFrontend):
21
21
  self.sim_env = sim_env
22
22
  self._viewport_size = viewport_size
23
23
 
24
+ self.keys = set()
25
+
24
26
  async def step(self, action, dt=0.01):
25
27
  state = self.sim_env.step(action, dt=dt)
26
28
  if self._root:
@@ -42,6 +44,10 @@ class FroggerTkFrontend(_tk_base.TkBaseFrontend):
42
44
  canvas.pack(fill="both", expand=True)
43
45
  self._root = root
44
46
  self._canvas = canvas
47
+
48
+ root.bind("<KeyPress>", lambda e: self.keys.add(e.keysym))
49
+ root.bind("<KeyRelease>", lambda e: self.keys.discard(e.keysym))
50
+
45
51
  self.bring_to_front(root)
46
52
  self._draw_state(self.sim_env)
47
53
  self._pump()
@@ -14,7 +14,7 @@ class TopDownDrivingWidget(anywidget.AnyWidget):
14
14
 
15
15
  sim_state = traitlets.Dict(default_value={}).tag(sync=True)
16
16
  wall_positions = traitlets.List(default_value=LOCAL_WALLS).tag(sync=True)
17
-
17
+
18
18
  _viewport_size = traitlets.Tuple(
19
19
  traitlets.Int(), traitlets.Int(), default_value=(800, 600)
20
20
  ).tag(sync=True)
@@ -38,8 +38,7 @@ class TopDownDrivingWidget(anywidget.AnyWidget):
38
38
  ui_poll(100)
39
39
  except Exception:
40
40
  pass
41
-
42
-
41
+
43
42
  def copy_py_state(self, sim_state: dict):
44
43
  self.sim_state = {
45
44
  "x": sim_state["x"].tolist(),
@@ -57,4 +56,4 @@ class TopDownDrivingWidget(anywidget.AnyWidget):
57
56
  async def reset(self) -> dict:
58
57
  sim_state = self.sim_env.reset()
59
58
  self.copy_py_state(sim_state)
60
- return sim_state
59
+ return sim_state
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinysim
3
+ Version: 0.0.3
4
+ Summary: small modular simulation environments
5
+ Author-email: Matthew Taylor <matthew.taylor.andre@gmail.com>
6
+ Project-URL: Homepage, https://github.com/MatthewAndreTaylor/TinySim
7
+ Keywords: jupyter,simulation
8
+ Classifier: Framework :: Jupyter
9
+ Classifier: Framework :: Jupyter :: JupyterLab
10
+ Classifier: Framework :: Jupyter :: JupyterLab :: 4
11
+ Classifier: Framework :: Jupyter :: JupyterLab :: Extensions
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: numpy
22
+ Requires-Dist: anywidget
23
+ Requires-Dist: jupyter-ui-poll
24
+ Provides-Extra: warp
25
+ Requires-Dist: warp-lang==1.8.1; extra == "warp"
26
+ Provides-Extra: mujoco
27
+ Requires-Dist: mujoco; extra == "mujoco"
28
+
29
+
30
+ # TinySim
31
+
32
+ [![PyPI](https://img.shields.io/pypi/v/tinysim.svg)](https://pypi.org/project/tinysim)
33
+ [![Jupyterlite](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://matthewandretaylor.github.io/TinySim/lab?path=example.ipynb)
34
+
35
+ The goal of this project is to create more minimal simulation environments for robotics and machine learning.
36
+
37
+ Existing solutions like [ROS 2](https://github.com/ros2) and [Gymnasium](https://gymnasium.farama.org/) come with many heavy dependencies.
38
+
39
+ Additionally they are usually difficult to get setup and may require domain specific knowledge to use.
40
+ Simulation environments should be able to run in any notebook environment and require fewer dependencies.
41
+
42
+
43
+ ## Get Started 🚀
44
+
45
+ To use the baseline Python only simulation environments.
46
+
47
+ ```bash
48
+ pip install tinysim
49
+ ```
50
+
51
+ | [MountainCarEnv](https://github.com/MatthewAndreTaylor/TinySim/blob/main/tinysim/mountain_car) | [TopDownDrivingEnv](https://github.com/MatthewAndreTaylor/TinySim/blob/main/tinysim/topdown_driving) |
52
+ |:-------------------------:|:-------------------------:|
53
+ | <img width="312" alt="mountain_car" src="https://github.com/user-attachments/assets/adbde7f4-6b4e-4c06-8dd3-ea278d5576ad" /> | <img width="312" alt="topdown_driving" src="https://github.com/user-attachments/assets/79dcbf45-055e-4a4e-b6ff-38e938e99a4e" /> |
54
+
55
+ | [FroggerEnv](https://github.com/MatthewAndreTaylor/TinySim/blob/main/tinysim/frogger) | [FlappyEnv](https://github.com/MatthewAndreTaylor/TinySim/blob/main/tinysim/flappy) |
56
+ |:-------------------------:|:-------------------------:|
57
+ | <img width="312" alt="frogger" src="https://github.com/user-attachments/assets/bb9a8382-b9a0-4280-ae23-cbd02e2d78f6" /> | <img width="312" alt="flappy" src="https://github.com/user-attachments/assets/b5e2a502-d9fe-4ff1-9e4e-a47f7e50ebd9" /> |
58
+
59
+
60
+ To use the [warp](https://github.com/NVIDIA/warp) simulation environments.
61
+
62
+ ```bash
63
+ pip install tinysim[warp]
64
+ ```
65
+
66
+ | [CartPoleBaseEnv](https://github.com/MatthewAndreTaylor/TinySim/tree/main/tinysim_warp/cart_pole) | [SimpleRobotDogBaseEnv](https://github.com/MatthewAndreTaylor/TinySim/tree/main/tinysim_warp/simple_quadruped) |
67
+ |:-------------------------:|:-------------------------:|
68
+ | <img width="312" alt="warp_cart_pole" src="https://github.com/user-attachments/assets/f29f3749-d836-4afa-9f27-8578aca020dc" /> | <img width="312" alt="warp_simple_quadruped" src="https://github.com/user-attachments/assets/a966e881-358e-47ee-b0e1-b67eba903987" /> |
69
+
70
+
71
+ To use the [mujoco](https://github.com/google-deepmind/mujoco) simulation environments.
72
+
73
+ ```bash
74
+ pip install tinysim[mujoco]
75
+ ```
76
+ | [UnitreeA1WalkEnv](https://github.com/MatthewAndreTaylor/TinySim/tree/main/tinysim_mujoco/unitree_a1) |
77
+ |:-------------------------:|
78
+ | <img width="312" alt="mujoco_sim" src="https://github.com/user-attachments/assets/1109488e-0563-4694-8926-682edd0c8278" /> |
79
+
80
+
@@ -6,7 +6,7 @@ tinysim/flappy/tk.py,sha256=DUJqy0uPn-AvNE4tuA3a4Dh6xaj0twsUOP8leqGgBX4,2458
6
6
  tinysim/flappy/widget.py,sha256=zyaoW-rWgAkmbCY7yrSYyAZDUQaAPnuQEzldSawkOnI,1597
7
7
  tinysim/frogger/__init__.py,sha256=gA2jvG_WDZRpZY0Zg_aJmWrdsk2siIL49wjFTRfOSxo,5227
8
8
  tinysim/frogger/sim.js,sha256=b1HfEtFZFbbsrZNtltuWO4h-QM4Fw3XjMRydwm5l2NU,3081
9
- tinysim/frogger/tk.py,sha256=3A1wnWcOBCoRNSI4-oHFbMpRwf0iwdV54GBgtAxVufo,2823
9
+ tinysim/frogger/tk.py,sha256=gb2V8bVCVuVazRrfTbAWq46bK20d5ugHxau_rlNABXE,2992
10
10
  tinysim/frogger/widget.py,sha256=Bvb6aUiVqJTHTdd6EzpCp0FqpRa0bl9RNcK12_weiPM,1810
11
11
  tinysim/mountain_car/__init__.py,sha256=V3Qxj_ydWAqOJiQUqmIRegBe9GNjU82BfbG2XvV0OQg,1986
12
12
  tinysim/mountain_car/sim.js,sha256=z7kxl7LbkRglfzXxmrBbwz2vMYdQV84QP3WhE0hoPVQ,4581
@@ -22,12 +22,24 @@ tinysim/topdown_driving/__init__.py,sha256=HiRpxQNeu4IIOPFraGHLTO2F0VNISAk6bUiK1
22
22
  tinysim/topdown_driving/sim.js,sha256=5jmyQk4zp4KFhaG-uTsW_Cq1gHoIkR9d2Cv_ezDKarM,3076
23
23
  tinysim/topdown_driving/tk.py,sha256=8UB95ZjIJiUQeXC5w3pXkjukOJXfisLKrfIn0bVFzjU,4751
24
24
  tinysim/topdown_driving/track_0.json,sha256=3zPfPX72s5zxs4znoO8rXBqGNciai10Jbl2rFCaACow,15338
25
- tinysim/topdown_driving/widget.py,sha256=UqhV9Dps8BQTwTFP1g2wlmzql2ooAhAU8-YF8OmpoD8,1734
26
- tinysim_warp/cart_pole/__init__.py,sha256=TGtvBvQP6lh7ItOwdDyC-tMV-9N3GhgtjAcGvSPpgrk,8344
27
- tinysim_warp/quadruped/__init__.py,sha256=HlC_DYrpvbTUwvlmBY8FUUQPfmqbPjS1hfNP_bXaljk,6393
28
- tinysim_warp/simple_quadruped/__init__.py,sha256=sVyQoFVu_EiBNw1P1U9UfSLXNqyIjIks_XF1tyq3Rtk,5329
25
+ tinysim/topdown_driving/widget.py,sha256=Eh7tvtmMbgmzFEvQZ10D6npfF2s1k7QD5AHLYX5Jaqw,1718
26
+ tinysim_mujoco/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ tinysim_mujoco/unitree_a1/__init__.py,sha256=lxN_94WK_9JdZd3pjZfS_BpvtCY7lmPqQ8KLvaKoLmE,12684
28
+ tinysim_mujoco/unitree_a1/unitree_a1/CHANGELOG.md,sha256=FIdhX4_Q21xamWMS3-2677HhlETlCnfaAzGemqs4Lpo,143
29
+ tinysim_mujoco/unitree_a1/unitree_a1/LICENSE,sha256=K3lKzx4lD1VFrlpH2B5WhepUiM4HfRCH7Yvl-B6bpcA,1559
30
+ tinysim_mujoco/unitree_a1/unitree_a1/README.md,sha256=tHCjy_A1jcGD7bkFy9Qw9z249Kb6HTDQQBlCk8buqlM,1606
31
+ tinysim_mujoco/unitree_a1/unitree_a1/a1.xml,sha256=gJidzvTdw5WwD0X_qrJWjsLwCYmHNqK19fExTtkws4s,9674
32
+ tinysim_mujoco/unitree_a1/unitree_a1/scene.xml,sha256=kvMcNRf1mXFg8CQBp9jrM8B8GCm_TcnQgW68k_iVKis,887
33
+ tinysim_mujoco/unitree_a1/unitree_a1/assets/calf.obj,sha256=duTEMtWXoV4C0q_TFBCxQ_QhodZ7yG1_OALcbzkfCh4,5327177
34
+ tinysim_mujoco/unitree_a1/unitree_a1/assets/hip.obj,sha256=2xx78ZCRU3bnj31Q6iqztiXRtu3QLsLtDQLXweGVqrs,1974009
35
+ tinysim_mujoco/unitree_a1/unitree_a1/assets/thigh.obj,sha256=kUajiusjGyU_X8EyFormznSFUCvGtvrT2K8IhyTGILk,3245342
36
+ tinysim_mujoco/unitree_a1/unitree_a1/assets/thigh_mirror.obj,sha256=A8ynmhTjB9AKowPWTcNk9OYsoWE41Y-XifThe-PdL1g,3354472
37
+ tinysim_mujoco/unitree_a1/unitree_a1/assets/trunk.obj,sha256=wp6C0K3bTH4PArWQV_zMU-m28rFJ1BQMwmRHEqObiHg,5410512
38
+ tinysim_warp/cart_pole/__init__.py,sha256=ltL0cgySOSbeqBvks-UqV1OpT-K-kxo3GylNdvfCB1w,8344
39
+ tinysim_warp/quadruped/__init__.py,sha256=aIOlF9vO9GIYn1y4dth4q-fpUdvB9KZq2rf25YcOcqc,6393
40
+ tinysim_warp/simple_quadruped/__init__.py,sha256=sXL7EOtlB4qm_l4NsYTOFKvpt438voq7OaPgzciPOU0,5329
29
41
  tinysim_warp/simple_quadruped/simple_quadruped.urdf,sha256=UfyGROUD5Qlf_t5ISipE2L7uQPBl-hwFnmRsjNL9yH4,8311
30
- tinysim-0.0.2.dist-info/METADATA,sha256=D0Gt-BPOUwtiYvDosx0Zq_9zusl0Narr2tw9j_UHL80,1942
31
- tinysim-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
- tinysim-0.0.2.dist-info/top_level.txt,sha256=JsBSgxajxod2mlNodBvwkBsn3vMHse0jLqqni2Q5-9Y,21
33
- tinysim-0.0.2.dist-info/RECORD,,
42
+ tinysim-0.0.3.dist-info/METADATA,sha256=C8c2FG_mLRBcUv3j8Rqd3Z14zHif-BI8G4LUjfvQw2A,3941
43
+ tinysim-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
+ tinysim-0.0.3.dist-info/top_level.txt,sha256=v-9NmTLfZukVjPwJwEdtCU7yY4TdLsBf1H8s25tR3V4,36
45
+ tinysim-0.0.3.dist-info/RECORD,,
@@ -1,2 +1,3 @@
1
1
  tinysim
2
+ tinysim_mujoco
2
3
  tinysim_warp
File without changes
@@ -0,0 +1,354 @@
1
+ import pathlib
2
+
3
+ import numpy as np
4
+
5
+ try:
6
+ import mujoco
7
+ import glfw
8
+ except ImportError:
9
+ raise ImportError(
10
+ "Mujoco is not properly installed. Install using `pip install tinysim[mujoco]`"
11
+ )
12
+
13
+
14
+ class Viewer:
15
+ def __init__(self, model, data, width=1200, height=900):
16
+ self.model = model
17
+ self.data = data
18
+ self.width = width
19
+ self.height = height
20
+
21
+ self.window = None
22
+ self.scene = None
23
+ self.context = None
24
+ if not glfw.init():
25
+ raise RuntimeError("Failed to initialize GLFW")
26
+
27
+ self._create_window()
28
+
29
+ def _create_window(self):
30
+ glfw.window_hint(glfw.VISIBLE, glfw.TRUE)
31
+ self.window = glfw.create_window(self.width, self.height, "Viewer", None, None)
32
+ if not self.window:
33
+ raise RuntimeError("Failed to create GLFW window")
34
+
35
+ glfw.make_context_current(self.window)
36
+ glfw.swap_interval(1)
37
+ glfw.set_window_close_callback(self.window, self._on_close)
38
+
39
+ self.cam = mujoco.MjvCamera()
40
+ self.opt = mujoco.MjvOption()
41
+ self.scene = mujoco.MjvScene(self.model, maxgeom=500)
42
+ self.context = mujoco.MjrContext(
43
+ self.model, mujoco.mjtFontScale.mjFONTSCALE_150
44
+ )
45
+
46
+ self.cam.azimuth = 90.0
47
+ self.cam.elevation = -25.0
48
+ self.cam.distance = 4.0
49
+ self.cam.lookat[:] = np.array([0.0, 0.0, 0.0])
50
+ self.cam.type = mujoco.mjtCamera.mjCAMERA_FREE
51
+
52
+ def render(self):
53
+ if self.window is None:
54
+ return
55
+
56
+ glfw.make_context_current(self.window)
57
+ width, height = glfw.get_framebuffer_size(self.window)
58
+ viewport = mujoco.MjrRect(0, 0, width, height)
59
+ mujoco.mjv_updateScene(
60
+ self.model,
61
+ self.data,
62
+ self.opt,
63
+ None,
64
+ self.cam,
65
+ mujoco.mjtCatBit.mjCAT_ALL,
66
+ self.scene,
67
+ )
68
+
69
+ mujoco.mjr_render(viewport, self.scene, self.context)
70
+ glfw.swap_buffers(self.window)
71
+ glfw.poll_events()
72
+
73
+ def _on_close(self, window):
74
+ self.close()
75
+
76
+ def close(self):
77
+ if self.window is not None:
78
+ glfw.destroy_window(self.window)
79
+ self.window = None
80
+ self.scene = None
81
+ self.context = None
82
+
83
+
84
+ class UnitreeA1BaseEnv:
85
+
86
+ def __init__(self, headless=False):
87
+ model_path = str(pathlib.Path(__file__).parent / "unitree_a1/scene.xml")
88
+ self.model = mujoco.MjModel.from_xml_path(model_path)
89
+ self.data = mujoco.MjData(self.model)
90
+ self.action_space_size = self.model.nu
91
+
92
+ self.default_joint_position = np.array(
93
+ [-0.05, 0.8, -1.5, 0.05, 0.8, -1.5, -0.03, 0.9, -1.4, 0.03, 0.9, -1.4],
94
+ dtype=np.float32,
95
+ )
96
+
97
+ if not headless:
98
+ self.viewer = Viewer(self.model, self.data)
99
+ else:
100
+ self.viewer = None
101
+
102
+ def step(self, actions, n_frames=20):
103
+ if self.viewer is not None:
104
+ self.render()
105
+
106
+ if len(actions) != self.action_space_size:
107
+ raise ValueError(
108
+ f"Expected {self.action_space_size} actions, but got {len(actions)}"
109
+ )
110
+
111
+ self.data.ctrl[:] = actions
112
+ mujoco.mj_step(self.model, self.data, nstep=n_frames)
113
+ mujoco.mj_rnePostConstraint(self.model, self.data)
114
+
115
+ def render(self):
116
+ if self.viewer is None:
117
+ return
118
+ self.viewer.render()
119
+
120
+ def set_state(self, qpos, qvel):
121
+ self.data.qpos[:] = qpos
122
+ self.data.qvel[:] = qvel
123
+ mujoco.mj_forward(self.model, self.data)
124
+
125
+ def close(self):
126
+ self.viewer.close()
127
+
128
+ def reset(self):
129
+ mujoco.mj_resetData(self.model, self.data)
130
+ self.data.qpos[0:3] = [0.0, 0.0, 0.3]
131
+ self.data.qpos[7:19] = self.default_joint_position.copy()
132
+ self.data.qvel[:] = 0
133
+ self.data.ctrl[:] = 0
134
+ mujoco.mj_forward(self.model, self.data)
135
+
136
+ def get_position_data(self):
137
+ return self.data.qpos[0:3].copy()
138
+
139
+ def get_joint_data(self):
140
+ return self.data.qpos[7:19].copy()
141
+
142
+ def get_joint_effort(self):
143
+ return self.data.ctrl[:12].copy()
144
+
145
+ def get_velocity_data(self):
146
+ return self.data.qvel.copy()
147
+
148
+
149
+ class UnitreeA1WalkEnv:
150
+ def __init__(self, reward_weights, cost_weights, headless=False):
151
+ self.env = UnitreeA1BaseEnv(headless=headless)
152
+ self.frame_skip = 20
153
+ self.obs_dim = 45
154
+ self._previous_observation = np.zeros(self.obs_dim, dtype=np.float32)
155
+ self.joint_limits_low = np.array(
156
+ [-0.1, 0.25, -2.0, -0.1, 0.25, -2.0, -0.03, 0.25, -1.6, 0.03, 0.25, -1.6],
157
+ dtype=np.float32,
158
+ )
159
+ self.joint_limits_high = np.array(
160
+ [0.1, 1.2, -0.5, 0.1, 1.2, -0.5, 0.03, 1.2, -1.1, 0.03, 1.2, -1.1],
161
+ dtype=np.float32,
162
+ )
163
+
164
+ self._feet_air_time = np.zeros(4)
165
+ self._last_contacts = np.zeros(4)
166
+ self.step_counter = 0
167
+ self.tracking_sigma = 0.25
168
+ self.maximum_episode_steps = 1024
169
+ self.prev_action = np.zeros_like(self.env.default_joint_position)
170
+ self.target_lin_vel = np.array([0.5, 0.0])
171
+ self.target_ang_vel = 0.0
172
+ self._healthy_z_range = (0.3, 0.335)
173
+ self._healthy_rotation = np.array(
174
+ [np.deg2rad(6), np.deg2rad(6), np.deg2rad(10)]
175
+ )
176
+
177
+ self._cfrc_ext_feet_indices = [4, 7, 10, 13]
178
+ self.reward_weights = reward_weights
179
+ self.cost_weights = cost_weights
180
+ # TODO: requires keys
181
+
182
+ def reset(self, **kwargs):
183
+ self.env.reset()
184
+ self.step_counter = 0
185
+ self._feet_air_time = np.zeros(4)
186
+ self._last_contacts = np.zeros(4)
187
+ self.prev_action = self.env.default_joint_position.copy()
188
+ self.target_lin_vel = np.array([0.5, 0.0])
189
+ current_obs = self._get_obs()
190
+ self._previous_observation = np.zeros_like(current_obs)
191
+ full_obs = np.concatenate([current_obs, self._previous_observation])
192
+ self._previous_observation = current_obs.copy()
193
+ return full_obs, {}
194
+
195
+ def step(self, action):
196
+ self.step_counter += 1
197
+ a = 0.7
198
+ action_filtered = a * self.prev_action + (1 - a) * action
199
+ if self.step_counter < 1:
200
+ action_filtered = self.env.default_joint_position.copy()
201
+ self.env.step(action_filtered, self.frame_skip)
202
+
203
+ current_obs = self._get_obs()
204
+ obs = np.concatenate([current_obs, self._previous_observation])
205
+ reward = self._compute_reward(action)
206
+ terminated = not self.is_healthy
207
+ truncated = self.step_counter >= self.maximum_episode_steps
208
+ self.prev_action = action_filtered
209
+ self._previous_observation = current_obs.copy()
210
+ return obs, reward, terminated, truncated, {}
211
+
212
+ def _get_obs(self):
213
+ base_ang_vel = self.euler_from_quaternion(*self.env.data.qpos[3:7])
214
+ joint_positions = self.env.get_joint_data()
215
+ joint_velocities = self.env.get_velocity_data()[6:]
216
+ curr_contact = self.feet_contact_forces > 1.0
217
+ return np.concatenate(
218
+ (
219
+ base_ang_vel,
220
+ joint_positions,
221
+ joint_velocities,
222
+ curr_contact,
223
+ self.target_lin_vel,
224
+ self.prev_action,
225
+ ),
226
+ dtype=np.float32,
227
+ )
228
+
229
+ def _compute_reward(self, action):
230
+ vel = self.env.get_velocity_data()
231
+ lin_vel = np.array(vel[:3])
232
+ ang_vel = np.array(vel[3:6])
233
+
234
+ tracking_lin_vel_reward, tracking_ang_vel_reward, lin_vel_z_penalty = (
235
+ self._tracking_velocity_penalty(lin_vel, ang_vel)
236
+ )
237
+ action_diff_penalty = np.sum(np.abs(action - self.prev_action))
238
+ action_sym = self.action_sym()
239
+
240
+ Positive_rewards = (
241
+ tracking_lin_vel_reward * self.reward_weights["linear_vel_tracking"]
242
+ + tracking_ang_vel_reward * self.reward_weights["angular_vel_tracking"]
243
+ + self.is_healthy * self.reward_weights["healthy"]
244
+ + self.feet_air_time_reward * self.reward_weights["feet_airtime"]
245
+ )
246
+
247
+ Negative_rewards = (
248
+ self.torque_cost * self.cost_weights["torque"]
249
+ + action_diff_penalty * self.cost_weights["action_rate"]
250
+ + lin_vel_z_penalty * self.cost_weights["vertical_vel"]
251
+ + self.xy_angular_velocity_cost * self.cost_weights["xy_angular_vel"]
252
+ + action_sym * self.cost_weights["action_sym"]
253
+ + self.acceleration_cost * self.cost_weights["joint_acceleration"]
254
+ + self.orientation_cost * self.cost_weights["orientation"]
255
+ + self.default_joint_position_cost
256
+ * self.cost_weights["default_joint_position"]
257
+ )
258
+ reward = Positive_rewards - Negative_rewards
259
+ return reward
260
+
261
+ def _tracking_velocity_penalty(self, lin_vel, ang_vel):
262
+ lin_vel_error = np.sum(np.abs(self.target_lin_vel[0] - lin_vel[0]))
263
+ tracking_lin_vel_reward = np.exp(-lin_vel_error / self.tracking_sigma)
264
+ ang_vel_error = np.sum(np.square(self.target_ang_vel - ang_vel[2]))
265
+ tracking_ang_vel_reward = np.exp(-ang_vel_error / self.tracking_sigma)
266
+ lin_vel_z_penalty = np.square((lin_vel[2]))
267
+ return tracking_lin_vel_reward, tracking_ang_vel_reward, lin_vel_z_penalty
268
+
269
+ @property
270
+ def feet_air_time_reward(self):
271
+ curr_contact = self.feet_contact_forces > 1.0
272
+ contact_filter = np.logical_or(curr_contact, self._last_contacts)
273
+ self._last_contacts = curr_contact
274
+ first_contact = (self._feet_air_time > 0.0) * contact_filter
275
+ self._feet_air_time += self.env.model.opt.timestep * self.frame_skip
276
+ air_time_reward = np.sum((self._feet_air_time) * first_contact)
277
+ air_time_reward *= np.linalg.norm(self.target_lin_vel) > 0.1
278
+ self._feet_air_time *= ~contact_filter
279
+ return air_time_reward
280
+
281
+ @property
282
+ def torque_cost(self):
283
+ return np.sum(np.abs(self.env.get_joint_effort()))
284
+
285
+ @property
286
+ def xy_angular_velocity_cost(self):
287
+ return np.sum(np.square(self.env.data.qvel[5]))
288
+
289
+ @property
290
+ def is_healthy(self):
291
+ _, _, z = self.env.get_position_data()
292
+ min_z, max_z = self._healthy_z_range
293
+ if not (min_z <= z <= max_z):
294
+ return False
295
+
296
+ r = self.euler_from_quaternion(*self.env.data.qpos[3:7])
297
+ return np.all(np.abs(r) <= self._healthy_rotation)
298
+
299
+ @property
300
+ def feet_contact_forces(self):
301
+ contact_forces = self.env.data.cfrc_ext[self._cfrc_ext_feet_indices].copy()
302
+ return np.linalg.norm(contact_forces, axis=1)
303
+
304
+ @property
305
+ def orientation_cost(self):
306
+ roll, pitch, _ = self.euler_from_quaternion(*self.env.data.qpos[3:7])
307
+ return np.square(roll) + np.square(pitch)
308
+
309
+ @property
310
+ def default_joint_position_cost(self):
311
+ joint_pos = self.env.get_joint_data()
312
+ soft_joint_limits_low = self.joint_limits_low * 0.9
313
+ soft_joint_limits_high = self.joint_limits_high * 0.9
314
+
315
+ lower_violation = np.maximum(soft_joint_limits_low - joint_pos, 0)
316
+ upper_violation = np.maximum(joint_pos - soft_joint_limits_high, 0)
317
+ return np.sum(np.square(lower_violation + upper_violation))
318
+
319
+ def action_sym(self):
320
+ in_phase_pairs_thigh = [(1, 7), (4, 10)]
321
+ out_of_phase_pairs_thigh = [(1, 4), (7, 10)]
322
+ out_of_phase_pairs_calf = [(2, 5), (8, 11)]
323
+ in_phase_pairs_calf = [(2, 8), (5, 11)]
324
+
325
+ jointpositions = self.env.get_joint_data()
326
+ loss_in_thigh = sum(
327
+ (jointpositions[i] - jointpositions[j]) ** 2
328
+ for i, j in in_phase_pairs_thigh
329
+ )
330
+ loss_out_thigh = sum(
331
+ (jointpositions[i] + jointpositions[j] - 1.5) ** 2
332
+ for i, j in out_of_phase_pairs_thigh
333
+ )
334
+ loss_in_calf = sum(
335
+ (jointpositions[i] - jointpositions[j]) ** 2 for i, j in in_phase_pairs_calf
336
+ )
337
+ loss_out_calf = sum(
338
+ (jointpositions[i] + jointpositions[j] + 3.6) ** 2
339
+ for i, j in out_of_phase_pairs_calf
340
+ )
341
+ loss_in = loss_in_thigh + 0.1 * loss_in_calf
342
+ loss_out = loss_out_thigh + 0.1 * loss_out_calf
343
+ return loss_in + loss_out
344
+
345
+ @property
346
+ def acceleration_cost(self):
347
+ return np.sum(np.square(self.env.data.qacc[6:]))
348
+
349
+ @staticmethod
350
+ def euler_from_quaternion(w, x, y, z):
351
+ roll = np.arctan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y))
352
+ pitch = np.arcsin(np.clip(2 * (w * y - z * x), -1.0, 1.0))
353
+ yaw = np.arctan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z))
354
+ return roll, pitch, yaw
@@ -0,0 +1,6 @@
1
+ # Changelog – Unitree A1 Description
2
+
3
+ All notable changes to this model will be documented in this file.
4
+
5
+ ## [2022-09-07]
6
+ - Initial release.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2016-2022 HangZhou YuShu TECHNOLOGY CO.,LTD. ("Unitree Robotics")
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,41 @@
1
+ # Unitree A1 Description (MJCF)
2
+
3
+ > [!IMPORTANT]
4
+ > Requires MuJoCo 2.2.2 or later.
5
+
6
+ ## Changelog
7
+
8
+ See [CHANGELOG.md](./CHANGELOG.md) for a full history of changes.
9
+
10
+ ## Overview
11
+
12
+ This package contains a simplified robot description (MJCF) of the [A1 Quadruped
13
+ Robot](https://www.unitree.com/a1/) developed by [Unitree
14
+ Robotics](https://www.unitree.com/). It is derived from the [publicly available
15
+ URDF
16
+ description](https://github.com/unitreerobotics/unitree_mujoco/tree/main/data/a1/urdf).
17
+
18
+ <p float="left">
19
+ <img src="a1.png" width="400">
20
+ </p>
21
+
22
+ ## URDF → MJCF derivation steps
23
+
24
+ 1. Converted the DAE [mesh
25
+ files](https://github.com/unitreerobotics/unitree_mujoco/tree/main/data/a1/meshes)
26
+ to OBJ format using [Blender](https://www.blender.org/).
27
+ 2. Processed `.obj` files with [`obj2mjcf`](https://github.com/kevinzakka/obj2mjcf).
28
+ 3. Added `<mujoco> <compiler discardvisual="false"/> </mujoco>` to the URDF's
29
+ `<robot>` clause in order to preserve visual geometries.
30
+ 4. Loaded the URDF into MuJoCo and saved a corresponding MJCF.
31
+ 5. Added a `<freejoint/>` to the base, and a tracking light.
32
+ 6. Manually edited the MJCF to extract common properties into the `<default>` section.
33
+ 7. Manually designed collision geometries.
34
+ 8. Shifted joint reference values and ranges so that 0 corresponds to standing pose.
35
+ 9. Softened the contacts of the feet to approximate the effect of rubber and
36
+ increased `impratio` to reduce slippage.
37
+ 10. Added `scene.xml` which includes the robot, with a textured groundplane, skybox, and haze.
38
+
39
+ ## License
40
+
41
+ This model is released under a [BSD-3-Clause License](LICENSE).