示例#1
0
    def convert_key_item(self, key_item):
        """Converts a named indexing expression to a numpy-friendly index."""

        _validate_key_item(key_item)

        if isinstance(key_item, str):
            key_item = self._names_to_offsets[util.to_native_string(key_item)]

        elif isinstance(key_item, (list, np.ndarray)):
            # Cast lists to numpy arrays.
            key_item = np.array(key_item, copy=False)
            original_shape = key_item.shape

            # We assume that either all or none of the items in the array are strings
            # representing names. If there is a mix, we will let NumPy throw an error
            # when trying to index with the returned item.
            if isinstance(key_item.flat[0], str):
                key_item = np.array([
                    self._names_to_offsets[util.to_native_string(k)]
                    for k in key_item.flat
                ])
                # Ensure the output shape is the same as that of the input.
                key_item.shape = original_shape

        return key_item
def _load_xml(filename, vfs_or_none):
    """Invokes `mj_loadXML` with logging/error handling."""
    error_buf = ctypes.create_string_buffer(_ERROR_BUFSIZE)
    model_ptr = mjlib.mj_loadXML(util.to_binary_string(filename), vfs_or_none,
                                 error_buf, _ERROR_BUFSIZE)
    if not model_ptr:
        raise Error(util.to_native_string(error_buf.value))
    elif error_buf.value:
        logging.warning(util.to_native_string(error_buf.value))

    # Free resources when the ctypes pointer is garbage collected.
    _create_finalizer(model_ptr, mjlib.mj_deleteModel)

    return model_ptr
    def testActuatorReordering(self):
        def make_model_with_mixed_actuators(name):
            actuators = []
            root = mjcf.RootElement(model=name)
            body = root.worldbody.add('body')
            body.add('geom', type='sphere', size=[0.1])
            slider = body.add('joint', type='slide', name='slide_joint')
            # Third-order `general` actuator.
            actuators.append(
                root.actuator.add('general',
                                  dyntype='integrator',
                                  biastype='affine',
                                  dynprm=[1, 0, 0],
                                  joint=slider,
                                  name='general_act'))
            # Cylinder actuators are also third-order.
            actuators.append(
                root.actuator.add('cylinder',
                                  joint=slider,
                                  name='cylinder_act'))
            # A second-order actuator, added after the third-order actuators.
            actuators.append(
                root.actuator.add('velocity',
                                  joint=slider,
                                  name='velocity_act'))
            return root, actuators

        child_1, actuators_1 = make_model_with_mixed_actuators(name='child_1')
        child_2, actuators_2 = make_model_with_mixed_actuators(name='child_2')
        child_3, actuators_3 = make_model_with_mixed_actuators(name='child_3')
        parent = mjcf.RootElement()
        parent.attach(child_1)
        parent.attach(child_2)
        child_2.attach(child_3)

        # Check that the generated XML contains all of the actuators that we expect
        # it to have.
        expected_xml_strings = [
            actuator.to_xml_string(prefix_root=parent.namescope)
            for actuator in actuators_1 + actuators_2 + actuators_3
        ]
        xml_strings = [
            util.to_native_string(etree.tostring(node, pretty_print=True))
            for node in parent.to_xml().find('actuator').getchildren()
        ]
        self.assertSameElements(expected_xml_strings, xml_strings)

        # MuJoCo requires that all 3rd-order actuators (i.e. those with internal
        # dynamics) come after all 2nd-order actuators in the XML. Attempting to
        # compile this model will result in an error unless PyMJCF internally
        # reorders the actuators so that the 3rd-order actuator comes last in the
        # generated XML.
        _ = mjcf.Physics.from_mjcf_model(child_1)

        # Actuator re-ordering should also work in cases where there are multiple
        # attached submodels with mixed 2nd- and 3rd-order actuators.
        _ = mjcf.Physics.from_mjcf_model(parent)
