mujodico 3.15.0__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.
- mujodico/examples/__main__.py +107 -0
- mujodico/examples/methods/__init__.py +49 -0
- mujodico/examples/methods/ipopt_contact_implicit_shooting.py +386 -0
- mujodico/examples/methods/ipopt_lifted_multiple_shooting.py +68 -0
- mujodico/examples/methods/ipopt_multiple_shooting.py +48 -0
- mujodico/examples/methods/ipopt_single_shooting.py +134 -0
- mujodico/examples/methods/ippas_lifted_multiple_shooting.py +110 -0
- mujodico/examples/methods/support.py +159 -0
- mujodico/examples/methods/transcription_lifted_multiple_shooting.py +108 -0
- mujodico/examples/methods/transcription_multiple_shooting.py +76 -0
- mujodico/examples/methods/transcription_single_shooting.py +86 -0
- mujodico/examples/tasks/__init__.py +61 -0
- mujodico/examples/tasks/allegro_ball.py +170 -0
- mujodico/examples/tasks/allegro_cube.py +181 -0
- mujodico/examples/tasks/allegro_pen.py +167 -0
- mujodico/examples/tasks/finger_circular_knob.py +132 -0
- mujodico/examples/tasks/fingers_sphere_2d.py +171 -0
- mujodico/examples/tasks/plotly_util.py +100 -0
- mujodico/examples/tasks/push_box_2d.py +150 -0
- mujodico/examples/tasks/push_circular_knob.py +181 -0
- mujodico/examples/tasks/push_multisphere_2d.py +166 -0
- mujodico/examples/tasks/push_sphere_1d.py +122 -0
- mujodico/examples/tasks/push_sphere_2d.py +150 -0
- mujodico/examples/tasks/push_square_knob.py +181 -0
- mujodico/examples/tasks/task.py +176 -0
- mujodico/examples/visualizer.py +60 -0
- mujodico/mjx/__init__.py +79 -0
- mujodico/mjx/_src/__init__.py +14 -0
- mujodico/mjx/_src/bvh.py +39 -0
- mujodico/mjx/_src/collision_convex.py +1117 -0
- mujodico/mjx/_src/collision_driver.py +461 -0
- mujodico/mjx/_src/collision_driver_test.py +1113 -0
- mujodico/mjx/_src/collision_primitive.py +405 -0
- mujodico/mjx/_src/collision_primitive_test.py +182 -0
- mujodico/mjx/_src/collision_sdf.py +275 -0
- mujodico/mjx/_src/collision_types.py +79 -0
- mujodico/mjx/_src/conic_solver.py +766 -0
- mujodico/mjx/_src/conic_solver_test.py +142 -0
- mujodico/mjx/_src/constraint.py +757 -0
- mujodico/mjx/_src/constraint_test.py +178 -0
- mujodico/mjx/_src/dataclasses.py +210 -0
- mujodico/mjx/_src/dataclasses_test.py +117 -0
- mujodico/mjx/_src/derivative.py +100 -0
- mujodico/mjx/_src/forward.py +552 -0
- mujodico/mjx/_src/forward_test.py +313 -0
- mujodico/mjx/_src/inverse.py +112 -0
- mujodico/mjx/_src/inverse_test.py +150 -0
- mujodico/mjx/_src/io.py +1859 -0
- mujodico/mjx/_src/io_test.py +1440 -0
- mujodico/mjx/_src/math.py +394 -0
- mujodico/mjx/_src/math_test.py +202 -0
- mujodico/mjx/_src/mesh.py +337 -0
- mujodico/mjx/_src/mesh_test.py +138 -0
- mujodico/mjx/_src/passive.py +206 -0
- mujodico/mjx/_src/passive_test.py +87 -0
- mujodico/mjx/_src/ray.py +317 -0
- mujodico/mjx/_src/ray_test.py +290 -0
- mujodico/mjx/_src/render.py +82 -0
- mujodico/mjx/_src/render_test.py +132 -0
- mujodico/mjx/_src/render_util.py +150 -0
- mujodico/mjx/_src/render_util_test.py +233 -0
- mujodico/mjx/_src/scan.py +497 -0
- mujodico/mjx/_src/scan_test.py +228 -0
- mujodico/mjx/_src/sensor.py +789 -0
- mujodico/mjx/_src/sensor_test.py +223 -0
- mujodico/mjx/_src/smooth.py +1490 -0
- mujodico/mjx/_src/smooth_test.py +449 -0
- mujodico/mjx/_src/solver.py +610 -0
- mujodico/mjx/_src/solver_conic.py +216 -0
- mujodico/mjx/_src/solver_sap.py +953 -0
- mujodico/mjx/_src/solver_sap_test.py +328 -0
- mujodico/mjx/_src/solver_sap_util.py +143 -0
- mujodico/mjx/_src/solver_test.py +169 -0
- mujodico/mjx/_src/support.py +1153 -0
- mujodico/mjx/_src/support_test.py +810 -0
- mujodico/mjx/_src/test_util.py +488 -0
- mujodico/mjx/_src/types.py +1249 -0
- mujodico/mjx/integration_test/collision_driver_test.py +96 -0
- mujodico/mjx/integration_test/forward_test.py +70 -0
- mujodico/mjx/integration_test/smooth_test.py +95 -0
- mujodico/mjx/test_data/actuator/arm21.xml +37 -0
- mujodico/mjx/test_data/actuator/arm26.xml +118 -0
- mujodico/mjx/test_data/actuator/general_dyntype.xml +18 -0
- mujodico/mjx/test_data/actuator/tendon_force_clamp.xml +55 -0
- mujodico/mjx/test_data/barkour_v0/README.md +25 -0
- mujodico/mjx/test_data/barkour_v0/assets/abduction.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/barkour_v0_mjx.xml +245 -0
- mujodico/mjx/test_data/barkour_v0/assets/body.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/foot.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/handle.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/head.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/head_mount.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/hfield_240_280.png +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/lower_leg_1to1.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/powercable.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/upper_left_1.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/upper_left_2.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/upper_left_3.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/upper_right_1.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/upper_right_2.stl +0 -0
- mujodico/mjx/test_data/barkour_v0/assets/upper_right_3.stl +0 -0
- mujodico/mjx/test_data/constraints.xml +115 -0
- mujodico/mjx/test_data/convex.xml +42 -0
- mujodico/mjx/test_data/humanoid/01_humanoids.xml +150 -0
- mujodico/mjx/test_data/humanoid/02_humanoids.xml +255 -0
- mujodico/mjx/test_data/humanoid/03_humanoids.xml +359 -0
- mujodico/mjx/test_data/humanoid/04_humanoids.xml +463 -0
- mujodico/mjx/test_data/humanoid/05_humanoids.xml +567 -0
- mujodico/mjx/test_data/humanoid/06_humanoids.xml +671 -0
- mujodico/mjx/test_data/humanoid/07_humanoids.xml +775 -0
- mujodico/mjx/test_data/humanoid/08_humanoids.xml +879 -0
- mujodico/mjx/test_data/humanoid/09_humanoids.xml +983 -0
- mujodico/mjx/test_data/humanoid/10_humanoids.xml +1087 -0
- mujodico/mjx/test_data/humanoid/README.md +26 -0
- mujodico/mjx/test_data/humanoid/humanoid.png +0 -0
- mujodico/mjx/test_data/humanoid/humanoid.xml +262 -0
- mujodico/mjx/test_data/meshes/dodecahedron.stl +0 -0
- mujodico/mjx/test_data/meshes/pyramid.stl +0 -0
- mujodico/mjx/test_data/meshes/tetrahedron.stl +0 -0
- mujodico/mjx/test_data/pendula.xml +160 -0
- mujodico/mjx/test_data/quadratic_frictionloss.xml +21 -0
- mujodico/mjx/test_data/ray.xml +18 -0
- mujodico/mjx/test_data/sensor/model.xml +74 -0
- mujodico/mjx/test_data/sensor/sensor.xml +224 -0
- mujodico/mjx/test_data/shadow_hand/README.md +28 -0
- mujodico/mjx/test_data/shadow_hand/assets/f_distal_pst.obj +10772 -0
- mujodico/mjx/test_data/shadow_hand/assets/f_distal_pst_214.obj +326 -0
- mujodico/mjx/test_data/shadow_hand/assets/f_knuckle.obj +1200 -0
- mujodico/mjx/test_data/shadow_hand/assets/f_middle.obj +908 -0
- mujodico/mjx/test_data/shadow_hand/assets/f_proximal.obj +2206 -0
- mujodico/mjx/test_data/shadow_hand/assets/forearm_0.obj +2220 -0
- mujodico/mjx/test_data/shadow_hand/assets/forearm_1.obj +37554 -0
- mujodico/mjx/test_data/shadow_hand/assets/forearm_collision.obj +924 -0
- mujodico/mjx/test_data/shadow_hand/assets/lf_metacarpal.obj +2996 -0
- mujodico/mjx/test_data/shadow_hand/assets/mounting_plate.obj +3936 -0
- mujodico/mjx/test_data/shadow_hand/assets/palm.obj +23598 -0
- mujodico/mjx/test_data/shadow_hand/assets/th_distal_pst.obj +9528 -0
- mujodico/mjx/test_data/shadow_hand/assets/th_distal_pst_190.obj +288 -0
- mujodico/mjx/test_data/shadow_hand/assets/th_middle.obj +2544 -0
- mujodico/mjx/test_data/shadow_hand/assets/th_proximal.obj +840 -0
- mujodico/mjx/test_data/shadow_hand/assets/wrist.obj +3708 -0
- mujodico/mjx/test_data/shadow_hand/right_hand.xml +330 -0
- mujodico/mjx/test_data/shadow_hand/scene_right.xml +31 -0
- mujodico/mjx/test_data/shadow_hand/shadow_hand.png +0 -0
- mujodico/mjx/test_data/tendon/armature.xml +24 -0
- mujodico/mjx/test_data/tendon/fixed.xml +23 -0
- mujodico/mjx/test_data/tendon/fixed_site.xml +39 -0
- mujodico/mjx/test_data/tendon/fixed_site_wrap.xml +58 -0
- mujodico/mjx/test_data/tendon/no_tendon.xml +16 -0
- mujodico/mjx/test_data/tendon/pulley_fixed_site_wrap.xml +103 -0
- mujodico/mjx/test_data/tendon/pulley_site.xml +84 -0
- mujodico/mjx/test_data/tendon/pulley_site_wrap.xml +98 -0
- mujodico/mjx/test_data/tendon/pulley_wrap.xml +78 -0
- mujodico/mjx/test_data/tendon/site.xml +26 -0
- mujodico/mjx/test_data/tendon/site_wrap.xml +54 -0
- mujodico/mjx/test_data/tendon/tendon.xml +66 -0
- mujodico/mjx/test_data/tendon/wrap_sidesite.xml +61 -0
- mujodico/mjx/testspeed.py +96 -0
- mujodico/mjx/viewer.py +166 -0
- mujodico/trajopt/__init__.py +7 -0
- mujodico/trajopt/_src/fatrop_ref/__init__.py +12 -0
- mujodico/trajopt/_src/fatrop_ref/fatrop_v43_runtime_patch.py +318 -0
- mujodico/trajopt/_src/fatrop_ref/linear.py +273 -0
- mujodico/trajopt/_src/fatrop_ref/linear_test.py +52 -0
- mujodico/trajopt/_src/fatrop_ref/restoration_v32.py +959 -0
- mujodico/trajopt/_src/fatrop_ref/solver.py +1474 -0
- mujodico/trajopt/_src/ipopt_multiple_shooting_solver.py +513 -0
- mujodico/trajopt/_src/prox_ddp.py +1049 -0
- mujodico/trajopt/_src/prox_ddp_test.py +122 -0
- mujodico-3.15.0.dist-info/METADATA +116 -0
- mujodico-3.15.0.dist-info/RECORD +174 -0
- mujodico-3.15.0.dist-info/WHEEL +5 -0
- mujodico-3.15.0.dist-info/entry_points.txt +3 -0
- mujodico-3.15.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import csv
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from absl import app
|
|
7
|
+
from absl import flags
|
|
8
|
+
import jax
|
|
9
|
+
from jax import numpy as jp
|
|
10
|
+
import mujoco
|
|
11
|
+
from mujodico import mjx
|
|
12
|
+
from mujodico.examples.methods import get_method_names, parse_method_name, support
|
|
13
|
+
from mujodico.examples.tasks import get_task_names, parse_task_name
|
|
14
|
+
from mujodico.examples.visualizer import Visualizer
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_METHOD = flags.DEFINE_string(
|
|
19
|
+
'method',
|
|
20
|
+
None,
|
|
21
|
+
f'<{"|".join(get_method_names())}>\n(required)',
|
|
22
|
+
required=True,
|
|
23
|
+
)
|
|
24
|
+
_TASK = flags.DEFINE_string(
|
|
25
|
+
'task',
|
|
26
|
+
None,
|
|
27
|
+
f'<{"|".join(get_task_names())}>\n(required)',
|
|
28
|
+
required=True,
|
|
29
|
+
)
|
|
30
|
+
_ITERATIONS = flags.DEFINE_integer(
|
|
31
|
+
'iterations', 500, 'Number of solver iterations', lower_bound=0,
|
|
32
|
+
)
|
|
33
|
+
_TESTS = flags.DEFINE_integer(
|
|
34
|
+
'tests', 1, 'Number of test cases to run.', lower_bound=1,
|
|
35
|
+
)
|
|
36
|
+
_SEED = flags.DEFINE_integer(
|
|
37
|
+
'seed', 0, 'Random seed for tests.',
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _main(argv) -> None:
|
|
42
|
+
if len(argv) > 1:
|
|
43
|
+
raise app.UsageError('Too many command-line arguments.')
|
|
44
|
+
|
|
45
|
+
method_cls = parse_method_name(_METHOD.value)
|
|
46
|
+
task_cls = parse_task_name(_TASK.value)
|
|
47
|
+
|
|
48
|
+
result_dir = Path(__file__).resolve().parent / 'result'
|
|
49
|
+
result_dir.mkdir(exist_ok=True)
|
|
50
|
+
result_file = result_dir / f'{task_cls.__name__}_{method_cls.__name__}.csv'
|
|
51
|
+
print(f'Writing results to "{result_file}"')
|
|
52
|
+
|
|
53
|
+
with open(result_file, 'a', newline='') as f:
|
|
54
|
+
writer = csv.writer(f)
|
|
55
|
+
if f.tell() == 0:
|
|
56
|
+
writer.writerow(['seed', 'goal_error', 'solve_time_sec', 'solver_iters'])
|
|
57
|
+
|
|
58
|
+
visualizer = Visualizer()
|
|
59
|
+
visualizer.run()
|
|
60
|
+
|
|
61
|
+
method = None
|
|
62
|
+
for seed in range(_SEED.value, _SEED.value + _TESTS.value):
|
|
63
|
+
task = task_cls(seed)
|
|
64
|
+
if method is None:
|
|
65
|
+
method = method_cls(task)
|
|
66
|
+
elif hasattr(method, 'update'):
|
|
67
|
+
method.update(task)
|
|
68
|
+
else:
|
|
69
|
+
method = method_cls(task)
|
|
70
|
+
|
|
71
|
+
iters = _ITERATIONS.value
|
|
72
|
+
|
|
73
|
+
tic = time.perf_counter()
|
|
74
|
+
|
|
75
|
+
traj = method.solve(
|
|
76
|
+
traj_init={
|
|
77
|
+
'xs': task.xs_init,
|
|
78
|
+
'us': task.us_init,
|
|
79
|
+
},
|
|
80
|
+
iterations=iters,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
solve_time = time.perf_counter() - tic
|
|
84
|
+
|
|
85
|
+
goal_error = support.state_cost(
|
|
86
|
+
task.Q, task.x_goal, traj['xs'][-1], task.quaternion_w_idxs
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
writer.writerow([seed, goal_error, solve_time, iters])
|
|
90
|
+
f.flush()
|
|
91
|
+
|
|
92
|
+
traj['info'] = f'''
|
|
93
|
+
- Task: {task_cls.__name__}(seed={seed})
|
|
94
|
+
- goal error: {goal_error:.8f}
|
|
95
|
+
- Method: {method_cls.__name__}
|
|
96
|
+
- solve time: {solve_time:.4f} sec
|
|
97
|
+
- iterations: {iters}
|
|
98
|
+
'''
|
|
99
|
+
visualizer.update(task, traj)
|
|
100
|
+
|
|
101
|
+
if _TESTS.value == 1:
|
|
102
|
+
while True:
|
|
103
|
+
time.sleep(3600)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == '__main__':
|
|
107
|
+
app.run(_main)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from mujodico.examples.methods.ipopt_contact_implicit_shooting import IpoptContactImplicitShooting
|
|
2
|
+
from mujodico.examples.methods.ipopt_lifted_multiple_shooting import IpoptLiftedMultipleShooting
|
|
3
|
+
from mujodico.examples.methods.ipopt_multiple_shooting import IpoptMultipleShooting
|
|
4
|
+
from mujodico.trajopt._src.ipopt_multiple_shooting_solver import IpoptMultipleShootingSolver
|
|
5
|
+
from mujodico.examples.methods.ipopt_single_shooting import IpoptSingleShooting
|
|
6
|
+
from mujodico.examples.methods.ippas_lifted_multiple_shooting import IppasLiftedMultipleShooting
|
|
7
|
+
from mujodico.examples.methods.transcription_lifted_multiple_shooting import LiftedMultipleShootingTranscription
|
|
8
|
+
from mujodico.examples.methods.transcription_multiple_shooting import MultipleShootingTranscription
|
|
9
|
+
from mujodico.examples.methods.transcription_single_shooting import SingleShootingTranscription
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'IpoptContactImplicitShooting',
|
|
14
|
+
'IpoptLiftedMultipleShooting',
|
|
15
|
+
'IpoptMultipleShooting',
|
|
16
|
+
'IpoptMultipleShootingSolver',
|
|
17
|
+
'IpoptSingleShooting',
|
|
18
|
+
'IppasLiftedMultipleShooting',
|
|
19
|
+
'LiftedMultipleShootingTranscription',
|
|
20
|
+
'MultipleShootingTranscription',
|
|
21
|
+
'SingleShootingTranscription',
|
|
22
|
+
'get_method_names',
|
|
23
|
+
'parse_method_name',
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_METHODS = [
|
|
28
|
+
IpoptContactImplicitShooting,
|
|
29
|
+
IpoptLiftedMultipleShooting,
|
|
30
|
+
IpoptMultipleShooting,
|
|
31
|
+
IpoptSingleShooting,
|
|
32
|
+
IppasLiftedMultipleShooting,
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_method_names():
|
|
37
|
+
return [method.__name__ for method in _METHODS]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def parse_method_name(name: str):
|
|
41
|
+
name = name.lower().strip().translate(str.maketrans('', '', ' -_'))
|
|
42
|
+
|
|
43
|
+
for method in _METHODS:
|
|
44
|
+
if name == method.__name__.lower():
|
|
45
|
+
return method
|
|
46
|
+
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f'unknown method: value should be one of <{"|".join(get_method_names())}>'
|
|
49
|
+
)
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import importlib
|
|
3
|
+
import logging
|
|
4
|
+
import jax
|
|
5
|
+
import jax.numpy as jp
|
|
6
|
+
from mujodico import mjx
|
|
7
|
+
from mujodico.examples.tasks import Task
|
|
8
|
+
from mujodico.examples.methods import support
|
|
9
|
+
from mujodico.mjx._src import constraint
|
|
10
|
+
from mujodico.mjx._src import solver_conic
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_CONSTITUTIVE_RELATIONS_FUNC = {
|
|
15
|
+
mjx.ContactApproxType.CONIC: solver_conic.constitutive_relations,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IpoptContactImplicitShooting:
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
task: Task,
|
|
24
|
+
):
|
|
25
|
+
try:
|
|
26
|
+
self._cyipopt = importlib.import_module('cyipopt')
|
|
27
|
+
logging.getLogger('cyipopt').setLevel(logging.WARNING)
|
|
28
|
+
except ImportError as e:
|
|
29
|
+
raise ImportError(
|
|
30
|
+
'Optional solver dependency not installed. Install it with:\n\n'
|
|
31
|
+
' pip install cyipopt'
|
|
32
|
+
) from e
|
|
33
|
+
|
|
34
|
+
self.task = task
|
|
35
|
+
|
|
36
|
+
model = task.model
|
|
37
|
+
|
|
38
|
+
contact_approx = mjx.ContactApproxType.CONIC
|
|
39
|
+
|
|
40
|
+
mjx_model = mjx.put_model(
|
|
41
|
+
model,
|
|
42
|
+
impl='jax',
|
|
43
|
+
contact_approximation=contact_approx,
|
|
44
|
+
contact_smoothing=0.0,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
mjx_smooth_model = mjx.put_model(
|
|
48
|
+
model,
|
|
49
|
+
impl=mjx_model.impl,
|
|
50
|
+
contact_approximation=contact_approx,
|
|
51
|
+
contact_smoothing=task.contact_smoothing,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
mjx_data = mjx.make_data(mjx_model, impl=mjx_model.impl)
|
|
55
|
+
|
|
56
|
+
N = task.horizon
|
|
57
|
+
nx = task.nx
|
|
58
|
+
nu = task.nu
|
|
59
|
+
|
|
60
|
+
x_goal = jp.array(task.x_goal)
|
|
61
|
+
Q = jp.array(task.Q)
|
|
62
|
+
Qf = jp.array(task.Qf)
|
|
63
|
+
u_ref = jp.array(task.u_ref)
|
|
64
|
+
R = jp.array(task.R)
|
|
65
|
+
|
|
66
|
+
efc_type = constraint.make_efc_type(mjx_smooth_model)
|
|
67
|
+
if jp.any(efc_type == mjx.ConstraintType.CONTACT_ELLIPTIC):
|
|
68
|
+
raise NotImplementedError(
|
|
69
|
+
'IpoptKKTContactImplicit does not support ellictic cone yet'
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
efc_contact_mask = jp.bitwise_or(
|
|
73
|
+
efc_type == mjx.ConstraintType.CONTACT_FRICTIONLESS,
|
|
74
|
+
efc_type == mjx.ConstraintType.CONTACT_PYRAMIDAL,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
nc = int(jp.count_nonzero(efc_contact_mask))
|
|
78
|
+
self.nc = nc
|
|
79
|
+
|
|
80
|
+
def advance(x, u, cf):
|
|
81
|
+
# TODO(wei-chen): consider using computed force with stop gradient instead of zero
|
|
82
|
+
efc_force = jp.zeros(mjx_data._impl.nefc)
|
|
83
|
+
efc_force = efc_force.at[efc_contact_mask].set(cf)
|
|
84
|
+
|
|
85
|
+
return support.step_given_efc_force(mjx_model, x, u, efc_force)
|
|
86
|
+
|
|
87
|
+
contact_approx = mjx.ContactApproxType._resolve(contact_approx)
|
|
88
|
+
if contact_approx not in _CONSTITUTIVE_RELATIONS_FUNC:
|
|
89
|
+
raise NotImplementedError(
|
|
90
|
+
f'constitutive relation for {contact_approx} contact approximation not implemented.'
|
|
91
|
+
)
|
|
92
|
+
constitutive_relations_func = _CONSTITUTIVE_RELATIONS_FUNC[contact_approx]
|
|
93
|
+
|
|
94
|
+
constitutive_fn, constitutive_lb, constitutive_ub = constitutive_relations_func(mjx_smooth_model, efc_contact_mask)
|
|
95
|
+
|
|
96
|
+
def objective(vars: jax.Array):
|
|
97
|
+
xs, us, cfs = self._unpack(vars)
|
|
98
|
+
|
|
99
|
+
du = us - u_ref
|
|
100
|
+
|
|
101
|
+
cost = (
|
|
102
|
+
jp.sum(jax.vmap(lambda x: support.state_cost(Q, x_goal, x, task.quaternion_w_idxs))(xs[:-1]))
|
|
103
|
+
+ jp.sum((du @ R) * du)
|
|
104
|
+
+ support.state_cost(Qf, x_goal, xs[-1], task.quaternion_w_idxs)
|
|
105
|
+
)
|
|
106
|
+
return cost
|
|
107
|
+
|
|
108
|
+
def constraints(vars: jax.Array):
|
|
109
|
+
xs, us, cfs = self._unpack(vars)
|
|
110
|
+
|
|
111
|
+
def stage(x, u, cf, xnext):
|
|
112
|
+
return jp.concatenate([
|
|
113
|
+
xnext - advance(x, u, cf),
|
|
114
|
+
constitutive_fn(x, u, cf, xnext),
|
|
115
|
+
])
|
|
116
|
+
|
|
117
|
+
per_stage = jax.vmap(stage)(xs[:-1], us, cfs, xs[1:])
|
|
118
|
+
return per_stage.ravel()
|
|
119
|
+
|
|
120
|
+
def constraints_jac_entries(vars: jax.Array):
|
|
121
|
+
xs, us, cfs = self._unpack(vars)
|
|
122
|
+
|
|
123
|
+
def stage_jac(x, u, cf, xnext):
|
|
124
|
+
A, B, C = jax.jacfwd(advance, argnums=(0, 1, 2))(x, u, cf)
|
|
125
|
+
jacobians = jax.jacfwd(constitutive_fn, argnums=(0, 1, 2, 3))(x, u, cf, xnext)
|
|
126
|
+
return jp.concatenate([
|
|
127
|
+
(-A).ravel(),
|
|
128
|
+
(-B).ravel(),
|
|
129
|
+
(-C).ravel(),
|
|
130
|
+
jp.ones(nx),
|
|
131
|
+
jp.concatenate(jacobians, axis=1).ravel(),
|
|
132
|
+
])
|
|
133
|
+
|
|
134
|
+
per_stage = jax.vmap(stage_jac)(xs[:-1], us, cfs, xs[1:])
|
|
135
|
+
return per_stage.ravel()
|
|
136
|
+
|
|
137
|
+
vars_trace = self._pack(
|
|
138
|
+
xs=jp.zeros((N+1, nx)),
|
|
139
|
+
us=jp.zeros((N, nu)),
|
|
140
|
+
cfs=jp.zeros((N, nc)),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
objective_grad = jax.jit(jax.grad(objective)).lower(vars_trace).compile()
|
|
144
|
+
objective = jax.jit(objective).lower(vars_trace).compile()
|
|
145
|
+
objective_hess_structure, objective_hess_entries = self._objective_hessian()
|
|
146
|
+
|
|
147
|
+
constraints = jax.jit(constraints).lower(vars_trace).compile()
|
|
148
|
+
constraints_jac_entries = jax.jit(constraints_jac_entries).lower(vars_trace).compile()
|
|
149
|
+
constraints_jac_structure = self._constraint_jacobian_structure(constitutive_lb.size)
|
|
150
|
+
|
|
151
|
+
self._dynamics_time = 0
|
|
152
|
+
|
|
153
|
+
def timed(fn):
|
|
154
|
+
def wrapped(*args, **kwargs):
|
|
155
|
+
tic = time.perf_counter()
|
|
156
|
+
out = fn(*args, **kwargs)
|
|
157
|
+
self._dynamics_time += time.perf_counter() - tic
|
|
158
|
+
return out
|
|
159
|
+
return wrapped
|
|
160
|
+
|
|
161
|
+
class NLP:
|
|
162
|
+
|
|
163
|
+
@timed
|
|
164
|
+
def objective(self, vars):
|
|
165
|
+
return float(objective(jp.asarray(vars)))
|
|
166
|
+
|
|
167
|
+
@timed
|
|
168
|
+
def gradient(self, vars):
|
|
169
|
+
return np.asarray(objective_grad(jp.asarray(vars)))
|
|
170
|
+
|
|
171
|
+
@timed
|
|
172
|
+
def constraints(self, vars):
|
|
173
|
+
return np.asarray(constraints(jp.asarray(vars)))
|
|
174
|
+
|
|
175
|
+
@timed
|
|
176
|
+
def jacobian(self, vars):
|
|
177
|
+
return np.asarray(constraints_jac_entries(jp.asarray(vars)))
|
|
178
|
+
|
|
179
|
+
@timed
|
|
180
|
+
def jacobianstructure(self):
|
|
181
|
+
return constraints_jac_structure
|
|
182
|
+
|
|
183
|
+
@timed
|
|
184
|
+
def hessian(self, vars, lmbda, objective_factor):
|
|
185
|
+
return objective_hess_entries * objective_factor
|
|
186
|
+
|
|
187
|
+
@timed
|
|
188
|
+
def hessianstructure(self):
|
|
189
|
+
return objective_hess_structure
|
|
190
|
+
|
|
191
|
+
x_lb, x_ub = task.x_bounds
|
|
192
|
+
xs_lb = np.repeat(x_lb[None, :], N + 1, axis=0)
|
|
193
|
+
xs_lb[0] = task.x_0
|
|
194
|
+
xs_ub = np.repeat(x_ub[None, :], N + 1, axis=0)
|
|
195
|
+
xs_ub[0] = task.x_0
|
|
196
|
+
|
|
197
|
+
u_lb, u_ub = task.u_bounds
|
|
198
|
+
us_lb = np.repeat(u_lb[None, :], N, axis=0)
|
|
199
|
+
us_ub = np.repeat(u_ub[None, :], N, axis=0)
|
|
200
|
+
|
|
201
|
+
cfs_lb = np.zeros((N, nc))
|
|
202
|
+
cfs_ub = np.full((N, nc), np.inf)
|
|
203
|
+
|
|
204
|
+
vars_lb = self._pack(xs_lb, us_lb, cfs_lb)
|
|
205
|
+
vars_ub = self._pack(xs_ub, us_ub, cfs_ub)
|
|
206
|
+
|
|
207
|
+
constraints_lb = np.tile(np.concatenate([
|
|
208
|
+
np.zeros(nx),
|
|
209
|
+
np.asarray(constitutive_lb)
|
|
210
|
+
]), N)
|
|
211
|
+
constraints_ub = np.tile(np.concatenate([
|
|
212
|
+
np.zeros(nx),
|
|
213
|
+
np.asarray(constitutive_ub)
|
|
214
|
+
]), N)
|
|
215
|
+
|
|
216
|
+
self.problem = self._cyipopt.Problem(
|
|
217
|
+
n=vars_lb.size,
|
|
218
|
+
m=constraints_lb.size,
|
|
219
|
+
problem_obj=NLP(),
|
|
220
|
+
lb=vars_lb,
|
|
221
|
+
ub=vars_ub,
|
|
222
|
+
cl=constraints_lb,
|
|
223
|
+
cu=constraints_ub,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def solve(
|
|
227
|
+
self,
|
|
228
|
+
traj_init: dict,
|
|
229
|
+
*,
|
|
230
|
+
iterations: int = 500,
|
|
231
|
+
verbose: bool = True,
|
|
232
|
+
):
|
|
233
|
+
xs_init = traj_init['xs']
|
|
234
|
+
us_init = traj_init['us']
|
|
235
|
+
cfs_init = traj_init.get('cfs', np.full((self.task.horizon, self.nc), 1e-6))
|
|
236
|
+
|
|
237
|
+
vars_init = self._pack(xs_init, us_init, cfs_init)
|
|
238
|
+
|
|
239
|
+
self.problem.add_option('max_iter', iterations)
|
|
240
|
+
if not verbose:
|
|
241
|
+
self.problem.add_option('print_level', 0)
|
|
242
|
+
|
|
243
|
+
self._dynamics_time = 0
|
|
244
|
+
tic = time.perf_counter()
|
|
245
|
+
|
|
246
|
+
vars_opt, info = self.problem.solve(vars_init)
|
|
247
|
+
|
|
248
|
+
total_time = time.perf_counter() - tic
|
|
249
|
+
|
|
250
|
+
print(f'''
|
|
251
|
+
Method: {self.__class__.__name__}
|
|
252
|
+
total time: {total_time:.6f} sec
|
|
253
|
+
dynamics time: {self._dynamics_time:.6f} sec / {self._dynamics_time/total_time*100:.2f}%
|
|
254
|
+
ipopt time: {total_time-self._dynamics_time:.6f} sec / {(1-self._dynamics_time/total_time)*100:.2f}%
|
|
255
|
+
''')
|
|
256
|
+
|
|
257
|
+
xs_opt, us_opt, cfs_opt = self._unpack(vars_opt)
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
'xs': xs_opt,
|
|
261
|
+
'us': us_opt,
|
|
262
|
+
'cfs': cfs_opt,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
def _pack(
|
|
266
|
+
self,
|
|
267
|
+
xs: jax.Array | np.ndarray,
|
|
268
|
+
us: jax.Array | np.ndarray,
|
|
269
|
+
cfs: jax.Array | np.ndarray,
|
|
270
|
+
):
|
|
271
|
+
N, nx, nu, nc = self.task.horizon, self.task.nx, self.task.nu, self.nc
|
|
272
|
+
assert xs.shape == (N + 1, nx)
|
|
273
|
+
assert us.shape == (N, nu)
|
|
274
|
+
assert cfs.shape == (N, nc)
|
|
275
|
+
|
|
276
|
+
cp = jp if isinstance(xs, jax.Array) else np
|
|
277
|
+
|
|
278
|
+
return cp.concatenate([
|
|
279
|
+
cp.hstack((xs[:-1], us, cfs)).ravel(),
|
|
280
|
+
xs[-1],
|
|
281
|
+
])
|
|
282
|
+
|
|
283
|
+
def _unpack(self, vars: jax.Array | np.ndarray):
|
|
284
|
+
N, nx, nu, nc = self.task.horizon, self.task.nx, self.task.nu, self.nc
|
|
285
|
+
n_vars = (N + 1) * nx + N * nu + N * nc
|
|
286
|
+
assert vars.shape == (n_vars,)
|
|
287
|
+
|
|
288
|
+
cp = jp if isinstance(vars, jax.Array) else np
|
|
289
|
+
|
|
290
|
+
xucs = vars[:-nx].reshape(N, nx + nu + nc)
|
|
291
|
+
xs = cp.vstack((xucs[:, :nx], vars[-nx:]))
|
|
292
|
+
us = xucs[:, nx : nx + nu]
|
|
293
|
+
cfs = xucs[:, nx + nu :]
|
|
294
|
+
|
|
295
|
+
return xs, us, cfs
|
|
296
|
+
|
|
297
|
+
def _constraint_jacobian_structure(self, n_constitutive_per_stage: int):
|
|
298
|
+
N, nx, nu, nc = self.task.horizon, self.task.nx, self.task.nu, self.nc
|
|
299
|
+
|
|
300
|
+
xx_rows, xx_cols = np.indices((nx, nx))
|
|
301
|
+
xu_rows, xu_cols = np.indices((nx, nu))
|
|
302
|
+
xc_rows, xc_cols = np.indices((nx, nc))
|
|
303
|
+
x_eye = np.arange(nx)
|
|
304
|
+
constitutive_rows, constitutive_cols = np.indices((n_constitutive_per_stage, nx + nu + nc + nx))
|
|
305
|
+
|
|
306
|
+
xx_rows, xx_cols = xx_rows.ravel(), xx_cols.ravel()
|
|
307
|
+
xu_rows, xu_cols = xu_rows.ravel(), xu_cols.ravel()
|
|
308
|
+
xc_rows, xc_cols = xc_rows.ravel(), xc_cols.ravel()
|
|
309
|
+
constitutive_rows = constitutive_rows.ravel()
|
|
310
|
+
constitutive_cols = constitutive_cols.ravel()
|
|
311
|
+
|
|
312
|
+
stage_rows = np.concatenate([
|
|
313
|
+
xx_rows,
|
|
314
|
+
xu_rows,
|
|
315
|
+
xc_rows,
|
|
316
|
+
x_eye,
|
|
317
|
+
nx + constitutive_rows,
|
|
318
|
+
])
|
|
319
|
+
stage_cols = np.concatenate([
|
|
320
|
+
xx_cols,
|
|
321
|
+
nx + xu_cols,
|
|
322
|
+
nx + nu + xc_cols,
|
|
323
|
+
nx + nu + nc + x_eye,
|
|
324
|
+
constitutive_cols,
|
|
325
|
+
])
|
|
326
|
+
|
|
327
|
+
irow = []
|
|
328
|
+
jcol = []
|
|
329
|
+
|
|
330
|
+
for k in range(N):
|
|
331
|
+
row_offset = k * (nx + n_constitutive_per_stage)
|
|
332
|
+
col_offset = k * (nx + nu + nc)
|
|
333
|
+
|
|
334
|
+
irow.append(stage_rows + row_offset)
|
|
335
|
+
jcol.append(stage_cols + col_offset)
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
np.concatenate(irow).astype(np.int32),
|
|
339
|
+
np.concatenate(jcol).astype(np.int32),
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def _objective_hessian(self):
|
|
343
|
+
N, nx, nu, nc = self.task.horizon, self.task.nx, self.task.nu, self.nc
|
|
344
|
+
|
|
345
|
+
Q = 2 * np.asarray(self.task.Q)
|
|
346
|
+
Qf = 2 * np.asarray(self.task.Qf)
|
|
347
|
+
R = 2 * np.asarray(self.task.R)
|
|
348
|
+
|
|
349
|
+
q_i, q_j = np.tril_indices(nx)
|
|
350
|
+
qf_i, qf_j = np.tril_indices(nx)
|
|
351
|
+
r_i, r_j = np.tril_indices(nu)
|
|
352
|
+
|
|
353
|
+
q_entries = Q[q_i, q_j]
|
|
354
|
+
qf_entries = Qf[qf_i, qf_j]
|
|
355
|
+
r_entries = R[r_i, r_j]
|
|
356
|
+
|
|
357
|
+
irow = []
|
|
358
|
+
jcol = []
|
|
359
|
+
entries = []
|
|
360
|
+
|
|
361
|
+
offset = 0
|
|
362
|
+
for _ in range(N):
|
|
363
|
+
irow.append(q_i + offset)
|
|
364
|
+
jcol.append(q_j + offset)
|
|
365
|
+
entries.append(q_entries)
|
|
366
|
+
offset += nx
|
|
367
|
+
|
|
368
|
+
irow.append(r_i + offset)
|
|
369
|
+
jcol.append(r_j + offset)
|
|
370
|
+
entries.append(r_entries)
|
|
371
|
+
offset += nu
|
|
372
|
+
offset += nc
|
|
373
|
+
|
|
374
|
+
irow.append(qf_i + offset)
|
|
375
|
+
jcol.append(qf_j + offset)
|
|
376
|
+
entries.append(qf_entries)
|
|
377
|
+
|
|
378
|
+
structure = (
|
|
379
|
+
np.concatenate(irow).astype(np.int32),
|
|
380
|
+
np.concatenate(jcol).astype(np.int32),
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
structure,
|
|
385
|
+
np.concatenate(entries),
|
|
386
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import jax.numpy as jp
|
|
3
|
+
from mujodico import trajopt
|
|
4
|
+
from mujodico.examples import methods
|
|
5
|
+
from mujodico.examples.tasks import Task
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IpoptLiftedMultipleShooting:
|
|
9
|
+
|
|
10
|
+
def __init__(self, task: Task):
|
|
11
|
+
self.transcription = methods.LiftedMultipleShootingTranscription(task)
|
|
12
|
+
|
|
13
|
+
trans = self.transcription
|
|
14
|
+
self.solver = trajopt.IpoptMultipleShootingSolver(
|
|
15
|
+
N=trans.N,
|
|
16
|
+
nx=trans.nx,
|
|
17
|
+
nu=trans.nw,
|
|
18
|
+
stage_cost=trans.stage_cost,
|
|
19
|
+
terminal_cost=trans.terminal_cost,
|
|
20
|
+
initial_equality_constraint=lambda x, params: x - params.x_0,
|
|
21
|
+
local_equality_constraint=lambda x, w, xnext, params: (
|
|
22
|
+
jp.concatenate([
|
|
23
|
+
xnext - trans.dynamics(x, w, params),
|
|
24
|
+
trans.stage_equality_constraint(x, w, params),
|
|
25
|
+
])
|
|
26
|
+
),
|
|
27
|
+
local_inequality_constraint=lambda x, w, xnext, params: (
|
|
28
|
+
trans.stage_inequality_constraint(x, w, params)
|
|
29
|
+
),
|
|
30
|
+
x_bounds=trans.x_bounds,
|
|
31
|
+
u_bounds=trans.w_bounds,
|
|
32
|
+
params_trace=self.transcription.params,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def update(self, task: Task):
|
|
36
|
+
self.transcription.update(task)
|
|
37
|
+
|
|
38
|
+
def solve(self, traj_init: dict, *, iterations: int = 500, verbose: bool = True):
|
|
39
|
+
xs_init = traj_init['xs']
|
|
40
|
+
us_init = traj_init['us']
|
|
41
|
+
N = self.transcription.N
|
|
42
|
+
nw = self.transcription.nw
|
|
43
|
+
nu = us_init.shape[-1]
|
|
44
|
+
cfs_init = traj_init.get('cfs', np.zeros((N, nw - nu)))
|
|
45
|
+
ws_init = np.hstack([us_init, cfs_init])
|
|
46
|
+
|
|
47
|
+
xs_opt, ws_opt, info = self.solver.solve(
|
|
48
|
+
self.transcription.params,
|
|
49
|
+
xs_init,
|
|
50
|
+
ws_init,
|
|
51
|
+
max_iter=iterations,
|
|
52
|
+
print_level=5 if verbose else 0,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
total_time = info['total_time_sec']
|
|
56
|
+
eval_time = info['eval_time_sec']
|
|
57
|
+
print(f'''
|
|
58
|
+
Method: {self.__class__.__name__}
|
|
59
|
+
total time: {total_time:.6f} sec
|
|
60
|
+
dynamics time: {eval_time:.6f} sec / {eval_time/total_time*100:.2f}%
|
|
61
|
+
ipopt time: {total_time-eval_time:.6f} sec / {(1-eval_time/total_time)*100:.2f}%
|
|
62
|
+
''')
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
'xs': xs_opt,
|
|
66
|
+
'us': ws_opt[:, :nu],
|
|
67
|
+
'cfs': ws_opt[:, nu:],
|
|
68
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from mujodico import trajopt
|
|
2
|
+
from mujodico.examples import methods
|
|
3
|
+
from mujodico.examples.tasks import Task
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IpoptMultipleShooting:
|
|
7
|
+
|
|
8
|
+
def __init__(self, task: Task):
|
|
9
|
+
self.transcription = methods.MultipleShootingTranscription(task)
|
|
10
|
+
|
|
11
|
+
trans = self.transcription
|
|
12
|
+
self.solver = trajopt.IpoptMultipleShootingSolver(
|
|
13
|
+
trans.N,
|
|
14
|
+
trans.nx,
|
|
15
|
+
trans.nu,
|
|
16
|
+
stage_cost=trans.stage_cost,
|
|
17
|
+
terminal_cost=trans.terminal_cost,
|
|
18
|
+
initial_equality_constraint=lambda x, params: x - params.x_0,
|
|
19
|
+
local_equality_constraint=lambda x, u, xnext, params: (
|
|
20
|
+
xnext - trans.dynamics(x, u, params)
|
|
21
|
+
),
|
|
22
|
+
x_bounds=trans.x_bounds,
|
|
23
|
+
u_bounds=trans.u_bounds,
|
|
24
|
+
params_trace=self.transcription.params,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def update(self, task: Task):
|
|
28
|
+
self.transcription.update(task)
|
|
29
|
+
|
|
30
|
+
def solve(self, traj_init: dict, *, iterations: int = 500, verbose: bool = True):
|
|
31
|
+
xs_opt, us_opt, info = self.solver.solve(
|
|
32
|
+
self.transcription.params,
|
|
33
|
+
traj_init['xs'],
|
|
34
|
+
traj_init['us'],
|
|
35
|
+
max_iter=iterations,
|
|
36
|
+
print_level=5 if verbose else 0,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
total_time = info['total_time_sec']
|
|
40
|
+
eval_time = info['eval_time_sec']
|
|
41
|
+
print(f'''
|
|
42
|
+
Method: {self.__class__.__name__}
|
|
43
|
+
total time: {total_time:.6f} sec
|
|
44
|
+
dynamics time: {eval_time:.6f} sec / {eval_time/total_time*100:.2f}%
|
|
45
|
+
ipopt time: {total_time-eval_time:.6f} sec / {(1-eval_time/total_time)*100:.2f}%
|
|
46
|
+
''')
|
|
47
|
+
|
|
48
|
+
return {'xs': xs_opt, 'us': us_opt}
|