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.
Files changed (174) hide show
  1. mujodico/examples/__main__.py +107 -0
  2. mujodico/examples/methods/__init__.py +49 -0
  3. mujodico/examples/methods/ipopt_contact_implicit_shooting.py +386 -0
  4. mujodico/examples/methods/ipopt_lifted_multiple_shooting.py +68 -0
  5. mujodico/examples/methods/ipopt_multiple_shooting.py +48 -0
  6. mujodico/examples/methods/ipopt_single_shooting.py +134 -0
  7. mujodico/examples/methods/ippas_lifted_multiple_shooting.py +110 -0
  8. mujodico/examples/methods/support.py +159 -0
  9. mujodico/examples/methods/transcription_lifted_multiple_shooting.py +108 -0
  10. mujodico/examples/methods/transcription_multiple_shooting.py +76 -0
  11. mujodico/examples/methods/transcription_single_shooting.py +86 -0
  12. mujodico/examples/tasks/__init__.py +61 -0
  13. mujodico/examples/tasks/allegro_ball.py +170 -0
  14. mujodico/examples/tasks/allegro_cube.py +181 -0
  15. mujodico/examples/tasks/allegro_pen.py +167 -0
  16. mujodico/examples/tasks/finger_circular_knob.py +132 -0
  17. mujodico/examples/tasks/fingers_sphere_2d.py +171 -0
  18. mujodico/examples/tasks/plotly_util.py +100 -0
  19. mujodico/examples/tasks/push_box_2d.py +150 -0
  20. mujodico/examples/tasks/push_circular_knob.py +181 -0
  21. mujodico/examples/tasks/push_multisphere_2d.py +166 -0
  22. mujodico/examples/tasks/push_sphere_1d.py +122 -0
  23. mujodico/examples/tasks/push_sphere_2d.py +150 -0
  24. mujodico/examples/tasks/push_square_knob.py +181 -0
  25. mujodico/examples/tasks/task.py +176 -0
  26. mujodico/examples/visualizer.py +60 -0
  27. mujodico/mjx/__init__.py +79 -0
  28. mujodico/mjx/_src/__init__.py +14 -0
  29. mujodico/mjx/_src/bvh.py +39 -0
  30. mujodico/mjx/_src/collision_convex.py +1117 -0
  31. mujodico/mjx/_src/collision_driver.py +461 -0
  32. mujodico/mjx/_src/collision_driver_test.py +1113 -0
  33. mujodico/mjx/_src/collision_primitive.py +405 -0
  34. mujodico/mjx/_src/collision_primitive_test.py +182 -0
  35. mujodico/mjx/_src/collision_sdf.py +275 -0
  36. mujodico/mjx/_src/collision_types.py +79 -0
  37. mujodico/mjx/_src/conic_solver.py +766 -0
  38. mujodico/mjx/_src/conic_solver_test.py +142 -0
  39. mujodico/mjx/_src/constraint.py +757 -0
  40. mujodico/mjx/_src/constraint_test.py +178 -0
  41. mujodico/mjx/_src/dataclasses.py +210 -0
  42. mujodico/mjx/_src/dataclasses_test.py +117 -0
  43. mujodico/mjx/_src/derivative.py +100 -0
  44. mujodico/mjx/_src/forward.py +552 -0
  45. mujodico/mjx/_src/forward_test.py +313 -0
  46. mujodico/mjx/_src/inverse.py +112 -0
  47. mujodico/mjx/_src/inverse_test.py +150 -0
  48. mujodico/mjx/_src/io.py +1859 -0
  49. mujodico/mjx/_src/io_test.py +1440 -0
  50. mujodico/mjx/_src/math.py +394 -0
  51. mujodico/mjx/_src/math_test.py +202 -0
  52. mujodico/mjx/_src/mesh.py +337 -0
  53. mujodico/mjx/_src/mesh_test.py +138 -0
  54. mujodico/mjx/_src/passive.py +206 -0
  55. mujodico/mjx/_src/passive_test.py +87 -0
  56. mujodico/mjx/_src/ray.py +317 -0
  57. mujodico/mjx/_src/ray_test.py +290 -0
  58. mujodico/mjx/_src/render.py +82 -0
  59. mujodico/mjx/_src/render_test.py +132 -0
  60. mujodico/mjx/_src/render_util.py +150 -0
  61. mujodico/mjx/_src/render_util_test.py +233 -0
  62. mujodico/mjx/_src/scan.py +497 -0
  63. mujodico/mjx/_src/scan_test.py +228 -0
  64. mujodico/mjx/_src/sensor.py +789 -0
  65. mujodico/mjx/_src/sensor_test.py +223 -0
  66. mujodico/mjx/_src/smooth.py +1490 -0
  67. mujodico/mjx/_src/smooth_test.py +449 -0
  68. mujodico/mjx/_src/solver.py +610 -0
  69. mujodico/mjx/_src/solver_conic.py +216 -0
  70. mujodico/mjx/_src/solver_sap.py +953 -0
  71. mujodico/mjx/_src/solver_sap_test.py +328 -0
  72. mujodico/mjx/_src/solver_sap_util.py +143 -0
  73. mujodico/mjx/_src/solver_test.py +169 -0
  74. mujodico/mjx/_src/support.py +1153 -0
  75. mujodico/mjx/_src/support_test.py +810 -0
  76. mujodico/mjx/_src/test_util.py +488 -0
  77. mujodico/mjx/_src/types.py +1249 -0
  78. mujodico/mjx/integration_test/collision_driver_test.py +96 -0
  79. mujodico/mjx/integration_test/forward_test.py +70 -0
  80. mujodico/mjx/integration_test/smooth_test.py +95 -0
  81. mujodico/mjx/test_data/actuator/arm21.xml +37 -0
  82. mujodico/mjx/test_data/actuator/arm26.xml +118 -0
  83. mujodico/mjx/test_data/actuator/general_dyntype.xml +18 -0
  84. mujodico/mjx/test_data/actuator/tendon_force_clamp.xml +55 -0
  85. mujodico/mjx/test_data/barkour_v0/README.md +25 -0
  86. mujodico/mjx/test_data/barkour_v0/assets/abduction.stl +0 -0
  87. mujodico/mjx/test_data/barkour_v0/assets/barkour_v0_mjx.xml +245 -0
  88. mujodico/mjx/test_data/barkour_v0/assets/body.stl +0 -0
  89. mujodico/mjx/test_data/barkour_v0/assets/foot.stl +0 -0
  90. mujodico/mjx/test_data/barkour_v0/assets/handle.stl +0 -0
  91. mujodico/mjx/test_data/barkour_v0/assets/head.stl +0 -0
  92. mujodico/mjx/test_data/barkour_v0/assets/head_mount.stl +0 -0
  93. mujodico/mjx/test_data/barkour_v0/assets/hfield_240_280.png +0 -0
  94. mujodico/mjx/test_data/barkour_v0/assets/lower_leg_1to1.stl +0 -0
  95. mujodico/mjx/test_data/barkour_v0/assets/powercable.stl +0 -0
  96. mujodico/mjx/test_data/barkour_v0/assets/upper_left_1.stl +0 -0
  97. mujodico/mjx/test_data/barkour_v0/assets/upper_left_2.stl +0 -0
  98. mujodico/mjx/test_data/barkour_v0/assets/upper_left_3.stl +0 -0
  99. mujodico/mjx/test_data/barkour_v0/assets/upper_right_1.stl +0 -0
  100. mujodico/mjx/test_data/barkour_v0/assets/upper_right_2.stl +0 -0
  101. mujodico/mjx/test_data/barkour_v0/assets/upper_right_3.stl +0 -0
  102. mujodico/mjx/test_data/constraints.xml +115 -0
  103. mujodico/mjx/test_data/convex.xml +42 -0
  104. mujodico/mjx/test_data/humanoid/01_humanoids.xml +150 -0
  105. mujodico/mjx/test_data/humanoid/02_humanoids.xml +255 -0
  106. mujodico/mjx/test_data/humanoid/03_humanoids.xml +359 -0
  107. mujodico/mjx/test_data/humanoid/04_humanoids.xml +463 -0
  108. mujodico/mjx/test_data/humanoid/05_humanoids.xml +567 -0
  109. mujodico/mjx/test_data/humanoid/06_humanoids.xml +671 -0
  110. mujodico/mjx/test_data/humanoid/07_humanoids.xml +775 -0
  111. mujodico/mjx/test_data/humanoid/08_humanoids.xml +879 -0
  112. mujodico/mjx/test_data/humanoid/09_humanoids.xml +983 -0
  113. mujodico/mjx/test_data/humanoid/10_humanoids.xml +1087 -0
  114. mujodico/mjx/test_data/humanoid/README.md +26 -0
  115. mujodico/mjx/test_data/humanoid/humanoid.png +0 -0
  116. mujodico/mjx/test_data/humanoid/humanoid.xml +262 -0
  117. mujodico/mjx/test_data/meshes/dodecahedron.stl +0 -0
  118. mujodico/mjx/test_data/meshes/pyramid.stl +0 -0
  119. mujodico/mjx/test_data/meshes/tetrahedron.stl +0 -0
  120. mujodico/mjx/test_data/pendula.xml +160 -0
  121. mujodico/mjx/test_data/quadratic_frictionloss.xml +21 -0
  122. mujodico/mjx/test_data/ray.xml +18 -0
  123. mujodico/mjx/test_data/sensor/model.xml +74 -0
  124. mujodico/mjx/test_data/sensor/sensor.xml +224 -0
  125. mujodico/mjx/test_data/shadow_hand/README.md +28 -0
  126. mujodico/mjx/test_data/shadow_hand/assets/f_distal_pst.obj +10772 -0
  127. mujodico/mjx/test_data/shadow_hand/assets/f_distal_pst_214.obj +326 -0
  128. mujodico/mjx/test_data/shadow_hand/assets/f_knuckle.obj +1200 -0
  129. mujodico/mjx/test_data/shadow_hand/assets/f_middle.obj +908 -0
  130. mujodico/mjx/test_data/shadow_hand/assets/f_proximal.obj +2206 -0
  131. mujodico/mjx/test_data/shadow_hand/assets/forearm_0.obj +2220 -0
  132. mujodico/mjx/test_data/shadow_hand/assets/forearm_1.obj +37554 -0
  133. mujodico/mjx/test_data/shadow_hand/assets/forearm_collision.obj +924 -0
  134. mujodico/mjx/test_data/shadow_hand/assets/lf_metacarpal.obj +2996 -0
  135. mujodico/mjx/test_data/shadow_hand/assets/mounting_plate.obj +3936 -0
  136. mujodico/mjx/test_data/shadow_hand/assets/palm.obj +23598 -0
  137. mujodico/mjx/test_data/shadow_hand/assets/th_distal_pst.obj +9528 -0
  138. mujodico/mjx/test_data/shadow_hand/assets/th_distal_pst_190.obj +288 -0
  139. mujodico/mjx/test_data/shadow_hand/assets/th_middle.obj +2544 -0
  140. mujodico/mjx/test_data/shadow_hand/assets/th_proximal.obj +840 -0
  141. mujodico/mjx/test_data/shadow_hand/assets/wrist.obj +3708 -0
  142. mujodico/mjx/test_data/shadow_hand/right_hand.xml +330 -0
  143. mujodico/mjx/test_data/shadow_hand/scene_right.xml +31 -0
  144. mujodico/mjx/test_data/shadow_hand/shadow_hand.png +0 -0
  145. mujodico/mjx/test_data/tendon/armature.xml +24 -0
  146. mujodico/mjx/test_data/tendon/fixed.xml +23 -0
  147. mujodico/mjx/test_data/tendon/fixed_site.xml +39 -0
  148. mujodico/mjx/test_data/tendon/fixed_site_wrap.xml +58 -0
  149. mujodico/mjx/test_data/tendon/no_tendon.xml +16 -0
  150. mujodico/mjx/test_data/tendon/pulley_fixed_site_wrap.xml +103 -0
  151. mujodico/mjx/test_data/tendon/pulley_site.xml +84 -0
  152. mujodico/mjx/test_data/tendon/pulley_site_wrap.xml +98 -0
  153. mujodico/mjx/test_data/tendon/pulley_wrap.xml +78 -0
  154. mujodico/mjx/test_data/tendon/site.xml +26 -0
  155. mujodico/mjx/test_data/tendon/site_wrap.xml +54 -0
  156. mujodico/mjx/test_data/tendon/tendon.xml +66 -0
  157. mujodico/mjx/test_data/tendon/wrap_sidesite.xml +61 -0
  158. mujodico/mjx/testspeed.py +96 -0
  159. mujodico/mjx/viewer.py +166 -0
  160. mujodico/trajopt/__init__.py +7 -0
  161. mujodico/trajopt/_src/fatrop_ref/__init__.py +12 -0
  162. mujodico/trajopt/_src/fatrop_ref/fatrop_v43_runtime_patch.py +318 -0
  163. mujodico/trajopt/_src/fatrop_ref/linear.py +273 -0
  164. mujodico/trajopt/_src/fatrop_ref/linear_test.py +52 -0
  165. mujodico/trajopt/_src/fatrop_ref/restoration_v32.py +959 -0
  166. mujodico/trajopt/_src/fatrop_ref/solver.py +1474 -0
  167. mujodico/trajopt/_src/ipopt_multiple_shooting_solver.py +513 -0
  168. mujodico/trajopt/_src/prox_ddp.py +1049 -0
  169. mujodico/trajopt/_src/prox_ddp_test.py +122 -0
  170. mujodico-3.15.0.dist-info/METADATA +116 -0
  171. mujodico-3.15.0.dist-info/RECORD +174 -0
  172. mujodico-3.15.0.dist-info/WHEEL +5 -0
  173. mujodico-3.15.0.dist-info/entry_points.txt +3 -0
  174. 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}