示例#4
0
def _get_size_name_to_element_names(model):
  """Returns a dict that maps size names to element names.

  Args:
    model: An instance of `mjbindings.mjModelWrapper`.

  Returns:
    A `dict` mapping from a size name (e.g. `'nbody'`) to a list of element
    names.
  """

  names = model.names[:model.nnames]
  size_name_to_element_names = {}

  for field_name in dir(model):
    if not _is_name_pointer(field_name):
      continue

    # Get addresses of element names in `model.names` array, e.g.
    # field name: `name_nbodyadr` and name_addresses: `[86, 92, 101]`, and skip
    # when there are no elements for this type in the model.
    name_addresses = getattr(model, field_name).ravel()
    if not name_addresses.size:
      continue

    # Get the element names.
    element_names = []
    for start_index in name_addresses:
      name = names[start_index:names.find(b'\0', start_index)]
      element_names.append(util.to_native_string(name))

    # String identifier for the size of the first dimension, e.g. 'nbody'.
    size_name = _get_size_name(field_name)

    size_name_to_element_names[size_name] = element_names

  # Add custom element names for certain columns.
  for size_name, element_names in six.iteritems(_COLUMN_NAMES):
    size_name_to_element_names[size_name] = element_names

  # "Ragged" axes inherit their element names from other "non-ragged" axes.
  # For example, the element names for "nv" axis come from "njnt".
  for size_name, address_field_name in six.iteritems(_RAGGED_ADDRS):
    donor = 'n' + address_field_name.split('_')[0]
    if donor in size_name_to_element_names:
      size_name_to_element_names[size_name] = size_name_to_element_names[donor]

  # Mocap bodies are a special subset of bodies.
  mocap_body_names = [None] * model.nmocap
  for body_id, body_name in enumerate(size_name_to_element_names['nbody']):
    body_mocapid = model.body_mocapid[body_id]
    if body_mocapid != -1:
      mocap_body_names[body_mocapid] = body_name
  assert None not in mocap_body_names
  size_name_to_element_names['nmocap'] = mocap_body_names

  return size_name_to_element_names
示例#5
0
 def to_xml_string(self, prefix_root=None):  # pylint: disable=unused-argument
   if self._value is None:
     return None
   else:
     out = six.BytesIO()
     # 17 decimal digits is sufficient to represent a double float without loss
     # of precision.
     # https://en.wikipedia.org/wiki/IEEE_754#Character_representation
     np.savetxt(out, self._value, fmt='%.17g', newline=' ')
     return util.to_native_string(out.getvalue())[:-1]  # Strip trailing space.
示例#6
0
    def convert_key_item(self, key):
        """Converts a named indexing expression to a numpy-friendly index."""

        _validate_key_item(key)

        if isinstance(key, str):
            key = self._names_to_slices[util.to_native_string(key)]

        elif isinstance(key, (list, np.ndarray)):
            # We assume that either all or none of the items in the sequence are
            # strings representing names. If there is a mix, we will let NumPy throw
            # an error when trying to index with the returned key.
            if isinstance(key[0], str):
                new_key = []
                for k in key:
                    idx = self._names_to_indices[util.to_native_string(k)]
                    if isinstance(idx, int):
                        new_key.append(idx)
                    else:
                        new_key.extend(idx)
                key = new_key

        return key
    def test_export_model(self, xml_path, model_name):
        """Save processed MJCF model."""
        out_dir = self.create_tempdir().full_path
        mjcf_model = mjcf.from_path(xml_path)
        mjcf.export_with_assets_as_zip(mjcf_model,
                                       out_dir=out_dir,
                                       model_name=model_name)

        # Read the .zip file in the output directory.
        # Check that the only directory is named `model_name`/, and put all the
        # contents under any directory in a dict a directory in a dict.
        zip_file_contents = {}
        zip_filename = os.path.join(out_dir, (model_name + '.zip'))
        self.assertTrue(zipfile.is_zipfile(zip_filename))
        with zipfile.ZipFile(zip_filename, 'r') as zip_file:
            for zip_info in zip_file.infolist():
                # Note: zip_info.is_dir() is not Python 2 compatible, but directories
                # inside a ZipFile are guaranteed to end with '/'.
                if not zip_info.filename.endswith(os.path.sep):
                    with zip_file.open(zip_info.filename) as f:
                        zip_file_contents[zip_info.filename] = f.read()
                else:
                    self.assertEqual(os.path.join(model_name),
                                     zip_info.filename)

        # Check that the output directory contains an XML file of the correct name.
        xml_filename = os.path.join(model_name, model_name) + '.xml'
        self.assertIn(xml_filename, zip_file_contents)

        # Check that its contents match the output of `mjcf_model.to_xml_string()`.
        xml_contents = util.to_native_string(
            zip_file_contents.pop(xml_filename))
        expected_xml_contents = mjcf_model.to_xml_string()
        self.assertEqual(xml_contents, expected_xml_contents)

        # Check that the other files in the directory match the contents of the
        # model's `assets` dict.
        assets = mjcf_model.get_assets()
        for asset_name, asset_contents in assets.items():
            self.assertEqual(
                asset_contents,
                zip_file_contents[os.path.join(model_name, asset_name)])
