Skip to content
Permalink
Browse files

Release dm_control.locomotion, containing a multi-agent soccer enviro…

…nment

PiperOrigin-RevId: 234823536
  • Loading branch information...
liusiqi43 authored and alimuldal committed Feb 20, 2019
1 parent 7a374b1 commit 5583074411eb2101fdd91ac0a76812472926f02f
Showing with 3,493 additions and 1 deletion.
  1. +14 −0 dm_control/entities/__init__.py
  2. +23 −0 dm_control/entities/props/__init__.py
  3. +210 −0 dm_control/entities/props/position_detector.py
  4. +133 −0 dm_control/entities/props/position_detector_test.py
  5. +112 −0 dm_control/entities/props/primitive.py
  6. +100 −0 dm_control/entities/props/primitive_test.py
  7. +14 −0 dm_control/locomotion/__init__.py
  8. +39 −0 dm_control/locomotion/soccer/README.md
  9. +75 −0 dm_control/locomotion/soccer/__init__.py
  10. +55 −0 dm_control/locomotion/soccer/assets/boxhead/boxhead.xml
  11. BIN dm_control/locomotion/soccer/assets/boxhead/digits/00.png
  12. BIN dm_control/locomotion/soccer/assets/boxhead/digits/01.png
  13. BIN dm_control/locomotion/soccer/assets/boxhead/digits/02.png
  14. BIN dm_control/locomotion/soccer/assets/boxhead/digits/03.png
  15. BIN dm_control/locomotion/soccer/assets/boxhead/digits/04.png
  16. BIN dm_control/locomotion/soccer/assets/boxhead/digits/05.png
  17. BIN dm_control/locomotion/soccer/assets/boxhead/digits/06.png
  18. BIN dm_control/locomotion/soccer/assets/boxhead/digits/07.png
  19. BIN dm_control/locomotion/soccer/assets/boxhead/digits/08.png
  20. BIN dm_control/locomotion/soccer/assets/boxhead/digits/09.png
  21. BIN dm_control/locomotion/soccer/assets/boxhead/digits/10.png
  22. BIN dm_control/locomotion/soccer/assets/soccer_ball/back.png
  23. BIN dm_control/locomotion/soccer/assets/soccer_ball/down.png
  24. BIN dm_control/locomotion/soccer/assets/soccer_ball/front.png
  25. BIN dm_control/locomotion/soccer/assets/soccer_ball/left.png
  26. BIN dm_control/locomotion/soccer/assets/soccer_ball/right.png
  27. BIN dm_control/locomotion/soccer/assets/soccer_ball/up.png
  28. +243 −0 dm_control/locomotion/soccer/boxhead.py
  29. +49 −0 dm_control/locomotion/soccer/boxhead_test.py
  30. +35 −0 dm_control/locomotion/soccer/explore.py
  31. +68 −0 dm_control/locomotion/soccer/initializers.py
  32. +54 −0 dm_control/locomotion/soccer/loader_test.py
  33. +408 −0 dm_control/locomotion/soccer/observables.py
  34. +296 −0 dm_control/locomotion/soccer/pitch.py
  35. +86 −0 dm_control/locomotion/soccer/pitch_test.py
  36. +213 −0 dm_control/locomotion/soccer/soccer_ball.py
  37. +67 −0 dm_control/locomotion/soccer/soccer_ball_test.py
  38. +160 −0 dm_control/locomotion/soccer/task.py
  39. +350 −0 dm_control/locomotion/soccer/task_test.py
  40. +32 −0 dm_control/locomotion/soccer/team.py
  41. +14 −0 dm_control/locomotion/walkers/__init__.py
  42. +460 −0 dm_control/locomotion/walkers/base.py
  43. +108 −0 dm_control/locomotion/walkers/base_test.py
  44. +57 −0 dm_control/locomotion/walkers/initializers/__init__.py
  45. +1 −0 requirements.txt
  46. +17 −1 setup.py
@@ -0,0 +1,14 @@
# Copyright 2019 The dm_control Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
@@ -0,0 +1,23 @@
# Copyright 2019 The dm_control Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================

"""Composer entities corresponding to props.
A "prop" is typically a non-actuated entity representing an object in the world.
"""

# Prop imports
from dm_control.entities.props.position_detector import PositionDetector
from dm_control.entities.props.primitive import Primitive
@@ -0,0 +1,210 @@
# Copyright 2019 The dm_control Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================

