Skip to content

Commit ffe869e

Browse files
committed
Squashed commit of the following:
commit 22ce55f Author: Maximilian Stolzle <[email protected]> Date: Mon Sep 23 17:26:38 2024 -0400 Fix bugs in calling `approx_derivative` commit 0c23d08 Author: Maximilian Stolzle <[email protected]> Date: Mon Sep 23 17:16:13 2024 -0400 Add script to test finite differences commit 6552bf3 Author: Maximilian Stolzle <[email protected]> Date: Mon Sep 23 16:32:27 2024 -0400 Start implementing numerical Jacobian method commit 14bd079 Author: Maximilian Stolzle <[email protected]> Date: Mon Sep 23 15:41:42 2024 -0400 Migrate to `scipy.optimize.minimize` commit c5bbb3d Author: Maximilian Stolzle <[email protected]> Date: Mon Sep 23 15:11:01 2024 -0400 Fix bug in previous commit commit 276a131 Author: Maximilian Stolzle <[email protected]> Date: Mon Sep 23 15:09:12 2024 -0400 Compile residual function including its jacobian ourselves commit 6117c26 Author: Maximilian Stolzle <[email protected]> Date: Mon Sep 23 13:51:56 2024 -0400 Add `jaxopt` dependency commit 05d400e Author: Maximilian Stölzle <[email protected]> Date: Mon Sep 23 10:38:00 2024 -0400 Decrease tolerance for the nonlinear least squares solver commit ef3bf7c Author: Maximilian Stölzle <[email protected]> Date: Mon Sep 23 10:14:11 2024 -0400 Deactivate Jacobian computation for now commit 3fc38d5 Author: Maximilian Stölzle <[email protected]> Date: Mon Sep 23 10:13:09 2024 -0400 Add aux outputs to functions commit 09754f2 Author: Maximilian Stölzle <[email protected]> Date: Mon Sep 23 09:34:46 2024 -0400 First version of script to derive a motor2ee jacobian for planar hsa robot
1 parent de71a13 commit ffe869e

File tree

8 files changed

+658
-5
lines changed

8 files changed

+658
-5
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ pip install jsrm
4040
or locally from the source code:
4141

4242
```bash
43-
pip install .
43+
pip install -e .
4444
```
4545

4646
If you want to run the examples, you will also need to install the following dependencies:
4747

4848
```bash
49-
pip install ".[examples]"
49+
pip install -e ".[examples]"
5050
```
5151

