Skip to content

Commit 6ae1c27

Browse files
committed
slack variables for coulomb friction. A possible example for PR csu-hmc#476
1 parent 59825d4 commit 6ae1c27

File tree

1 file changed

+289
-0
lines changed

1 file changed

+289
-0
lines changed
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
# %%
2+
r"""
3+
Coulomb Friction with Slack Variables
4+
=====================================
5+
6+
Objectives
7+
-----------
8+
9+
- Demonstrate how slack variables and inequality constraints can be used to
10+
manage discontinuties in the equations of motion.
11+
- Show how to use a differentiable approximation to get good initial guesses
12+
for the original problem.
13+
14+
Description
15+
-----------
16+
17+
A block of mass :math:`m` is being pushed with force :math:`F(t)` along on a
18+
surface. Coulomb friction acts between the block and the surface. Find a
19+
minimal time solution to push the block 10 meters and then back to the original
20+
position.
21+
22+
Notes
23+
-----
24+
25+
- Good initial guesses are needed in this example.
26+
27+
**States**
28+
29+
- :math:`x(t)` - position of the block
30+
- :math:`v(t)` - velocity of the block
31+
32+
**Inputs**
33+
34+
- :math:`F(t)` - force applied to the block
35+
- :math:`F_{fp}(t)` - positive friction force
36+
- :math:`F_{fn}(t)` - negative friction force
37+
- :math:`\psi(t)` - slack variable to handle discontinuities in the equations
38+
of motion.
39+
40+
**Parameters**
41+
42+
- :math:`m` - mass of the block
43+
- :math:`\mu` - coefficient of friction
44+
- :math:`g` - gravitational acceleration
45+
46+
"""
47+
48+
import numpy as np
49+
import sympy as sm
50+
from opty import Problem
51+
from opty.utils import MathJaxRepr
52+
import matplotlib.pyplot as plt
53+
54+
# sphinx_gallery_thumbnail_number = 6
55+
56+
# %%
57+
# Coulomb Friction with Step Function
58+
# ===================================
59+
#
60+
# A differentiable approximation of the Coulomb friction force is used to
61+
# get good initial guesses for the original problem.
62+
63+
64+
def smooth_step(x, steepness=10.0):
65+
"""Return a smooth step function, with step at x = 0.0"""
66+
return 0.5 * (1 + sm.tanh(steepness * x))
67+
68+
69+
# Symbolic equations of motion.
70+
m, mu, g, t, h, Fr = sm.symbols('m, mu, g, t, h, Fr', real=True)
71+
x, v, psi, Ffp, Ffn, F = sm.symbols('x, v, psi, Ffp, Ffn, F', cls=sm.Function)
72+
h = sm.symbols('h', real=True)
73+
74+
state_symbols = (x(t), v(t))
75+
constant_symbols = (m, mu, g)
76+
specified_symbols = (F(t), Ffn(t), Ffp(t), psi(t))
77+
78+
eom = sm.Matrix([
79+
# equations of motion with positive and negative friction force
80+
x(t).diff(t) - v(t),
81+
m*v(t).diff(t) - Ffp(t) + Ffn(t) - F(t),
82+
Ffp(t) - Fr * smooth_step(v(t)),
83+
Ffn(t) - Fr * smooth_step(-v(t)),
84+
])
85+
86+
MathJaxRepr(eom)
87+
88+
# %%
89+
# Specify the known system parameters.
90+
91+
par_map = {
92+
m: 1.0,
93+
mu: 0.6,
94+
g: 9.81,
95+
}
96+
Freib = par_map[m] * par_map[g] * par_map[mu] # Max. friction force
97+
par_map.update({Fr: Freib})
98+
99+
100+
# %%
101+
# Specify the objective function and it's gradient.
102+
103+
104+
def obj(free):
105+
"""Return h (always the last element in the free variables)."""
106+
return free[-1]
107+
108+
109+
def obj_grad(free):
110+
"""Return the gradient of the objective."""
111+
grad = np.zeros_like(free)
112+
grad[-1] = 1.0
113+
return grad
114+
115+
116+
# %%
117+
# Specify the symbolic instance constraints, i.e. initial and end conditions
118+
# Start with defining the fixed duration and number of nodes.
119+
# N must be even so the solution of the hump approximation may be used as an
120+
# initial guess for the slack variable problem.
121+
N = 100
122+
if N % 2 != 0:
123+
raise ValueError("N must be even for this example.")
124+
125+
t0, tm, tf = 0*h, (N // 2) * h, (N - 1)*h
126+
instance_constraints = (
127+
x(t0) - 0.0,
128+
v(t0) - 0.0,
129+
Ffp(t0) - 0.0,
130+
Ffn(t0) - 0.0,
131+
x(tm) - 10.0,
132+
v(tm) - 0.0,
133+
x(tf) + 0.0,
134+
v(tf) - 0.0,
135+
)
136+
137+
bounds = {
138+
F(t): (-400.0, 400.0), # Force
139+
h: (0.0, 0.2),
140+
}
141+
142+
# %%
143+
# Create an optimization problem.
144+
prob = Problem(obj, obj_grad, eom, state_symbols, N, h,
145+
known_parameter_map=par_map,
146+
instance_constraints=instance_constraints,
147+
time_symbol=t,
148+
bounds=bounds)
149+
150+
prob.add_option('max_iter', 5000)
151+
152+
# %%
153+
# Use a random initial guess.
154+
np.random.seed(42)
155+
initial_guess = np.random.randn(prob.num_free)
156+
initial_guess[-1] = 0.005
157+
158+
# %%
159+
# Plot the initial guess.
160+
_ = prob.plot_trajectories(initial_guess)
161+
162+
# %%
163+
# Find the optimal solution.
164+
solution, info = prob.solve(initial_guess)
165+
initial_guess = solution
166+
print(info['status_msg'])
167+
print(f"Interval value h = {info['obj_val']:.5f} s")
168+
# %%
169+
# Plot the objective function as a function of optimizer iteration.
170+
_ = prob.plot_objective_value()
171+
172+
# %%
173+
# Plot the constraint violations.
174+
_ = prob.plot_constraint_violations(solution)
175+
176+
# %%
177+
# Plot the optimal state and input trajectories.
178+
_ = prob.plot_trajectories(solution)
179+
180+
# %%
181+
# Plot the friction force.
182+
xs, rs, _, _ = prob.parse_free(solution)
183+
ts = prob.time_vector(solution)
184+
fig, ax = plt.subplots()
185+
ax.plot(ts, -rs[1] + rs[2])
186+
ax.set_ylabel(r'$F_f$ [N]')
187+
ax.set_xlabel('Time [s]')
188+
ax.set_title('Friction Force with Smooth Step Function')
189+
plt.show()
190+
191+
# %%
192+
# Coulomb Friction with Slack Variables
193+
# =====================================
194+
#
195+
# This is the original Problem using slack variables.
196+
197+
# %%
198+
# Symbolic equations of motion.
199+
200+
eom = sm.Matrix([
201+
# equations of motion with positive and negative friction force
202+
x(t).diff(t) - v(t),
203+
m*v(t).diff(t) - Ffp(t) + Ffn(t) - F(t),
204+
# following two lines ensure: psi >= abs(v)
205+
psi(t) + v(t), # >= 0
206+
psi(t) - v(t), # >= 0
207+
# mu*m*g*psi = (Ffp + Ffn)*psi -> mu*m*g = Ffn v > 0 & mu*m*g = Ffp
208+
# if v < 0
209+
(mu*m*g - Ffp(t) - Ffn(t))*psi(t),
210+
# Ffp*psi = -Ffp*v -> Ffp is zero if v > 0
211+
Ffp(t)*(psi(t) + v(t)),
212+
# Ffn*psi = Ffn*v -> Ffn is zero if v < 0
213+
Ffn(t)*(psi(t) - v(t)),
214+
])
215+
216+
MathJaxRepr(eom)
217+
218+
# %%
219+
# Adjust parameters and bounds to the slack variable problem.
220+
del par_map[Fr]
221+
222+
bounds.update({Ffn(t): (0.0, Freib)}) # Negative friction force
223+
bounds.update({Ffp(t): (0.0, Freib)}) # Positive friction force
224+
225+
eom_bounds = {
226+
2: (0.0, np.inf),
227+
3: (0.0, np.inf),
228+
}
229+
230+
# %%
231+
# Create an optimization problem.
232+
prob = Problem(obj, obj_grad, eom, state_symbols, N, h,
233+
known_parameter_map=par_map,
234+
instance_constraints=instance_constraints,
235+
time_symbol=t,
236+
bounds=bounds,
237+
eom_bounds=eom_bounds)
238+
239+
prob.add_option('max_iter', 5000)
240+
241+
# %%
242+
# Take the solution of the differentiable approximation as initial guess. Some
243+
# noise is added.
244+
initial_guess = (np.zeros(prob.num_free) +
245+
np.random.normal(loc=1.0, scale=1.0, size=prob.num_free))
246+
better_guess = (solution +
247+
np.random.normal(loc=1.0, scale=1.0, size=len(solution)))
248+
initial_guess[0: 5*N] = better_guess[0: 5*N]
249+
initial_guess[5*N: 6*N] = np.abs(initial_guess[1*N: 2*N])
250+
initial_guess[-1] = solution[-1]
251+
252+
# %%
253+
# Plot the initial guess.
254+
_ = prob.plot_trajectories(initial_guess)
255+
256+
# %%
257+
# Find the optimal solution.
258+
solution, info = prob.solve(initial_guess)
259+
initial_guess = solution
260+
print(info['status_msg'])
261+
print(f"Interval value h = {info['obj_val']:.5f} s")
262+
263+
# %%
264+
# Plot the objective function as a function of optimizer iteration.
265+
_ = prob.plot_objective_value()
266+
267+
# %%
268+
# Plot the constraint violations.
269+
_ = prob.plot_constraint_violations(solution)
270+
271+
# %%
272+
# Plot the optimal state and input trajectories.
273+
ax = prob.plot_trajectories(solution)
274+
t_v0 = (N // 2) * solution[-1]
275+
for i in range(len(ax)):
276+
ax[i].axvline(t_v0, color='k', linestyle='--')
277+
_ = ax[1].axhline(0.0, color='k', linestyle='--')
278+
279+
# %%
280+
# Plot the friction force.
281+
xs, rs, _, _ = prob.parse_free(solution)
282+
ts = prob.time_vector(solution)
283+
fig, ax = plt.subplots()
284+
ax.plot(ts, -rs[1] + rs[2])
285+
ax.set_ylabel(r'$F_f$ [N]')
286+
ax.set_xlabel('Time [s]')
287+
ax.set_title('Friction Force with Slack Variables')
288+
289+
plt.show()

0 commit comments

Comments
 (0)