"""Detects the presence of registered entities within a cuboidal region."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from dm_control import composer
from dm_control import mjcf
import numpy as np

_RENDERED_HEIGHT_IN_2D_MODE = 0.02


class _Detection(object):

__slots__ = ('entity', 'detected')

def __init__(self, entity, detected=False):
self.entity = entity
self.detected = detected


class PositionDetector(composer.Entity):
"""Detects the presence of registered entities within a cuboidal region.
An entity is considered "detected" if the `xpos` value of any one of its geom
lies within the active region defined by this detector. Note that this is NOT
a contact-based detector. Generally speaking, a geom will not be detected
until it is already "half inside" the region.
This detector supports both 2D and 3D modes. In 2D mode, the active region
has an effective infinite height along the z-direction.
This detector also provides an "inverted" detection mode, where an entity is
detected when it is not inside the detector's region.
"""

def _build(self,
pos,
size,
inverted=False,
visible=False,
rgba=(1, 0, 0, 0.25),
detected_rgba=(0, 1, 0, 0.25),
name='position_detector'):
"""Builds the detector.
Args:
pos: The position at the center of this detector's active region. Should
be an array-like object of length 3 in 3D mode, or length 2 in 2D mode.
size: The half-lengths of this detector's active region. Should
be an array-like object of length 3 in 3D mode, or length 2 in 2D mode.
inverted: (optional) A boolean, whether to operate in inverted detection
mode. If `True`, an entity is detected when it is not in the active
region.
visible: (optional) A boolean, whether this detector is visible by
default in rendered images. If `False`, this detector's active zone
is placed in MuJoCo rendering group 4, which is not rendered by default,
but can be toggled on (e.g. in Domain Explorer) for debugging purposes.
rgba: (optional) The color to render when nothing is detected.
detected_rgba: (optional) The color to render when an entity is detected.
name: (optional) XML element name of this position detector.
Raises:
ValueError: If the `pos` and `size` arrays do not have the same length.
"""
if len(pos) != len(size):
raise ValueError('`pos` and `size` should have the same length: '
'got {!r} and {!r}'.format(pos, size))

self._inverted = inverted
self._detected = False
self._lower = np.array(pos) - np.array(size)
self._upper = np.array(pos) + np.array(size)

self._entities = []
self._entity_geoms = {}

self._rgba = np.asarray(rgba)
self._detected_rgba = np.asarray(detected_rgba)

render_pos = np.zeros(3)
render_pos[:len(pos)] = pos

render_size = np.full(3, _RENDERED_HEIGHT_IN_2D_MODE)
render_size[:len(size)] = size

self._mjcf_root = mjcf.RootElement(model=name)
self._site = self._mjcf_root.worldbody.add(
'site', name='detection_zone', type='box',
pos=render_pos, size=render_size, rgba=self._rgba)
if not visible:
self._site.group = composer.SENSOR_SITES_GROUP

def resize(self, pos, size):
if len(pos) != len(size):
raise ValueError('`pos` and `size` should have the same length: '
'got {!r} and {!r}'.format(pos, size))
self._lower = np.array(pos) - np.array(size)
self._upper = np.array(pos) + np.array(size)

render_pos = np.zeros(3)
render_pos[:len(pos)] = pos

render_size = np.full(3, _RENDERED_HEIGHT_IN_2D_MODE)
render_size[:len(size)] = size

self._site.pos = render_pos
self._site.size = render_size

def set_colors(self, rgba, detected_rgba):
self.set_color(rgba)
self.set_detected_color(detected_rgba)

def set_color(self, rgba):
self._rgba[:3] = rgba
self._site.rgba = self._rgba

def set_detected_color(self, detected_rgba):
self._detected_rgba[:3] = detected_rgba

def set_position(self, physics, pos):
physics.bind(self._site).pos = pos
size = physics.bind(self._site).size[:3]
self._lower = np.array(pos) - np.array(size)
self._upper = np.array(pos) + np.array(size)

@property
def mjcf_model(self):
return self._mjcf_root

def register_entities(self, *entities):
for entity in entities:
self._entities.append(_Detection(entity))
self._entity_geoms[entity] = entity.mjcf_model.find_all('geom')

def deregister_entities(self):
self._entities = []

@property
def detected_entities(self):
"""A list of detected entities."""
return [
detection.entity for detection in self._entities if detection.detected]

def initialize_episode_mjcf(self, unused_random_state):
self._entity_geoms = {}
for detection in self._entities:
entity = detection.entity
self._entity_geoms[entity] = entity.mjcf_model.find_all('geom')

def initialize_episode(self, physics, unused_random_state):
self._update_detection(physics)

def after_substep(self, physics, unused_random_state):
self._update_detection(physics)

def _is_in_zone(self, xpos):
return (np.all(self._lower < xpos[:len(self._lower)])
and np.all(self._upper > xpos[:len(self._upper)]))

def _update_detection(self, physics):
previously_detected = self._detected
self._detected = False
for detection in self._entities:
detection.detected = False
for geom in self._entity_geoms[detection.entity]:
if self._is_in_zone(physics.bind(geom).xpos) != self._inverted:
detection.detected = True
self._detected = True
break

if self._detected and not previously_detected:
physics.bind(self._site).rgba = self._detected_rgba
elif previously_detected and not self._detected:
physics.bind(self._site).rgba = self._rgba

def site_pos(self, physics):
return physics.bind(self._site).pos

@property
def activated(self):
return self._detected

@property
def upper(self):
return self._upper

@property
def lower(self):
return self._lower

@property
def mid(self):
return (self._lower + self._upper) / 2.
@@ -0,0 +1,133 @@
# Copyright 2019 The dm_control Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================

"""Tests for dm_control.composer.props.position_detector."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