5252
## Usage
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import jax
2+
3+
jax.config.update("jax_enable_x64", True) # double precision
4+
from jax import Array, jacfwd, jacrev, jit, random, vmap
5+
from jax import numpy as jnp
6+
from jaxopt import GaussNewton, LevenbergMarquardt
7+
from functools import partial
8+
import numpy as onp
9+
from pathlib import Path
10+
import scipy as sp
11+
from typing import Callable, Dict, Tuple
12+
13+
import jsrm
14+
from jsrm.parameters.hsa_params import PARAMS_FPU_CONTROL
15+
from jsrm.systems import planar_hsa
16+
from jsrm.utils.numerical_jacobian import approx_derivative
17+
18+
num_segments = 1
19+
num_rods_per_segment = 2
20+
21+
# filepath to symbolic expressions
22+
sym_exp_filepath = (
23+
Path(jsrm.__file__).parent
24+
/ "symbolic_expressions"
25+
/ f"planar_hsa_ns-{num_segments}_nrs-{num_rods_per_segment}.dill"
26+
)
27+
28+
29+
def factory_fn(params: Dict[str, Array], verbose: bool = False) -> Tuple[Callable, Callable]:
30+
"""
31+
Factory function for the planar HSA.
32+
Args:
33+
params: dictionary with robot parameters
34+
verbose: flag to print additional information
35+
Returns:
36+
phi2chi_static_model_fn: function that maps motor angles to the end-effector pose
37+
jac_phi2chi_static_model_fn: function that computes the Jacobian between the actuation space and the task-space
38+
"""
39+
(
40+
forward_kinematics_virtual_backbone_fn,
41+
forward_kinematics_end_effector_fn,
42+
jacobian_end_effector_fn,
43+
inverse_kinematics_end_effector_fn,
44+
dynamical_matrices_fn,
45+
sys_helpers,
46+
) = planar_hsa.factory(sym_exp_filepath, strain_selector)
47+
dynamical_matrices_fn = partial(dynamical_matrices_fn, params)
48+
forward_kinematics_end_effector_fn = jit(partial(forward_kinematics_end_effector_fn, params))
49+
jacobian_end_effector_fn = jit(partial(jacobian_end_effector_fn, params))
50+
51+
def residual_fn(q: Array, phi: Array) -> Array:
52+
q_d = jnp.zeros_like(q)
53+
_, _, G, K, _, alpha = dynamical_matrices_fn(q, q_d, phi=phi)
54+
res = alpha - G - K
55+
return jnp.square(res).mean()
56+
57+
# jit the residual function
58+
residual_fn = jit(residual_fn)
59+
print("Compiling residual_fn...")
60+
print(residual_fn(jnp.zeros((3,)), jnp.zeros((2,))))
61+
62+
# define the Jacobian of the residual function
63+
jac_residual_fn = jit(jacrev(residual_fn, argnums=0))
64+
print("Compiling jac_residual_fn...")
65+
print(jac_residual_fn(jnp.zeros((3,)), jnp.zeros((2,))))
66+
67+
def phi2q_static_model_fn(phi: Array, q0: Array = jnp.zeros((3, ))) -> Tuple[Array, Dict[str, Array]]:
68+
"""
69+
A static model mapping the motor angles to the planar HSA configuration using scipy.optimize.minimize.
70+
Arguments:
71+
phi: motor angles
72+
q0: initial guess for the configuration
73+
Returns:
74+
q: planar HSA configuration consisting of (k_be, sigma_sh, sigma_ax)
75+
aux: dictionary with auxiliary data
76+
"""
77+
# solve the nonlinear least squares problem
78+
sol = sp.optimize.minimize(
79+
fun=lambda q: residual_fn(q, phi).item(),
80+
x0=q0,
81+
jac=lambda q: jac_residual_fn(q, phi),
82+
options={"disp": True} if verbose else None,
83+
)
84+
if verbose:
85+
print("Optimization converged after", sol.nit, "iterations with residual", sol.fun)
86+
87+
# configuration that minimizes the residual
88+
q = jnp.array(sol.x)
89+
90+
aux = dict(
91+
phi=phi,
92+
q=q,
93+
residual=sol.fun,
94+
)
95+
96+
return q, aux
97+
98+
def phi2chi_static_model_fn(phi: Array, q0: Array = jnp.zeros((3, ))) -> Tuple[Array, Dict[str, Array]]:
99+
"""
100+
A static model mapping the motor angles to the planar end-effector pose.
101+
Arguments:
102+
phi: motor angles
103+
q0: initial guess for the configuration
104+
Returns:
105+
chi: end-effector pose
106+
aux: dictionary with auxiliary data
107+
"""
108+
q, aux = phi2q_static_model_fn(phi, q0=q0)
109+
chi = forward_kinematics_end_effector_fn(q)
110+
aux["chi"] = chi
111+
return chi, aux
112+
113+
def jac_phi2chi_static_model_fn(phi: Array, q0: Array = jnp.zeros((3, ))) -> Tuple[Array, Dict[str, Array]]:
114+
"""
115+
Compute the Jacobian between the actuation space and the task-space.
116+
Arguments:
117+
phi: motor angles
118+
"""
119+
# evaluate the static model to convert motor angles into a configuration
120+
q, aux = phi2q_static_model_fn(phi, q0=q0)
121+
# approximate the Jacobian between the actuation and the task-space using finite differences
122+
J_phi2q = approx_derivative(
123+
fun=lambda _phi: phi2q_static_model_fn(_phi, q0=q0)[0],
124+
x0=phi,
125+
f0=q,
126+
)
127+
128+
# evaluate the closed-form, analytical jacobian of the forward kinematics
129+
J_q2chi = jacobian_end_effector_fn(q)
130+
131+
# evaluate the Jacobian between the actuation and the task-space
132+
J_phi2chi = J_q2chi @ J_phi2q
133+
134+
return J_phi2chi, aux
135+
136+
return phi2chi_static_model_fn, jac_phi2chi_static_model_fn
137+
138+
139+
if __name__ == "__main__":
140+
# activate all strains (i.e. bending, shear, and axial)
141+
strain_selector = jnp.ones((3 * num_segments,), dtype=bool)
142+
params = PARAMS_FPU_CONTROL
143+
phi_max = params["phi_max"].flatten()
144+
145+
# call the factory function
146+
phi2chi_static_model_fn, jac_phi2chi_static_model_fn = factory_fn(params)
147+
148+
# define initial configuration
149+
q0 = jnp.array([0.0, 0.0, 0.0])
150+
151+
rng = random.key(seed=0)
152+
for i in range(10):
153+
match i:
154+
case 0:
155+
phi = jnp.array([0.0, 0.0])
156+
case 1:
157+
phi = jnp.array([1.0, 1.0])
158+
case _:
159+
rng, subkey = random.split(rng)
160+
phi = random.uniform(
161+
subkey,
162+
phi_max.shape,
163+
minval=0.0,
164+
maxval=phi_max
165+
)
166+
167+
print("i", i)
168+
169+
chi, aux = phi2chi_static_model_fn(phi, q0=q0)
170+
print("phi", phi, "q", aux["q"], "chi", chi)
171+
172+
J_phi2chi, aux = jac_phi2chi_static_model_fn(phi, q0=q0)
173+
print("J_phi2chi:\n", J_phi2chi)

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,10 @@ dev = [
131131
]
132132
examples = [
133133
"diffrax",
134+
"jaxopt",
134135
"matplotlib",
135136
"opencv-python",
137+
"scipy",
136138
]
137139
test = [
138140
"codecov",

src/jsrm/systems/planar_hsa.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -717,9 +717,9 @@ def ode_factory(
717717
alpha_fn is a function to compute the actuation vector of shape (n_q). It has the following signature:
718718
alpha_fn(phi) -> tau_q where phi is the twist angle vector of shape (n_phi, )
719719
params: Dictionary with robot parameters
720-
control_fn: Callable that returns the forcing function of the form control_fn(t, x) -> u. If consider_underactuation_model is True,
721-
then u is an array of shape (n_q, ) with the configuration-space torques. If consider_underactuation_model is False,
722-
then u is an array of shape (n_phi, ) with the motor positions / twist angles of the proximal end of the rods.
720+
control_fn: Callable that returns the forcing function of the form control_fn(t, x) -> phi. If consider_underactuation_model is True,
721+
then phi is an array of shape (n_q, ) with the configuration-space torques. If consider_underactuation_model is False,
722+
then phi is an array of shape (n_phi, ) with the motor positions / twist angles of the proximal end of the rods.
723723
consider_underactuation_model: If True, the underactuation model is considered. Otherwise, the fully-actuated
724724
model is considered with the identity matrix as the actuation matrix.
725725
consider_hysteresis: If True, Bouc-Wen is used to model hysteresis. Otherwise, hysteresis will be neglected.

src/jsrm/utils/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)