示例#8
0
  def id2name(self, object_id, object_type):
    """Returns the name associated with a MuJoCo object ID, if there is one.

    Args:
      object_id: Integer ID.
      object_type: The type of the object. Can be either a lowercase string
        (e.g. 'body', 'geom') or an `mjtObj` enum value.

    Returns:
      A string containing the object name, or an empty string if the object ID
      either doesn't exist or has no name.

    Raises:
      Error: If `object_type` is not a valid MuJoCo object type.
    """
    if not isinstance(object_type, int):
      object_type = _str2type(object_type)
    name_ptr = mjlib.mj_id2name(self.ptr, object_type, object_id)
    if not name_ptr:
      return ""
    return util.to_native_string(ctypes.string_at(name_ptr))
    def test_export_model(self, xml_path, out_xml_name):
        """Save processed MJCF model."""
        out_dir = self.create_tempdir().full_path
        mjcf_model = mjcf.from_path(xml_path)
        mjcf.export_with_assets(mjcf_model,
                                out_dir=out_dir,
                                out_file_name=out_xml_name)

        # Read the files in the output directory and put their contents in a dict.
        out_dir_contents = {}
        for filename in os.listdir(out_dir):
            with open(os.path.join(out_dir, filename), 'rb') as f:
                out_dir_contents[filename] = f.read()

        # Check that the output directory contains an XML file of the correct name.
        self.assertIn(out_xml_name, out_dir_contents)

        # Check that its contents match the output of `mjcf_model.to_xml_string()`.
        xml_contents = util.to_native_string(
            out_dir_contents.pop(out_xml_name))
        expected_xml_contents = mjcf_model.to_xml_string()
        self.assertEqual(xml_contents, expected_xml_contents)

        # Check that the other files in the directory match the contents of the
        # model's `assets` dict.
        assets = mjcf_model.get_assets()
        self.assertDictEqual(out_dir_contents, assets)

        # Check that we can construct an MjModel instance using the path to the
        # output file.
        from_exported = wrapper.MjModel.from_xml_path(
            os.path.join(out_dir, out_xml_name))

        # Check that this model is identical to one constructed directly from
        # `mjcf_model`.
        from_mjcf = wrapper.MjModel.from_xml_string(expected_xml_contents,
                                                    assets=assets)
        self.assertEqual(from_exported.to_bytes(), from_mjcf.to_bytes())
示例#10
0
    def __repr__(self):
        """Returns a pretty string representation of the `FieldIndexer`."""
        def get_name_arr_and_len(dim_idx):
            """Returns a string array of element names and the max name length."""
            axis = self._axes[dim_idx]
            size = self._field.shape[dim_idx]
            try:
                name_len = max(len(name) for name in axis.names)
                name_arr = np.zeros(size, dtype='S{}'.format(name_len))
                for name in axis.names:
                    if name:
                        # Use the `Axis` object to convert the name into a numpy index, then
                        # use this index to write into name_arr.
                        name_arr[axis.convert_key_item(name)] = name
            except AttributeError:
                name_arr = np.zeros(
                    size, dtype='S0')  # An array of zero-length strings
                name_len = 0
            return name_arr, name_len

        row_name_arr, row_name_len = get_name_arr_and_len(0)
        if self._field.ndim > 1:
            col_name_arr, col_name_len = get_name_arr_and_len(1)
        else:
            col_name_arr, col_name_len = np.zeros(1, dtype='S0'), 0

        idx_len = int(np.log10(max(self._field.shape[0], 1))) + 1

        cls_template = '{class_name:}({field_name:}):'
        col_template = '{padding:}{col_names:}'
        row_template = '{idx:{idx_len:}} {row_name:>{row_name_len:}} {row_vals:}'

        lines = []

        # Write the class name and field name.
        lines.append(
            cls_template.format(class_name=self.__class__.__name__,
                                field_name=self._field_name))

        # Write a header line containing the column names (if there are any).
        if col_name_len:
            col_width = max(col_name_len, 9) + 1
            extra_indent = 4
            padding = ' ' * (idx_len + row_name_len + extra_indent)
            col_names = ''.join('{name:<{width:}}'.format(
                name=util.to_native_string(name), width=col_width)
                                for name in col_name_arr)
            lines.append(
                col_template.format(padding=padding, col_names=col_names))

        # Write the row names (if there are any) and the formatted array values.
        if not self._field.shape[0]:
            lines.append('(empty)')
        else:
            for idx, row in enumerate(self._field):
                row_vals = np.array2string(
                    np.atleast_1d(row),
                    suppress_small=True,
                    formatter={'float_kind': '{: < 9.3g}'.format})
                lines.append(
                    row_template.format(idx=idx,
                                        idx_len=idx_len,
                                        row_name=util.to_native_string(
                                            row_name_arr[idx]),
                                        row_name_len=row_name_len,
                                        row_vals=row_vals))
        return '\n'.join(lines)
示例#11
0
def _error_callback(message):
  logging.fatal(util.to_native_string(message))
示例#12
0
def _warning_callback(message):
  logging.warning(util.to_native_string(message))
示例#13
0
 def name(self):
   """Returns the name of the model."""
   # The model name is the first null-terminated string in the `names` buffer.
   return util.to_native_string(
       ctypes.string_at(ctypes.addressof(self.names.contents)))