|
| 1 | +# Prescribed movements |
| 2 | + |
| 3 | +The movement of a frame can be prescribed using any of the components |
| 4 | +- [`Position`](@ref) |
| 5 | +- [`Pose`](@ref) |
| 6 | + |
| 7 | +The motion of joints [`Revolute`](@ref) and [`Prismatic`](@ref) can also be prescribed by using `axisflange = true` and attaching a `ModelingToolkitStandardLibrary.Rotational.Position` or `ModelingToolkitStandardLibrary.TranslationalModelica.Position` to the axis flange of the joint. |
| 8 | + |
| 9 | + |
| 10 | +Prescribed motions can be useful to, e.g., |
| 11 | +- Use the 3D rendering capabilities to visualize the movement of a mechanism moving in a prescribed way without simulating the system. |
| 12 | +- Simplify simulations by prescribing the movement of a part of the mechanism. |
| 13 | + |
| 14 | + |
| 15 | +In the example below, we prescribe the motion of a suspension system using [`Pose`](@ref). We load the `Rotations` package in order to get access to the constructor `RotXYZ` which allows us to specify a rotation matrix using Euler angles. The component prescribing the motion is |
| 16 | +```julia |
| 17 | +body_upright = Pose(; r = [0, 0.17 + 0.1sin(t), 0], R = RotXYZ(0, 0.5sin(t), 0)) |
| 18 | +``` |
| 19 | +This makes the `body_upright` frame move in the vertical direction and rotate around the y-axis. Notice how the reference translation `r` and the reference orientation `R` are symbolic expressions that depend on time `t`. To make a frame follow recorded data, you may use [DataInterpolations.jl](https://docs.sciml.ai/DataInterpolations/stable/) to create interpolation objects that when accessed with `t` return the desired position and orientation. |
| 20 | + |
| 21 | +```@example PRESCRIBED_POSE |
| 22 | +using Multibody |
| 23 | +using Multibody.Rotations # To specify orientations using Euler angles |
| 24 | +using ModelingToolkit |
| 25 | +using Plots |
| 26 | +using OrdinaryDiffEq |
| 27 | +using LinearAlgebra |
| 28 | +using JuliaSimCompiler |
| 29 | +using Test |
| 30 | +
|
| 31 | +t = Multibody.t |
| 32 | +D = Differential(t) |
| 33 | +W(args...; kwargs...) = Multibody.world |
| 34 | +
|
| 35 | +n = [1, 0, 0] |
| 36 | +AB = 146.5 / 1000 |
| 37 | +BC = 233.84 / 1000 |
| 38 | +CD = 228.60 / 1000 |
| 39 | +DA = 221.43 / 1000 |
| 40 | +BP = 129.03 / 1000 |
| 41 | +DE = 310.31 / 1000 |
| 42 | +t5 = 19.84 |> deg2rad |
| 43 | +
|
| 44 | +@mtkmodel QuarterCarSuspension begin |
| 45 | + @structural_parameters begin |
| 46 | + spring = true |
| 47 | + (jc = [0.5, 0.5, 0.5, 0.7])#, [description = "Joint color"] |
| 48 | + mirror = false |
| 49 | + end |
| 50 | + @parameters begin |
| 51 | + cs = 4000, [description = "Damping constant [Ns/m]"] |
| 52 | + ks = 44000, [description = "Spring constant [N/m]"] |
| 53 | + rod_radius = 0.02 |
| 54 | + jr = 0.03, [description = "Radius of revolute joint"] |
| 55 | + end |
| 56 | + begin |
| 57 | + dir = mirror ? -1 : 1 |
| 58 | + rRod1_ia = AB*normalize([0, -0.1, 0.2dir]) |
| 59 | + rRod2_ib = BC*normalize([0, 0.2, 0dir]) |
| 60 | + end |
| 61 | + @components begin |
| 62 | + r123 = JointRRR(n_a = n*dir, n_b = n*dir, rRod1_ia, rRod2_ib, rod_radius=0.018, rod_color=jc) |
| 63 | + r2 = Revolute(; n=n*dir, radius=jr, color=jc) |
| 64 | + b1 = FixedTranslation(radius = rod_radius, r = CD*normalize([0, -0.1, 0.3dir])) # CD |
| 65 | + chassis = FixedTranslation(r = DA*normalize([0, 0.2, 0.2*sin(t5)*dir]), render=false) |
| 66 | + chassis_frame = Frame() |
| 67 | + |
| 68 | + if spring |
| 69 | + springdamper = SpringDamperParallel(c = ks, d = cs, s_unstretched = 1.3*BC, radius=rod_radius, num_windings=10) |
| 70 | + end |
| 71 | + if spring |
| 72 | + spring_mount_F = FixedTranslation(r = 0.7*CD*normalize([0, -0.1, 0.3dir]), render=false) |
| 73 | + end |
| 74 | + if spring |
| 75 | + spring_mount_E = FixedTranslation(r = 1.3DA*normalize([0, 0.2, 0.2*sin(t5)*dir]), render=true) |
| 76 | + end |
| 77 | + end |
| 78 | + begin |
| 79 | + A = chassis.frame_b |
| 80 | + D = chassis.frame_a |
| 81 | + end |
| 82 | + @equations begin |
| 83 | + # Main loop |
| 84 | + connect(A, r123.frame_a) |
| 85 | + connect(r123.frame_b, b1.frame_b) |
| 86 | + connect(b1.frame_a, r2.frame_b) |
| 87 | + connect(r2.frame_a, D) |
| 88 | +
|
| 89 | + # Spring damper |
| 90 | + if spring |
| 91 | + connect(springdamper.frame_b, spring_mount_E.frame_b) |
| 92 | + connect(b1.frame_a, spring_mount_F.frame_a) |
| 93 | + connect(D, spring_mount_E.frame_a) |
| 94 | + connect(springdamper.frame_a, spring_mount_F.frame_b) |
| 95 | + end |
| 96 | +
|
| 97 | + connect(chassis_frame, chassis.frame_a) |
| 98 | + end |
| 99 | +end |
| 100 | +
|
| 101 | +@mtkmodel ExcitedWheelAssembly begin |
| 102 | + @parameters begin |
| 103 | + rod_radius = 0.02 |
| 104 | + end |
| 105 | + @components begin |
| 106 | + chassis_frame = Frame() |
| 107 | + suspension = QuarterCarSuspension(; rod_radius) |
| 108 | +
|
| 109 | + wheel = SlippingWheel( |
| 110 | + radius = 0.2, |
| 111 | + m = 15, |
| 112 | + I_axis = 0.06, |
| 113 | + I_long = 0.12, |
| 114 | + x0 = 0.0, |
| 115 | + z0 = 0.0, |
| 116 | + der_angles = [0, 0, 0], |
| 117 | + iscut = true, |
| 118 | + ) |
| 119 | +
|
| 120 | + end |
| 121 | + @equations begin |
| 122 | + connect(wheel.frame_a, suspension.r123.frame_ib) |
| 123 | + connect(chassis_frame, suspension.chassis_frame) |
| 124 | + end |
| 125 | +end |
| 126 | +
|
| 127 | +
|
| 128 | +@mtkmodel SuspensionWithExcitationAndMass begin |
| 129 | + @parameters begin |
| 130 | + ms = 1500/4, [description = "Mass of the car [kg]"] |
| 131 | + rod_radius = 0.02 |
| 132 | + end |
| 133 | + @components begin |
| 134 | + world = W() |
| 135 | + mass = Body(m=ms, r_cm = 0.5DA*normalize([0, 0.2, 0.2*sin(t5)])) |
| 136 | + excited_suspension = ExcitedWheelAssembly(; rod_radius) |
| 137 | + prescribed_motion = Pose(; r = [0, 0.1 + 0.1sin(t), 0], R = RotXYZ(0, 0.5sin(t), 0)) |
| 138 | + end |
| 139 | + @equations begin |
| 140 | + connect(prescribed_motion.frame_b, excited_suspension.chassis_frame, mass.frame_a) |
| 141 | + end |
| 142 | +
|
| 143 | +end |
| 144 | +
|
| 145 | +@named model = SuspensionWithExcitationAndMass() |
| 146 | +model = complete(model) |
| 147 | +ssys = structural_simplify(IRSystem(model)) |
| 148 | +display([unknowns(ssys) diag(ssys.mass_matrix)]) |
| 149 | +
|
| 150 | +defs = [ |
| 151 | + model.excited_suspension.suspension.ks => 30*44000 |
| 152 | + model.excited_suspension.suspension.cs => 30*4000 |
| 153 | + model.excited_suspension.suspension.r2.phi => -0.6031*(1) |
| 154 | +] |
| 155 | +prob = ODEProblem(ssys, defs, (0, 2π)) |
| 156 | +sol = solve(prob, Rodas5P(autodiff=false), initializealg = BrownFullBasicInit()) |
| 157 | +@test SciMLBase.successful_retcode(sol) |
| 158 | +Multibody.render(model, sol, show_axis=false, x=-0.8, y=0.7, z=0.1, lookat=[0,0.1,0.0], filename="prescribed_motion.gif") # Video |
| 159 | +nothing # hide |
| 160 | +``` |
| 161 | + |
| 162 | + |
| 163 | + |
| 164 | +Even though we formulated an `ODEProblem` and called `solve`, we do not actually perform any simulation here! If we look at the mass matrix of the system |
| 165 | +```@example PRESCRIBED_POSE |
| 166 | +ssys.mass_matrix |
| 167 | +``` |
| 168 | +we see that it is all zeros. This means that there are no differential equations at all in the system, only algebraic equations. The solver will thus only solve for the algebraic variables using a nonlinear root finder. In general, prescribing the value of some state variables removes the need for the solver to solve for them, which can be useful for simplifying simulations. Using the "simulation" above, we can use the solution object to, e.g., find the compression of the spring and the forces acting on the ground over time. |
| 169 | + |
| 170 | +```@example PRESCRIBED_POSE |
| 171 | +plot(sol, idxs=[model.excited_suspension.suspension.springdamper.s, -model.excited_suspension.suspension.springdamper.f, model.excited_suspension.wheel.wheeljoint.f_n], labels=["Spring length [m]" "Spring force [N] " "Normal force [N]"], layout=(2,1), sp=[1 2 2]) |
| 172 | +``` |
| 173 | +Here, we see that the total spring force and the normal force acting on the ground are not equal, this is due to the spring not applying force only in the vertical direction. We can also compute the slip velocity, the velocity with which the contact between the wheel and the ground is sliding along the ground due to the prescribed motion. |
| 174 | + |
| 175 | +```@example PRESCRIBED_POSE |
| 176 | +wj = model.excited_suspension.wheel.wheeljoint |
| 177 | +plot(sol, idxs=[wj.v_slip, wj.v_slip_long, wj.v_slip_lat], labels=["Slip velocity magnitude" "Longitudinal slip velocity" "Lateral slip velocity"], ylabel="Velocity [m/s]") |
| 178 | +``` |
| 179 | + |
0 commit comments