From d95d78cbed244d8930b97ce42727acd00c9abed1 Mon Sep 17 00:00:00 2001 From: Peter Stahlecker Date: Tue, 10 Dec 2024 13:18:03 +0100 Subject: [PATCH] park a care in a garage --- examples-gallery/betts_10_57_solution.npy | Bin 0 -> 18728 bytes examples-gallery/car_in_garage_solution.npy | Bin 0 -> 36248 bytes examples-gallery/plot_betts_10_57.py | 256 +++++++++++ examples-gallery/plot_car_in_garage.py | 456 ++++++++++++++++++++ 4 files changed, 712 insertions(+) create mode 100644 examples-gallery/betts_10_57_solution.npy create mode 100644 examples-gallery/car_in_garage_solution.npy create mode 100644 examples-gallery/plot_betts_10_57.py create mode 100644 examples-gallery/plot_car_in_garage.py diff --git a/examples-gallery/betts_10_57_solution.npy b/examples-gallery/betts_10_57_solution.npy new file mode 100644 index 0000000000000000000000000000000000000000..e9538384ffee3cace3576fbe7107d759ae7028b0 GIT binary patch literal 18728 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 X6cCvEj5>01!-~yDl?G`ksRj54qD*G# literal 0 HcmV?d00001 diff --git a/examples-gallery/car_in_garage_solution.npy b/examples-gallery/car_in_garage_solution.npy new file mode 100644 index 0000000000000000000000000000000000000000..f7d928d3995efeea1ab0a524a9089af2f6df016e GIT binary patch literal 36248 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 Zqb|_^)4TSHDaKO+x#eH$6`SR;{|jE9?<4>K literal 0 HcmV?d00001 diff --git a/examples-gallery/plot_betts_10_57.py b/examples-gallery/plot_betts_10_57.py new file mode 100644 index 00000000..6ca18eef --- /dev/null +++ b/examples-gallery/plot_betts_10_57.py @@ -0,0 +1,256 @@ +# %% +""" +Heat Diffusion Process with Inequality +====================================== + +This is example 10.57 from Betts' book "Practical Methods for Optimal Control +Using Nonlinear Programming", 3rd edition, chapter 10: Test Problems. +It deals with the 'discretization' of a PDE. + +There are N equations of motion: + +:math:`\\dot{y}_i = function(y_j, u_0, u_{pi}, parameters)`. + +There are also N inequality constraints: + +:math:`y_i - g(x_k, t) \\geq 0`, with :math:`x_k = k \\dfrac{\ \pi}{N}`. + +So, I first do this: :math:`\\dfrac{d}{dt} y(t) \\geq \\dfrac{d}{dt} g(x_k, t)`. + +Then I rewrite the equations of motion like this: + +:math:`\\dfrac{d}{dt} g(x_k, t) = factor \cdot function(y_j, u_0, u_{pi}, parameters)`. + +Where factor :math:`\in (1.0, \infty)` if :math:`g(x_k, t) \\geq 0` +and factor :math:`\in (-\infty, 1.0)` if :math:`g(x_k, t) < 0`. + +As the equations of motion are only algebraic equations, and *opty* needs at +least one differential equation, I simply add a differential equation which +is not needed for the problem. + +**States** + +- :math:`y_0, .....y_{N-1}` : state variables +- :math:`u_{y0}` : not needed state variable + +**Specifieds** + +- :math:`u_0, u_{pi}` : control variables + +""" + +import numpy as np +import sympy as sm +import sympy.physics.mechanics as me +from opty.direct_collocation import Problem +import matplotlib.pyplot as plt + +# %% +# Equations of Motion +# ------------------- +t = me.dynamicsymbols._t +T = sm.symbols('T', cls=sm.Function) + +N = 20 +t0, tf = 0.0, 5.0 +exponent = 500 +num_nodes = 101 + +y = list(me.dynamicsymbols(f'y:{N}')) +uy0 = me.dynamicsymbols('uy0') +u0, upi = me.dynamicsymbols('u0 upi') + +faktor1, faktor2 = sm.symbols('faktor1 faktor2') + +#Parameters +q1 = 1.e-3 +q2 = 1.e-3 +a = 0.5 +b = 0.2 +c = 1.0 +delta = np.pi/N + +# %% +# The function gdt gives the derivative of the constraints, needed for the +# equations of motion. +def gdt(k, t): + x = k * np.pi/N + return ((c * (sm.sin(x) * sm.sin( np.pi*t/tf) - a) - b).diff(t)) + +# %% +# This function determines the factor for the equations of motion. +def faktor(k, t, faktor1, faktor2): + test = (c * (sm.sin(k * np.pi/N) * sm.sin(np.pi*t/tf) - a) - b) + hilfs = (1/(1+sm.exp(-exponent*test))*faktor1 + + 1/(1+sm.exp(exponent*test))*faktor2) + return hilfs + +# %% +# The first equation is only needed, because *opty* needs at least one +# differential equation. +eom = sm.Matrix([ + -y[0].diff(t) + uy0, + -gdt(1, T(t)) + faktor(1, T(t), faktor1, faktor2) * 1/delta**2 * (y[1] + - 2*y[0] + u0), + *[-gdt(i+1, T(t)) + faktor(i+1, T(t), faktor1, + faktor2) * 1/delta**2 * (y[i+1] - 2*y[i] + y[i-1]) + for i in range(1, N-1)], + -gdt(N, T(t)) + faktor(N, T(t), faktor1, faktor2) * 1/delta**2 * (upi - + 2*y[N-1] + y[N-2]), +]) + +sm.pprint(eom) + +# %% +# Solve the Optimization Problem +# ------------------------------ +interval_value = (tf - t0)/(num_nodes - 1) + +state_symbols = [uy0] + y +specified_symbols = (u0, upi) + +times = np.linspace(t0, tf, num=num_nodes) + +# %% +# Plot the constraints and the approximated Heavyside functions. It shows +# that even with exponent = 500 (the largest numpy seems to accept here) the +# Heavyside functions are not 'sharp' if the slope of the constraint is very +# close to zero. +x = sm.symbols('x') +t1 = sm.symbols('t1') +g = (c * (sm.sin(x) * sm.sin(sm.pi*t1/tf) - a) - b) + +hilfs = 1/(1+sm.exp(-exponent*g)) +hilfs1 = 1/(1+sm.exp(exponent*g)) +g_lam = sm.lambdify((x, t1), g, cse=True) +hilfs_lam = sm.lambdify((x, t1), hilfs, cse=True) +hilfs1_lam = sm.lambdify((x, t1), hilfs1, cse=True) + +Delta = [] +DELTA_1 = [] +DELTA_2 = [] +for i in range(N): + delta_h = [] + delta_h_1 = [] + delta_h_2 = [] + for j in range(num_nodes): + delta_h.append(g_lam((i+1)*np.pi/N, times[j])) + Delta.append(delta_h) + +fig, ax = plt.subplots(N ,1, figsize=(8, 1.5*N), constrained_layout=True) +for i in range(N): + ax[i].plot(times, Delta[i], label=str(i)) + ax[i].plot(times, hilfs_lam((i+1)*np.pi/N, times), label='hilfs') + ax[i].plot(times, hilfs1_lam((i+1)*np.pi/N, times), label='hilfs1') +# ax[i].axhline(0, color='black') + ax[i].legend() +ax[0].set_title('Constraints and approx. Heavyside functions') +ax[-1].set_xlabel('time [s]') +prevent_printing = 1 + +# %% +# Specify the objective function and form the gradient. +def obj(free): + value1 = interval_value * (delta/2 + q1) * sum([free[(N+1)*num_nodes+i]**2 + for i in range(num_nodes)]) + value2 = interval_value * (delta/2 + q2) * sum([free[(N+2)*num_nodes+i]**2 + for i in range(num_nodes)]) + value3 = 0 + for i in range(1, N+1): + value3 += interval_value * delta * sum([free[i*num_nodes+j]**2 + for j in range(num_nodes)]) + return value1 + value2 + value3 + +def obj_grad(free): + grad = np.zeros_like(free) + grad[(N+1)*num_nodes:(N+2)*num_nodes] = (2 * (delta/2 + q1) * + interval_value * free[(N+1)*num_nodes:(N+2)*num_nodes]) + grad[(N+2)*num_nodes:(N+3)*num_nodes] = (2 * (delta/2 + q2) * + interval_value * free[(N+2)*num_nodes:(N+3)*num_nodes]) + for i in range(1, N+1): + grad[i*num_nodes:(i+1)*num_nodes] = (2 * delta * interval_value * + free[i*num_nodes:(i+1)*num_nodes]) + return grad + +# %% +# Specify the instance constraints, as per the example, and the bounds. +instance_constraints = ( + *[y[i].func(t0) for i in range(N)], +) + +bounds = { + faktor1: (1.0, np.inf), + faktor2: (-np.inf, 1.0), +} + +# %% +# Create the optimization problem and set any options. +prob = Problem(obj, + obj_grad, + eom, + state_symbols, + num_nodes, + interval_value, + instance_constraints=instance_constraints, + known_trajectory_map={T(t): times}, + bounds=bounds, +) + +prob.add_option('max_iter', 20000) + +# %% +# Give some rough estimates for the trajectories. Here I use the solution from +# a previous run to speed up the optimization process. +initial_guess = np.zeros(prob.num_free) +initial_guess = np.load('betts_10_57_solution.npy') + +# %% +# Find the optimal solution. +for _ in range(1): + solution, info = prob.solve(initial_guess) + initial_guess = solution +# np.save('betts_10_57_solution', solution) + print(info['status_msg']) + print(f'Objective value achieved: {info['obj_val']:.4f}, as per the book ' + + f'it is {4.68159793*1.e-1}, so the error is: ' + f'{(info['obj_val'] - 4.68159793*1.e-1)/(4.68159793*1.e-1)*100:.3f} % ') + +# Plot the optimal state and input trajectories. +prob.plot_trajectories(solution) + +# %% +# Plot the constraint violations. +prob.plot_constraint_violations(solution) + +# %% +# Plot the objective function as a function of optimizer iteration. +prob.plot_objective_value() + + +# %% +# Plot the inequality constraint violations. It shows, that the constraints +# are not always fulfilled. +x = sm.symbols('x') +t1 = sm.symbols('t1') +g = c * (sm.sin(x) * sm.sin(sm.pi*t1/tf) - a) - b +g_lam = sm.lambdify((x, t1), g, cse=True) +Delta = [] +for i in range(N): + delta_h = [] + for j in range(num_nodes): + delta_h.append(solution[(i+1)*num_nodes + j] - + g_lam((i+1)*np.pi/N, times[j])) + Delta.append(delta_h) + +fig, ax = plt.subplots(N ,1, figsize=(8, 1.5*N), constrained_layout=True) +for i in range(N): + ax[i].plot(times, Delta[i], label=str(i)) + ax[i].axhline(0, color='black') + ax[i].legend() +ax[0].set_title('Constraint violation, Must be $\\geq 0.0$') +ax[-1].set_xlabel('time [s]') +prevent_printing = 1 + + +# %% +# sphinx_gallery_thumbnail_number = 3 diff --git a/examples-gallery/plot_car_in_garage.py b/examples-gallery/plot_car_in_garage.py new file mode 100644 index 00000000..deb728d5 --- /dev/null +++ b/examples-gallery/plot_car_in_garage.py @@ -0,0 +1,456 @@ +""" +Park a Car in a Garage +====================== +I try to model a **conventional car**: The rear axle is driven, +the front axle does the steering. +No speed possible perpendicular to the wheels. + +The car should enter the garage without colliding with the walls. + +**states** + +- :math:`x, y`: coordinates of the front of the car +- :math:`u_x, u_y`: velocities of the front of the car +- :math:`q_0, q_f`: orientation of the car and the steering angle of the front axle +- :math:`u_0, u_f`: angular velocities of the car and the front axle +- :math:`p_{min}`: the lowest point of the car +- :math:`p_{y_1}....p_{y_{number}}`: the y-coordinate of the points on the body of the car + +**controls** + +- :math:`T_f`: steering torque on the front axle +- :math:`F_b`: driving force on the rear axle + +**parameters** + +- :math:`l`: length of the car +- :math:`m_0, m_b, m_f`: mass of the car, the rear and the front axle +- :math:`i_{ZZ_0}, i_{ZZ_b}, i_{ZZ_f}`: moments of inertia of the car, the rear and the front axle +- :math:`reibung`: friction coefficient +- :math:`x_1, x_2, y_{12}`: the shape of the garage + +""" + +# %% +import sympy.physics.mechanics as me +import numpy as np +import sympy as sm +from scipy.interpolate import CubicSpline + +from opty.direct_collocation import Problem +from opty.utils import parse_free, create_objective_function +import matplotlib.pyplot as plt +from matplotlib.animation import FuncAnimation + +# %% +# Kane's Equations of Motion +#--------------------------- + +N, A0, Ab, Af = sm.symbols('N A0 Ab Af', cls= me.ReferenceFrame) +t = me.dynamicsymbols._t +O, Pb, Dmc, Pf = sm.symbols('O Pb Dmc Pf', cls= me.Point) +O.set_vel(N, 0) + +q0, qf = me.dynamicsymbols('q_0 q_f') +u0, uf = me.dynamicsymbols('u_0 u_f') +x, y = me.dynamicsymbols('x y') +ux, uy = me.dynamicsymbols('u_x u_y') +Tf, Fb = me.dynamicsymbols('T_f F_b') +reibung = sm.symbols('reibung') + +l, m0, mb, mf, iZZ0, iZZb, iZZf = sm.symbols('l m0 mb mf iZZ0, iZZb, iZZf') + +A0.orient_axis(N, q0, N.z) +A0.set_ang_vel(N, u0 * N.z) + +Ab.orient_axis(A0, 0, N.z) + +Af.orient_axis(A0, qf, N.z) +rot = Af.ang_vel_in(N) +Af.set_ang_vel(N, uf * N.z) +rot1 = Af.ang_vel_in(N) + +Pf.set_pos(O, x * N.x + y * N.y) +Pf.set_vel(N, ux * N.x + uy * N.y) + +Pb.set_pos(Pf, -l * A0.y) +Pb.v2pt_theory(Pf, N, A0) + +Dmc.set_pos(Pf, -l/2 * A0.y) +Dmc.v2pt_theory(Pf, N, A0) +prevent_print = 1. + +# %% +# No speed perpendicular to the wheels +vel1 = me.dot(Pb.vel(N), Ab.x) - 0 +vel2 = me.dot(Pf.vel(N), Af.x) - 0 + +# %% +I0 = me.inertia(A0, 0, 0, iZZ0) +body0 = me.RigidBody('body0', Dmc, A0, m0, (I0, Dmc)) +Ib = me.inertia(Ab, 0, 0, iZZb) +bodyb = me.RigidBody('bodyb', Pb, Ab, mb, (Ib, Pb)) +If = me.inertia(Af, 0, 0, iZZf) +bodyf = me.RigidBody('bodyf', Pf, Af, mf, (If, Pf)) +BODY = [body0, bodyb, bodyf] + +FL = [(Pb, Fb * Ab.y), (Af, Tf * N.z), (Dmc, -reibung * Dmc.vel(N))] + +kd = sm.Matrix([ux - x.diff(t), uy - y.diff(t), u0 - q0.diff(t), + me.dot(rot1- rot, N.z)]) +speed_constr = sm.Matrix([vel1, vel2]) + +q_ind = [x, y, q0, qf] +u_ind = [u0, uf] +u_dep = [ux, uy] + +KM = me.KanesMethod( + N, q_ind=q_ind, u_ind=u_ind, + kd_eqs=kd, + u_dependent=u_dep, + velocity_constraints=speed_constr, + ) +(fr, frstar) = KM.kanes_equations(BODY, FL) + +eom = kd.col_join(fr + frstar) +eom = eom.col_join(speed_constr) + +# %% +# Restrictions so the car does not crash into the walls. +# +# I define a (differentiable) 'trough' the shape of the garage and the walls. +# No part of (the body of) the car may be 'below' the trough. +# +# As *a priori* one does not know whether the car will drive straight into the +# garage, or back in, I take care of this with the variable *pmin*, which is the +# lower end of the car. + +# number of points considered on the body of the car. +number = 4 + +x1, x2, y12 = sm.symbols('x1 x2 y12') +pmin = me.dynamicsymbols('pmin') +py = me.dynamicsymbols(f'py:{number}') + +def min_diff(a, b, gr): + # differentiabl approximation of min(a, b) + # the higher gr the closer the approximation + return -1/gr * sm.log(sm.exp(-gr * a) + sm.exp(-gr * b)) + +def max_diff(a, b, gr): + # differentiabl approximation of max(a, b) + # the higher gr the closer the approximation + return 1/gr * sm.log(sm.exp(gr * a) + sm.exp(gr * b)) + +def trough(x, a, b, gr): + # approx zero for x in [a, b] + # approx one otherwise + # the higher gr the closer the approximation + return 1/(1 + sm.exp(gr*(x - a))) + 1/(1 + sm.exp(-gr*(x - b))) + +def step_l_diff(a, b, gr): + # approx zero for a < b, approx one otherwise + return 1/(1 + sm.exp(-gr*(a - b))) + +def step_r_diff(a, b, gr): + # approx zero for a > b, approx one otherwise + return 1/(1 + sm.exp(gr*(a - b))) + +def in_0_1(x): + wert = step_l_diff(x, 0, 50) * step_r_diff(x, 1, 50) * (1-trough(x, 0, 1, 50)) + return wert + +park1y = Pf.pos_from(O).dot(N.y) +park2y = Pb.pos_from(O).dot(N.y) +park1x = Pf.pos_from(O).dot(N.x) +park2x = Pb.pos_from(O).dot(N.x) + +delta_x = np.linspace(park1x, park2x, number) +delta_y = np.linspace(park1y, park2y, number) + +delta_p = [delta_y[i] - trough(delta_x[i], x1, x2, 50)*y12 + for i in range(number)] + +eom_add = sm.Matrix([ + *[-py[i] + delta_p[i] for i in range(number)], + -pmin + min_diff(park1y, park2y, 50), +]) +eom = eom.col_join(eom_add) +print(F'the eoms are too large to be printed here. The shape is {eom.shape}'+ + f' and they contain {sm.count_ops(eom)} operations.') + +# %% +# Check what the differentiable approximations of max(a, b), min(a, b), +# trough(a, b) and x :math:`\in` [0, 1] look like. + +# %% +a, b, c, gr = sm.symbols('a b c gr') +min_diff_lam = sm.lambdify((x, b, gr), min_diff(x, b, gr)) +max_diff_lam = sm.lambdify((x, b, gr), max_diff(x, b, gr)) +trough_lam = sm.lambdify((x, a, b, gr), trough(x, a, b, gr)) +step_l_diff_lam = sm.lambdify((a, b, gr), step_l_diff(a, b, gr)) +step_r_diff_lam = sm.lambdify((a, b, gr), step_r_diff(a, b, gr)) +in_0_1_lam = sm.lambdify(x, in_0_1(x)) + +a = -1.0 +b = 1.0 +c = 6.0 +gr = 50 +XX = np.linspace(-5.0, 5.0, 200) +fig, ax = plt.subplots(6, 1, figsize=(6.4, 7), constrained_layout=True) +ax[0].plot(XX, min_diff_lam(XX, a, gr)) +ax[0].axhline(a, color='k', linestyle='--') +ax[0].axvline(a, color='k', linestyle='--') +ax[0].set_title('differentiable approximation of min(a, b)') + + +ax[1].plot(XX, max_diff_lam(XX, a, gr)) +ax[1].axhline(a, color='k', linestyle='--') +ax[1].axvline(a, color='k', linestyle='--') +ax[1].set_title('differentiable approximation of max(a, b)') + + +ax[2].plot(XX, trough_lam(XX, a, b, gr)) +ax[2].axvline(a, color='k', linestyle='--') +ax[2].axvline(b, color='k', linestyle='--') +ax[2].axhline(0, color='k', linestyle='--') +ax[2].set_title('differentiable trough') + +ax[3].plot(XX, step_l_diff_lam(XX, b, gr)) +ax[3].axvline(b, color='k', linestyle='--') +ax[3].set_title('differentiable step_l') + +ax[4].plot(XX, step_r_diff_lam(XX, b, gr)) +ax[4].axvline(b, color='k', linestyle='--') +ax[4].set_title('differentiable step_r') + +ax[5].plot(XX, in_0_1_lam(XX)) +ax[5].axvline(0, color='k', linestyle='--') +ax[5].axvline(1, color='k', linestyle='--') +ax[5].set_title('differentiable in_0_1') +prevent_print = 1. + +# %% +# Set the Optimization Problem and Solve it +#------------------------------------------ + +state_symbols = [x, y, q0, qf, ux, uy, u0, uf, pmin] + py +laenge = len(state_symbols) +constant_symbols = (l, m0, mb, mf, iZZ0, iZZb, iZZf, reibung) +specified_symbols = (Fb, Tf) +unknown_symbols = () + +num_nodes = 301 +t0, tf = 0.0, 7.5 +interval_value = (tf - t0) / (num_nodes - 1) + +# %% +# Specify the known system parameters. +par_map = {} +par_map[m0] = 1.0 +par_map[mb] = 0.5 +par_map[mf] = 0.5 +par_map[iZZ0] = 1. +par_map[iZZb] = 0.5 +par_map[iZZf] = 0.5 +par_map[l] = 3.0 +par_map[reibung] = 0.5 +par_map[x1] = -0.75 +par_map[x2] = 0.75 +par_map[y12] = 5.0 + +# %% +# Specify the objective function, the constraints and the bounds. +objective = sm.Integral(Fb**2 + Tf**2, t) +obj, obj_grad = create_objective_function( + objective, + state_symbols, + specified_symbols, + tuple(), + num_nodes, + interval_value, +) + +initial_state_constraints = { + x: 7.5, + y: 10.0, + q0: np.pi/2.0, + qf: 0.5, + ux: 0., + uy: 0., + u0: 0., + uf: 0., +} + +final_state_constraints = { + pmin: 0.5, + ux: 0., + x : 0.0 , + uy: 0., +} + +instance_constraints = tuple(xi.subs({t: t0}) - xi_val for xi, xi_val + in initial_state_constraints.items()) + tuple(xi.subs({t: tf}) - xi_val + for xi, xi_val in final_state_constraints.items()) + +grenze = 25.0 +delta = np.pi/4. +bounds1 = { + Fb: (-grenze, grenze), + Tf: (-grenze, grenze), + # restrict the steering angle to avoid locking + qf: (-np.pi/2. + delta, np.pi/2. - delta), + x: (-10, 10), + y: (0.0, 25), +} + +bounds2 = {py[i]: (0, 100) for i in range(number)} +bounds = {**bounds1, **bounds2} + +prob = Problem( + obj, + obj_grad, + eom, + state_symbols, + num_nodes, + interval_value, + known_parameter_map=par_map, + instance_constraints=instance_constraints, + bounds=bounds, +) + +# %% +# I use the result of a previous run as initial guess, to speed up +# the optimization process. +initial_guess = np.ones(prob.num_free) +initial_guess = np.load('car_in_garage_solution.npy') + +prob.add_option('max_iter', 1000) +for i in range(1): +# Find the optimal solution. + solution, info = prob.solve(initial_guess) + initial_guess = solution + print(f'{i+1} - th iteration') + print('message from optimizer:', info['status_msg']) + print('Iterations needed',len(prob.obj_value)) + print(f"objective value {info['obj_val']:.3e} \n") +prob.plot_objective_value() + +# %% +# Plot the constraint violations. +prob.plot_constraint_violations(solution) + +# %% [markdown] +# Plot generalized coordinates / speeds and forces / torques +prob.plot_trajectories(solution) + +# %% +# Aminate the Car +#----------------- +# The green arrow symbolizes the force which opty calculated to drive the car. +# It is perpendicular to the rear axle. +fps = 20 + +def add_point_to_data(line, x, y): +# to trace the path of the point. Copied from Timo. + old_x, old_y = line.get_data() + line.set_data(np.append(old_x, x), np.append(old_y, y)) + + +state_vals, input_vals, _ = parse_free(solution, len(state_symbols), + len(specified_symbols), num_nodes) +t_arr = np.linspace(t0, tf, num_nodes) +state_sol = CubicSpline(t_arr, state_vals.T) +input_sol = CubicSpline(t_arr, input_vals.T) + +# create additional points for the axles +Pbl, Pbr, Pfl, Pfr = sm.symbols('Pbl Pbr Pfl Pfr', cls= me.Point) + +# end points of the force, length of the axles +Fbq = me.Point('Fbq') +la = sm.symbols('la') +fb, tq = sm.symbols('f_b, t_q') + +Pbl.set_pos(Pb, -la/2 * Ab.x) +Pbr.set_pos(Pb, la/2 * Ab.x) +Pfl.set_pos(Pf, -la/2 * Af.x) +Pfr.set_pos(Pf, la/2 * Af.x) + +Fbq.set_pos(Pb, fb * Ab.y) + +coordinates = Pb.pos_from(O).to_matrix(N) +for point in (Dmc, Pf, Pbl, Pbr, Pfl, Pfr, Fbq): + coordinates = coordinates.row_join(point.pos_from(O).to_matrix(N)) + +pL, pL_vals = zip(*par_map.items()) +la1 = par_map[l] / 4. # length of an axle +la2 = la1/2.0 +coords_lam = sm.lambdify((*state_symbols, fb, tq, *pL, la), coordinates, + cse=True) + + +# needed to give the picture the right size. +xmin, xmax = -10, 11. +ymin, ymax = 0.0, 21. + +fig = plt.figure(figsize=(8, 8)) +ax = fig.add_subplot(111) +ax.set_xlim(xmin, xmax) +ax.set_ylim(ymin, ymax) +ax.set_aspect('equal') +ax.grid() + +ax.plot(initial_state_constraints[x], initial_state_constraints[y], 'ro', + markersize=10) +ax.plot((par_map[x1]-la2, par_map[x1]-la2), (0.0, par_map[y12]-la2), + color='black', lw=1.5) +ax.plot((par_map[x2]+la2, par_map[x2]+la2), (0.0, par_map[y12]-la2), + color='black', lw=1.5) +ax.plot((xmin, par_map[x1]-la2), (par_map[y12]-la2, par_map[y12]-la2), + color='black', lw=1.5) +ax.plot((par_map[x2]+la2, xmax), (par_map[y12]-la2, par_map[y12]-la2), + color='black', lw=1.5) +ax.plot((par_map[x1]-la2, par_map[x2]+0.25), (0.0, 0.0), + color='black', lw=1.5) + +ax.fill_between((xmin, par_map[x1]-la2), (par_map[y12]-la2, par_map[y12]-la2), + color='grey', alpha=0.5) +ax.fill_between((par_map[x2]+la2, xmax), (par_map[y12]-la2, par_map[y12]-la2), + color='grey', alpha=0.5) + +# Initialize the block +line1, = ax.plot([], [], color='orange', lw=2) +line2, = ax.plot([], [], color='red', lw=2) +line3, = ax.plot([], [], color='magenta', lw=2) +line4 = ax.quiver([], [], [], [], color='green', scale=35, width=0.004, + headwidth=8) + + +# Function to update the plot for each animation frame +def update(t): + message = (f'running time {t:.2f} sec \n The back axle is red, the ' + + f'front axle is magenta \n The driving force is green') + ax.set_title(message, fontsize=12) + + coords = coords_lam(*state_sol(t), *input_sol(t), *pL_vals, la1) + + # Pb, Dmc, Pf, Pbl, Pbr, Pfl, Pfr, Fbq + line1.set_data([coords[0, 0], coords[0, 2]], [coords[1, 0], coords[1, 2]]) + line2.set_data([coords[0, 3], coords[0, 4]], [coords[1, 3], coords[1, 4]]) + line3.set_data([coords[0, 5], coords[0, 6]], [coords[1, 5], coords[1, 6]]) + + line4.set_offsets([coords[0, 0], coords[1, 0]]) + line4.set_UVC(coords[0, 7] - coords[0, 0] , coords[1, 7] - coords[1, 0]) + + return line1, line2, line3, line4, + +frames = np.linspace(t0, tf, int(fps * (tf - t0))) +animation = FuncAnimation(fig, update, frames=frames, interval=1000 / fps) + +plt.show() + +# %% +# sphinx_gallery_thumbnail_number = 3 + + +