# Internal dependencies.
from absl.testing import absltest
from absl.testing import parameterized
from dm_control import composer
from dm_control.entities.props import position_detector
from dm_control.entities.props import primitive
import numpy as np


class PositionDetectorTest(parameterized.TestCase):

def setUp(self):
super(PositionDetectorTest, self).setUp()
self.arena = composer.Arena()
self.props = [
primitive.Primitive(geom_type='sphere', size=(0.1,)),
primitive.Primitive(geom_type='sphere', size=(0.1,))
]
for prop in self.props:
self.arena.add_free_entity(prop)
self.task = composer.NullTask(self.arena)

def assertDetected(self, entity, detector):
if not self.inverted:
self.assertIn(entity, detector.detected_entities)
else:
self.assertNotIn(entity, detector.detected_entities)

def assertNotDetected(self, entity, detector):
if not self.inverted:
self.assertNotIn(entity, detector.detected_entities)
else:
self.assertIn(entity, detector.detected_entities)

@parameterized.parameters(False, True)
def test3DDetection(self, inverted):
self.inverted = inverted

detector_pos = np.array([0.3, 0.2, 0.1])
detector_size = np.array([0.1, 0.2, 0.3])
detector = position_detector.PositionDetector(
pos=detector_pos, size=detector_size, inverted=inverted)
detector.register_entities(*self.props)
self.arena.attach(detector)
env = composer.Environment(self.task)

env.reset()
self.assertNotDetected(self.props[0], detector)
self.assertNotDetected(self.props[1], detector)

def initialize_episode(physics, unused_random_state):
for prop in self.props:
prop.set_pose(physics, detector_pos)
self.task.initialize_episode = initialize_episode
env.reset()
self.assertDetected(self.props[0], detector)
self.assertDetected(self.props[1], detector)

self.props[0].set_pose(env.physics, detector_pos - detector_size)
env.step([])
self.assertNotDetected(self.props[0], detector)
self.assertDetected(self.props[1], detector)

self.props[0].set_pose(env.physics, detector_pos - detector_size / 2)
self.props[1].set_pose(env.physics, detector_pos + detector_size * 1.01)
env.step([])
self.assertDetected(self.props[0], detector)
self.assertNotDetected(self.props[1], detector)

@parameterized.parameters(False, True)
def test2DDetection(self, inverted):
self.inverted = inverted

detector_pos = np.array([0.3, 0.2])
detector_size = np.array([0.1, 0.2])
detector = position_detector.PositionDetector(
pos=detector_pos, size=detector_size, inverted=inverted)
detector.register_entities(*self.props)
self.arena.attach(detector)
env = composer.Environment(self.task)

env.reset()
self.assertNotDetected(self.props[0], detector)
self.assertNotDetected(self.props[1], detector)

def initialize_episode(physics, unused_random_state):
# In 2D mode, detection should occur no matter how large |z| is.
self.props[0].set_pose(physics, [detector_pos[0], detector_pos[1], 1e+6])
self.props[1].set_pose(physics, [detector_pos[0], detector_pos[1], -1e+6])
self.task.initialize_episode = initialize_episode
env.reset()
self.assertDetected(self.props[0], detector)
self.assertDetected(self.props[1], detector)

self.props[0].set_pose(
env.physics, [detector_pos[0] - detector_size[0], detector_pos[1], 0])
env.step([])
self.assertNotDetected(self.props[0], detector)
self.assertDetected(self.props[1], detector)

self.props[0].set_pose(
env.physics, [detector_pos[0] - detector_size[0] / 2,
detector_pos[1] + detector_size[1] / 2, 0])
self.props[1].set_pose(
env.physics, [detector_pos[0], detector_pos[1] + detector_size[1], 0])
env.step([])
self.assertDetected(self.props[0], detector)
self.assertNotDetected(self.props[1], detector)


if __name__ == '__main__':
absltest.main()

0 comments on commit 5583074

Please sign in to comment.
You can’t perform that action at this time.