Skip to content

Commit 3e66110

Browse files
authored
V0 (#1)
* init physics * mod mujoco * init struct Physics * init environment * init environment::Composer * TODO: action <-> control / 'agent' idea implecitly included in `Environment` * init design.md * I'll have dinner * core interfaces * struct Environment * todo: actuator * init Acturators * refactor * refactor * to examples * remove target git * start impl Task * init_episode * velocity? * TODOs * elbow, shoulder * 2025-07-03 18:06+9:00 * make `should_finish_episode` and `get_reward` to also use `onservation` * todo: impl wrapper {Data, Model} to provide properly typed way to access physics information * 2025-07-08 01:28+9:00 * model * TODO: Physics layer <- `buffer_slices!` * Physics/user-touchable-buffers * TODO: acrobot * TODO: should_finish)episode * 2025-07-11 05:44+9:00 * bin/qtable.rs * `LD_LIBRARY_PATH="$MUJOCO_HOME/lib" cargo run --bin train` * 2025-07-11 11:25+9:00 * git ignore logs * fix should_finish_episode * `WARNING: Nan, Inf or huge value in Q{VEL, ACC, POS} at DOF 0` * where's 'Nan, Inf or huge value'??? * update reward and timestep * debug_{qpos, qvel}() * 2025-07-12 08:11+9:00 * 2025-07-12 10:01+9:00 * 2025-07-12 10:04+9:00 * simulate worked * good camera 0 * 2025-07-12 11:21+9:00 * 2025-07-16 23:03+9:00 * update reward function * rename `{balance => task}: &AcrobotBalanceTask` arg of `get_reward` * update rustfmt.toml * update reward function * update init_episode and digitzed_state * try improving train.rs experiment * origanize train log output * more improve train log * fix typo * use emojis * train: not warm up if it's restored * refactor around trained agent * move current example to personal area * update following rusty_mujoco * update following rusty_mujoco * fix around unused import * update following rusty_mujoco * update following rusty_mujoco & update README * update Cargo.toml
1 parent 8f1333a commit 3e66110

File tree

9 files changed

+393
-17
lines changed

9 files changed

+393
-17
lines changed

.github/workflows/AutoApprove.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# will be removed when this project has more than one maintainers
2+
3+
name: AutoApprove
4+
5+
on:
6+
pull_request:
7+
types: [opened, reopened, synchronize, ready_for_review]
8+
9+
jobs:
10+
approve:
11+
if: |
12+
github.event.pull_request.user.login == 'kanarus' &&
13+
!github.event.pull_request.draft
14+
runs-on: ubuntu-latest
15+
permissions:
16+
pull-requests: write
17+
steps:
18+
- uses: actions/checkout@v4
19+
- name: approve
20+
env:
21+
GH_TOKEN: ${{ github.token }}
22+
run: |
23+
gh pr review ${{ github.event.number }} --approve

.github/workflows/CI.yml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
branches: [main, v*]
77

88
jobs:
9-
CI:
9+
build:
1010
runs-on: ubuntu-latest
1111

1212
strategy:
@@ -16,6 +16,18 @@ jobs:
1616
steps:
1717
- uses: actions/checkout@v4
1818

19-
- run: rustup update && rustup default ${{ matrix.toolchain }}
20-
21-
- run: cargo test
19+
- run: |
20+
rustup update
21+
rustup default ${{ matrix.toolchain }}
22+
rustup component add rustfmt ### required for rusty_mujoco to build ###
23+
24+
- name: install mujoco and set MUJOCO_DIR
25+
run: |
26+
mkdir -p $HOME/.mujoco
27+
cd $HOME/.mujoco
28+
wget https://github.com/google-deepmind/mujoco/releases/download/3.3.2/mujoco-3.3.2-linux-x86_64.tar.gz
29+
tar -xzf mujoco-3.3.2-linux-x86_64.tar.gz
30+
echo "MUJOCO_DIR=$HOME/.mujoco/mujoco-3.3.2" >> $GITHUB_ENV
31+
echo "LD_LIBRARY_PATH=$HOME/.mujoco/mujoco-3.3.2/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV
32+
33+
- run: cargo build

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
/target
2-
Cargo.lock
1+
**/target
2+
**/Cargo.lock

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ homepage = "https://crates.io/crates/oxide_control"
88
repository = "https://github.com/rust-control/oxide_control"
99
readme = "README.md"
1010
license = "MIT"
11-
description = ""
11+
description = "Rust software stack for physics-based simulation and Reinforcement Learning environments, using MuJoCo"
1212
keywords = ["mujoco", "rl", "ml", "physics", "robotics"]
1313
categories = ["science::robotics", "simulation"]
1414

1515
[dependencies]
16-
rusty_mujoco = { path = "../rusty_mujoco" }
16+
rusty_mujoco = "0.1.0"

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<div align="center">
2+
<h1><code>oxide_control</code>: The <a href="https://github.com/google-deepmind/dm_control"><code>dm_control</code></a> layer for Rust</h1>
3+
</div>
4+
5+
`oxide_control` is a Rust software stack for
6+
physics-based simulation and Reinforcement Learning environments, using MuJoCo.
7+
8+
This is built up on [rusty_mujoco](https://github.com/rust-control/rusty_mujoco) binding,
9+
and provides a high-level interface similar to [dm_control](https://github.com/google-deepmind/dm_control) in Python.
10+
11+
## Features
12+
13+

rustfmt.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
max_width = 160

src/error.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use rusty_mujoco::{obj, ObjectId};
2+
3+
pub enum Error {
4+
Mujoco(::rusty_mujoco::MjError),
5+
Mjs(String),
6+
NameNotFound(&'static str),
7+
PhysicsDiverged,
8+
JointTypeNotMatch {
9+
expected: ::rusty_mujoco::bindgen::mjtJoint,
10+
found: ::rusty_mujoco::bindgen::mjtJoint,
11+
},
12+
ActuatorStateless(ObjectId<obj::Actuator>),
13+
PluginStateless(ObjectId<obj::Plugin>),
14+
BodyNotMocap(ObjectId<obj::Body>),
15+
}
16+
17+
impl From<::rusty_mujoco::MjError> for Error {
18+
fn from(e: ::rusty_mujoco::MjError) -> Self {
19+
Error::Mujoco(e)
20+
}
21+
}
22+
23+
impl std::fmt::Debug for Error {
24+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25+
match self {
26+
Error::Mujoco(e) => write!(f, "Error::MuJoCo({e:?})"),
27+
Error::Mjs(msg) => write!(f, "Error::Mjs({msg})"),
28+
Error::NameNotFound(name) => write!(f, "Error::NameNotFound({name})"),
29+
Error::PhysicsDiverged => write!(f, "Error::PhysicsDiverged"),
30+
Error::JointTypeNotMatch { expected, found } => {
31+
write!(f, "Error::JointTypeNotMatch(expected: {expected:?}, found: {found:?})")
32+
}
33+
Error::ActuatorStateless(actuator_id) => {
34+
write!(f, "Error::ActuatorStateless({actuator_id:?})")
35+
}
36+
Error::PluginStateless(plugin_id) => {
37+
write!(f, "Error::PluginStateless({plugin_id:?})")
38+
}
39+
Error::BodyNotMocap(body_id) => {
40+
write!(f, "Error::BodyNotMocap({body_id:?})")
41+
}
42+
}
43+
}
44+
}
45+
46+
impl std::fmt::Display for Error {
47+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48+
match self {
49+
Error::Mujoco(e) => write!(f, "MuJoCo error: {e}"),
50+
Error::Mjs(msg) => write!(f, "MuJoCo error: {msg}"),
51+
Error::NameNotFound(name) => write!(f, "Given name not found: `{name}`"),
52+
Error::PhysicsDiverged => write!(f, "Physics simulation diverged"),
53+
Error::JointTypeNotMatch { expected, found } => {
54+
write!(f, "Joint type mismatch: expected {expected:?}, found {found:?}")
55+
}
56+
Error::ActuatorStateless(actuator_id) => {
57+
write!(f, "Actuator with ID {actuator_id:?} is stateless unexpectedly")
58+
}
59+
Error::PluginStateless(plugin_id) => {
60+
write!(f, "Plugin with ID {plugin_id:?} is stateless unexpectedly")
61+
}
62+
Error::BodyNotMocap(body_id) => {
63+
write!(f, "Body with ID {body_id:?} is not a mocap body")
64+
}
65+
}
66+
}
67+
}
68+
69+
impl std::error::Error for Error {
70+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
71+
match self {
72+
Error::Mujoco(e) => Some(e),
73+
Error::Mjs(_) => None,
74+
Error::NameNotFound(_) => None,
75+
Error::PhysicsDiverged => None,
76+
Error::JointTypeNotMatch { .. } => None,
77+
Error::ActuatorStateless(_) => None,
78+
Error::PluginStateless(_) => None,
79+
Error::BodyNotMocap(_) => None,
80+
}
81+
}
82+
}

src/lib.rs

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,88 @@
1-
pub fn add(left: u64, right: u64) -> u64 {
2-
left + right
1+
pub mod error;
2+
pub mod physics;
3+
4+
pub use physics::Physics as RawPhysics;
5+
6+
pub trait Physics: std::ops::DerefMut<Target = RawPhysics> {}
7+
8+
pub trait Task {
9+
type Physics: Physics;
10+
type Observation: Observation<Physics = Self::Physics>;
11+
type Action: Action<Physics = Self::Physics>;
12+
fn discount(&self) -> f64;
13+
fn init_episode(&self, physics: &mut Self::Physics);
14+
fn should_finish_episode(&self, observation: &Self::Observation) -> bool;
15+
fn get_reward(&self, observation: &Self::Observation, action: &Self::Action) -> f64;
16+
}
17+
18+
pub trait Observation {
19+
type Physics: Physics;
20+
fn generate(physics: &Self::Physics) -> Self;
21+
}
22+
23+
pub trait Action {
24+
type Physics: Physics;
25+
fn apply(&self, actuators: &mut physics::Actuators<'_>);
26+
}
27+
28+
pub struct Environment<T: Task> {
29+
task: T,
30+
physics: T::Physics,
31+
}
32+
33+
impl<T: Task> Environment<T> {
34+
pub fn new(physics: T::Physics, task: T) -> Self {
35+
Self { task, physics }
36+
}
37+
38+
pub fn task(&self) -> &T {
39+
&self.task
40+
}
41+
42+
pub fn physics(&self) -> &T::Physics {
43+
&self.physics
44+
}
45+
pub fn physics_mut(&mut self) -> &mut T::Physics {
46+
&mut self.physics
47+
}
348
}
449

5-
#[cfg(test)]
6-
mod tests {
7-
use super::*;
50+
pub enum TimeStep<O> {
51+
Step {
52+
observation: O,
53+
reward: f64,
54+
discount: f64,
55+
},
56+
Finish {
57+
observation: O,
58+
reward: f64,
59+
},
60+
}
61+
62+
impl<T: Task> Environment<T> {
63+
pub fn reset(&mut self) -> T::Observation {
64+
self.task.init_episode(&mut self.physics);
65+
T::Observation::generate(&self.physics)
66+
}
67+
68+
pub fn step(&mut self, action: T::Action) -> TimeStep<T::Observation> {
69+
action.apply(&mut self.physics.actuators());
70+
self.physics.step();
71+
72+
let observation = T::Observation::generate(&self.physics);
73+
let reward = self.task.get_reward(&observation, &action);
874

9-
#[test]
10-
fn it_works() {
11-
let result = add(2, 2);
12-
assert_eq!(result, 4);
75+
if self.task.should_finish_episode(&observation) {
76+
TimeStep::Finish {
77+
observation,
78+
reward,
79+
}
80+
} else {
81+
TimeStep::Step {
82+
observation,
83+
reward,
84+
discount: self.task.discount(),
85+
}
86+
}
1387
}
1488
}

0 commit comments

Comments
 (0)