Skip to content

Conversation

@machinaut
Copy link
Contributor

This introduces a couple things together:

  • A way to just-in-time compile small C functions that can be passed to mujoco as callbacks
  • A mechanism of naming fields in userdata and accessing them from the function
  • A substep callback function that can be set to be called every substep

Differences between this an our current user-defined-dynamics

  • Less flexible; cannot access global python state or any python objects or methods
  • Faster, parallelizeable; because it doesn't require the GIL, we can do this over MjSimPools

sim.step() # should increment userdata[0] 7 times
self.assertEqual(sim.data.userdata[0], 7)

def test_sum_ctrl(self):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test (test_sum_ctrl) is probably the best standalone example of what it looks like to use this.

if not os.environ.get('MUJOCO_PY_DEBUG_FN_BUILDER', False):
for f in glob.glob(name + '*'):
os.remove(f)
return module.lib.generic_fn
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called "generic" because it's signature matches the mjfGeneric type defined here:

# typedef void (*mjfGeneric)(const mjModel* m, mjData* d)

(and also in the mjdata header)

Copy link
Contributor

@wojzaremba wojzaremba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows version is breaking.

It's fine to disable these tests for windows. Something like that:
@pytest.mark.skipif(sys.platform.startswith("win"), reason="This test fails on windows.")

Finished generating code
FAILED
___________________________ TestSubstep.test_hello ____________________________
self = <mujoco_py.tests.test_substep.TestSubstep testMethod=test_hello>
def test_hello(self):
sim = MjSim(load_model_from_xml(XML.format(nuserdata=0)),

              substep_udd_fn=HELLO_FN)

mujoco_py\tests\test_substep.py:42:


mujoco_py\mjsim.pyx:92: in mujoco_py.cymj.MjSim.cinit
self.set_substep_udd_fn(substep_udd_fn)
mujoco_py\mjsim.pyx:210: in mujoco_py.cymj.MjSim.set_substep_udd_fn
substep_udd_fn = build_generic_fn(substep_udd_fn,
mujoco_py\builder.py:337: in build_generic_fn
fixed_library_path = manually_link_libraries(mjpro_path, library_path)
mujoco_py\builder.py:129: in manually_link_libraries
tmp_final_cext_dll_path])
c:\miniconda35-x64\envs\test-environment\lib\subprocess.py:266: in check_call
retcode = call(*popenargs, **kwargs)
c:\miniconda35-x64\envs\test-environment\lib\subprocess.py:247: in call
with Popen(*popenargs, **kwargs) as p:
c:\miniconda35-x64\envs\test-environment\lib\subprocess.py:676: in init
restore_signals, start_new_session)


self = <subprocess.Popen object at 0x000000A3BECF7C50>
args = 'install_name_tool -change @executable_path/libmujoco150.dylib C:\Users\appveyor\.mujoco\mjpro150\bin/libmujoco150.dylib C:\projects\mujoco-py\_generic_fn_vhxddhpmouwlbxy.cp35-win_amd64_final.pyd~'
executable = None, preexec_fn = None, close_fds = True, pass_fds = ()
cwd = None, env = None
startupinfo = <subprocess.STARTUPINFO object at 0x000000A3BECF7588>
creationflags = 0, shell = False, p2cread = -1, p2cwrite = -1, c2pread = -1
c2pwrite = -1, errread = -1, errwrite = -1, unused_restore_signals = True
unused_start_new_session = False
def _execute_child(self, args, executable, preexec_fn, close_fds,
pass_fds, cwd, env,
startupinfo, creationflags, shell,
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite,
unused_restore_signals, unused_start_new_session):
"""Execute program (MS Windows version)"""

    assert not pass_fds, "pass_fds not supported on Windows."

    if not isinstance(args, str):
        args = list2cmdline(args)

    # Process startup details
    if startupinfo is None:
        startupinfo = STARTUPINFO()
    if -1 not in (p2cread, c2pwrite, errwrite):
        startupinfo.dwFlags |= _winapi.STARTF_USESTDHANDLES
        startupinfo.hStdInput = p2cread
        startupinfo.hStdOutput = c2pwrite
        startupinfo.hStdError = errwrite

    if shell:
        startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW
        startupinfo.wShowWindow = _winapi.SW_HIDE
        comspec = os.environ.get("COMSPEC", "cmd.exe")
        args = '{} /c "{}"'.format (comspec, args)

    # Start the process
    try:
        hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
                                 # no special security
                                 None, None,
                                 int(not close_fds),
                                 creationflags,
                                 env,
                                 cwd,
                               startupinfo)

E FileNotFoundError: [WinError 2] The system cannot find the file specified
c:\miniconda35-x64\envs\test-environment\lib\subprocess.py:957: FileNotFoundError
mujoco_py/tests/test_substep.py::TestSubstep::test_increment generating ._generic_fn_ucevgvcdpgaisfz.c

'''
if substep_udd_fn is None:
self._substep_udd_fn = NULL
elif isinstance(substep_udd_fn, int):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we ensure that "substep_udd_fn" can be only string, or NULL.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit safer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason to allow ints is this lets us re-use these functions between sims (in a single python process).

sim1 = MjSim(...., substep_udd_fn=...)
sim2 = MjSim(...., substep_udd_fn=sim1.substep_udd_fn)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test which shows this

substep_udd_fn=INCREMENT_FN,
substep_udd_fields=['foo', 'bar'])

def test_penetrations(self):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc: @wojzaremba here's the test you asked for

next ``udd_state`` after applying the user-defined dynamics. This is
useful e.g. for reward functions that operate over functions of historical
state.
substep_udd_fn : str or int or None
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is a substantially different implementation from udd, I would prefer to give it a different name. For example, what about substep_userdata_fn?

@machinaut
Copy link
Contributor Author

Splitting this into several changes with feedback

@machinaut machinaut closed this Oct 12, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants