Skip to content

Commit bcf2934

Browse files
authored
Fullcar (#146)
* add full car model, WIP * handle non-smoothness * add intermediate quatercar with wheel * simplify slightly * rm Main var * add to docstring
1 parent 036c0da commit bcf2934

File tree

4 files changed

+170
-14
lines changed

4 files changed

+170
-14
lines changed

docs/src/examples/suspension.md

Lines changed: 152 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ t5 = 19.84 |> deg2rad
5454
chassis_frame = Frame()
5555
5656
if spring
57-
springdamper = SpringDamperParallel(c = ks, d = cs, s_unstretched = 1.3*BC, radius=rod_radius)
57+
springdamper = SpringDamperParallel(c = ks, d = cs, s_unstretched = 1.3*BC, radius=rod_radius, num_windings=10)
5858
end
5959
if spring
6060
spring_mount_F = FixedTranslation(r = 0.7*CD*normalize([0, -0.1, 0.3dir]), render=false)
@@ -235,14 +235,12 @@ defs = [
235235
model.excited_suspension_r.freq => 10
236236
model.excited_suspension_r.suspension.ks => 30*44000
237237
model.excited_suspension_r.suspension.cs => 30*4000
238-
model.excited_suspension_r.suspension.springdamper.num_windings => 10
239238
model.excited_suspension_r.suspension.r2.phi => -0.6031*(1)
240239
241240
model.excited_suspension_l.amplitude => 0.05
242241
model.excited_suspension_l.freq => 9.5
243242
model.excited_suspension_l.suspension.ks => 30*44000
244243
model.excited_suspension_l.suspension.cs => 30*4000
245-
model.excited_suspension_l.suspension.springdamper.num_windings => 10
246244
model.excited_suspension_l.suspension.r2.phi => -0.6031*(+1)
247245
248246
model.ms => 1500/2
@@ -271,15 +269,18 @@ nothing # hide
271269

272270
## Adding wheels
273271
The example below further extends the example from above by adding wheels to the suspension system. The excitation is not modeled as a time-varying surface profile, provided through the `surface` argument to the [`SlippingWheel`](@ref) component.
274-
The connection between the wheels and the ground form two kinematic loops together with the `body_upright` joint, we thus set both wheels to be cut joints using `iscut=true`.
272+
The connection between the wheels and the ground form two kinematic loops together with the `body_upright` joint, we thus set all wheels to be cut joints using `iscut=true`. We start by adding a wheel to the quarter-car setup and then to the half-car setup.
273+
274+
### Quarter car
275275
```@example suspension
276276
@mtkmodel ExcitedWheelAssembly begin
277277
@structural_parameters begin
278278
mirror = false
279+
iscut = true
279280
end
280281
@parameters begin
281282
rod_radius = 0.02
282-
amplitude = 0.1, [description = "Amplitude of wheel displacement"]
283+
amplitude = 0.02, [description = "Amplitude of wheel displacement"]
283284
freq = 2, [description = "Frequency of wheel displacement"]
284285
end
285286
begin
@@ -297,7 +298,7 @@ The connection between the wheels and the ground form two kinematic loops togeth
297298
x0 = 0.0,
298299
z0 = 0.0,
299300
der_angles = [0, 0, 0],
300-
iscut = true,
301+
iscut = iscut,
301302
# Note the ParentScope qualifier, without this, the parameters are treated as belonging to the wheel.wheel_joint component instead of the ExcitedWheelAssembly
302303
surface = (x,z)->ParentScope(ParentScope(amplitude))*(sin(2pi*ParentScope(ParentScope(freq))*t)), # Excitation from a time-varying surface profile
303304
)
@@ -307,9 +308,51 @@ The connection between the wheels and the ground form two kinematic loops togeth
307308
connect(wheel.frame_a, suspension.r123.frame_ib)
308309
connect(chassis_frame, suspension.chassis_frame)
309310
end
311+
end
312+
313+
314+
@mtkmodel SuspensionWithExcitationAndMass begin
315+
@parameters begin
316+
ms = 1500/4, [description = "Mass of the car [kg]"]
317+
rod_radius = 0.02
318+
end
319+
@components begin
320+
world = W()
321+
mass = Body(m=ms, r_cm = 0.5DA*normalize([0, 0.2, 0.2*sin(t5)]))
322+
excited_suspension = ExcitedWheelAssembly(; rod_radius)
323+
body_upright = Prismatic(n = [0, 1, 0], render = false, state_priority=1000)
324+
end
325+
@equations begin
326+
connect(world.frame_b, body_upright.frame_a)
327+
connect(body_upright.frame_b, excited_suspension.chassis_frame, mass.frame_a)
328+
end
310329
311330
end
312331
332+
@named model = SuspensionWithExcitationAndMass()
333+
model = complete(model)
334+
ssys = structural_simplify(IRSystem(model))
335+
display([unknowns(ssys) diag(ssys.mass_matrix)])
336+
337+
defs = [
338+
model.excited_suspension.amplitude => 0.05
339+
model.excited_suspension.freq => 10
340+
model.excited_suspension.suspension.ks => 30*44000
341+
model.excited_suspension.suspension.cs => 30*4000
342+
model.excited_suspension.suspension.r2.phi => -0.6031*(1)
343+
model.body_upright.s => 0.17
344+
model.body_upright.v => 0.14
345+
]
346+
prob = ODEProblem(ssys, defs, (0, 4))
347+
sol = solve(prob, Rodas5P(autodiff=false), initializealg = BrownFullBasicInit()) # FBDF is inefficient for models including the `SlippingWheel` component due to the discontinuous second-order derivative of the slip model
348+
@test SciMLBase.successful_retcode(sol)
349+
Multibody.render(model, sol, show_axis=false, x=-1.5, y=0.3, z=0.0, lookat=[0,0.1,0.0], timescale=3, filename="suspension_wheel.gif") # Video
350+
nothing # hide
351+
```
352+
![suspension with wheel](suspension_wheel.gif)
353+
354+
### Half car
355+
```@example suspension
313356
@mtkmodel HalfCar begin
314357
@structural_parameters begin
315358
wheel_base = 1
@@ -323,7 +366,7 @@ end
323366
mass = BodyShape(m=ms, r = [0,0,-wheel_base], radius=0.1, color=[0.4, 0.4, 0.4, 0.3])
324367
excited_suspension_r = ExcitedWheelAssembly(; mirror=false, rod_radius)
325368
excited_suspension_l = ExcitedWheelAssembly(; mirror=true, rod_radius)
326-
body_upright = Prismatic(n = [0, 1, 0], render = false, state_priority=2000, iscut=false)
369+
body_upright = Prismatic(n = [0, 1, 0], render = false, state_priority=2000)
327370
body_upright2 = Revolute(n = [1, 0, 0], render = false, state_priority=2000, phi0=0, w0=0, iscut=false)
328371
# body_upright = Planar(n = [1, 0, 0], n_x = [0,0,1], render = false, state_priority=100000, radius=0.01)
329372
end
@@ -349,14 +392,12 @@ defs = [
349392
model.excited_suspension_r.freq => 10
350393
model.excited_suspension_r.suspension.ks => 30*44000
351394
model.excited_suspension_r.suspension.cs => 30*4000
352-
model.excited_suspension_r.suspension.springdamper.num_windings => 10
353395
model.excited_suspension_r.suspension.r2.phi => -0.6031*(1)
354396
355397
model.excited_suspension_l.amplitude => 0.05
356398
model.excited_suspension_l.freq => 9.5
357399
model.excited_suspension_l.suspension.ks => 30*44000
358400
model.excited_suspension_l.suspension.cs => 30*4000
359-
model.excited_suspension_l.suspension.springdamper.num_windings => 10
360401
model.excited_suspension_l.suspension.r2.phi => -0.6031*(+1)
361402
362403
model.ms => 1500
@@ -380,4 +421,105 @@ Multibody.render(model, sol, show_axis=false, x=-1.5, y=0.3, z=0.0, lookat=[0,0.
380421
nothing # hide
381422
```
382423

383-
![suspension with wheels](suspension_halfcar_wheels.gif)
424+
![suspension with wheels](suspension_halfcar_wheels.gif)
425+
426+
# Full car
427+
428+
```@example suspension
429+
transparent_gray = [0.4, 0.4, 0.4, 0.3]
430+
@mtkmodel FullCar begin
431+
@structural_parameters begin
432+
wheel_base = 1
433+
end
434+
@parameters begin
435+
ms = 1500, [description = "Mass of the car [kg]"]
436+
rod_radius = 0.02
437+
end
438+
@components begin
439+
world = W()
440+
front_axle = BodyShape(m=ms/4, r = [0,0,-wheel_base], radius=0.1, color=transparent_gray)
441+
back_front = BodyShape(m=ms/2, r = [2, 0, 0], radius=0.2, color=transparent_gray, isroot=false)
442+
back_axle = BodyShape(m=ms/4, r = [0,0,-wheel_base], radius=0.1, color=transparent_gray)
443+
444+
excited_suspension_fr = ExcitedWheelAssembly(; mirror=false, rod_radius, freq = 10)
445+
excited_suspension_fl = ExcitedWheelAssembly(; mirror=true, rod_radius, freq = 10.5)
446+
447+
excited_suspension_br = ExcitedWheelAssembly(; mirror=false, rod_radius, freq = 10)
448+
excited_suspension_bl = ExcitedWheelAssembly(; mirror=true, rod_radius, freq = 9.7)
449+
450+
body_upright = Prismatic(n = [0, 1, 0], render = false, state_priority=2000)
451+
# body_upright = Planar(n = [1, 0, 0], n_x = [0, 0, 1], render = false, state_priority=2000)
452+
body_upright2 = Universal(n_a = [1, 0, 0], n_b = [0, 0, 1], state_priority=2000)
453+
# body_upright2 = Revolute(n = [1, 0, 0], render = false, state_priority=2000, phi0=0, w0=0)
454+
# body_upright2 = Spherical(render = false)
455+
# body_upright = FreeMotion(state_priority=10)
456+
end
457+
@equations begin
458+
connect(world.frame_b, body_upright.frame_a)
459+
connect(body_upright.frame_b, body_upright2.frame_a)
460+
connect(body_upright2.frame_b, back_axle.frame_cm)
461+
462+
# connect(body_upright.frame_b, back_front.frame_cm)
463+
464+
connect(back_front.frame_a, front_axle.frame_cm)
465+
connect(back_front.frame_b, back_axle.frame_cm)
466+
467+
468+
connect(excited_suspension_fr.chassis_frame, front_axle.frame_a)
469+
connect(excited_suspension_fl.chassis_frame, front_axle.frame_b)
470+
471+
connect(excited_suspension_br.chassis_frame, back_axle.frame_a)
472+
connect(excited_suspension_bl.chassis_frame, back_axle.frame_b)
473+
end
474+
475+
end
476+
477+
@named model = FullCar()
478+
model = complete(model)
479+
@time "simplification" ssys = structural_simplify(IRSystem(model))
480+
481+
482+
defs = [
483+
model.excited_suspension_br.amplitude => 0.02
484+
model.excited_suspension_br.freq => 10
485+
model.excited_suspension_br.suspension.ks => 30*44000
486+
model.excited_suspension_br.suspension.cs => 30*4000
487+
model.excited_suspension_br.suspension.r2.phi => -0.6031*(1)
488+
489+
model.excited_suspension_bl.amplitude => 0.02
490+
model.excited_suspension_bl.freq => 10.5
491+
model.excited_suspension_bl.suspension.ks => 30*44000
492+
model.excited_suspension_bl.suspension.cs => 30*4000
493+
model.excited_suspension_bl.suspension.r2.phi => -0.6031*(+1)
494+
495+
model.excited_suspension_fr.amplitude => 0.02
496+
model.excited_suspension_fr.freq => 10
497+
model.excited_suspension_fr.suspension.ks => 30*44000
498+
model.excited_suspension_fr.suspension.cs => 30*4000
499+
model.excited_suspension_fr.suspension.r2.phi => -0.6031*(1)
500+
501+
model.excited_suspension_fl.amplitude => 0.02
502+
model.excited_suspension_fl.freq => 9.7
503+
model.excited_suspension_fl.suspension.ks => 30*44000
504+
model.excited_suspension_fl.suspension.cs => 30*4000
505+
model.excited_suspension_fl.suspension.r2.phi => -0.6031*(+1)
506+
507+
model.ms => 100
508+
509+
model.body_upright.s => 0.17
510+
model.body_upright.v => 0.14
511+
512+
# model.body_upright.prismatic_y.s => 0.17
513+
# model.body_upright.prismatic_y.v => 0.14
514+
515+
# vec(ori(model.mass.frame_a).R .=> I(3))
516+
]
517+
518+
display(sort(unknowns(ssys), by=string))
519+
520+
prob = ODEProblem(ssys, defs, (0, 3))
521+
sol = solve(prob, Rodas5P(autodiff=false), initializealg = BrownFullBasicInit())
522+
@test SciMLBase.successful_retcode(sol)
523+
import GLMakie
524+
@time "render" Multibody.render(model, sol, show_axis=false, x=-3.5, y=0.5, z=0.15, lookat=[0,0.1,0.0], timescale=2, filename="suspension_fullcar_wheels.gif") # Video
525+
```

ext/Render.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ mutable struct CacheSol
8282
last_t::Float64
8383
function CacheSol(model, sol)
8484
vars = get_all_vars(model) |> unique
85-
Main.vars = vars
85+
# Main.vars = vars
86+
# @show length(vars)
87+
# filter!(v->ModelingToolkit.SymbolicIndexingInterface.is_variable(sol, v), vars) # To work around https://github.com/SciML/ModelingToolkit.jl/issues/3065
88+
# @show length(vars)
8689
values = sol(0.0, idxs=vars)
8790
new(model, sol, Dict(vars .=> values), vars, 0)
8891
end
@@ -768,7 +771,7 @@ function render!(scene, ::Union{typeof(Spring), typeof(SpringDamperParallel)}, s
768771
color = get_color(sys, sol, :blue)
769772
n_wind = sol(sol.t[1], idxs=sys.num_windings)
770773
radius = sol(sol.t[1], idxs=sys.radius) |> Float32
771-
N = sol(sol.t[1], idxs=sys.N) |> Int
774+
N = round(Int, sol(sol.t[1], idxs=sys.N))
772775
thing = @lift begin
773776
r1 = Point3f(r_0a($t))
774777
r2 = Point3f(r_0b($t))

src/PlanarMechanics/components.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ end
499499
500500
Returns a point-symmetric Triple S-Function
501501
502-
A point symmetric interpolation between points `(0, 0), (x_max, y_max) and (x_sat, y_sat)`, provided `x_max < x_sat`. The approximation is done in such a way that the 1st function's derivative is zero at points `(x_max, y_max)` and `(x_sat, y_sat)`. Thus, the 1st function's derivative is continuous for all `x`. The higher derivatives are discontinuous at these points.
502+
A point symmetric interpolation between points `(0, 0), (x_max, y_max) and (x_sat, y_sat)`, provided `x_max < x_sat`. The approximation is done in such a way that the function's 1st derivative is zero at points `(x_max, y_max)` and `(x_sat, y_sat)`. Thus, the function's 1st derivative is continuous for all `x`. The higher derivatives are discontinuous at these points.
503503
504504
```
505505
x_max = 0.2
@@ -546,7 +546,7 @@ end
546546
547547
Returns a S-shaped transition
548548
549-
A smooth transition between points `(x_min, y_min)` and `(x_max, y_max)`. The transition is done in such a way that the 1st function's derivative is continuous for all `x`. The higher derivatives are discontinuous at input points.
549+
A smooth transition between points `(x_min, y_min)` and `(x_max, y_max)`. The transition is done in such a way that the function's 1st derivative is continuous for all `x`. The higher derivatives are discontinuous at input points.
550550
551551
```
552552
x_min = -0.4

src/wheels.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ end
269269
270270
Joint for a wheel with slip rolling on a surface.
271271
272+
!!! tip "Integrator choice"
273+
The slip model contains a discontinuity in the second derivative at the transitions between adhesion and sliding. This can cause problems for integrators, in particular BDF-type integrators.
274+
272275
# Parameters
273276
- `radius`: Radius of the wheel
274277
- `vAdhesion_min`: Minimum adhesion velocity
@@ -277,6 +280,7 @@ Joint for a wheel with slip rolling on a surface.
277280
- `sSlide`: Sliding slippage
278281
- `mu_A`: Friction coefficient at adhesion
279282
- `mu_S`: Friction coefficient at sliding
283+
- `surface`: By default, the wheel is rolling on a flat xz plane. A function `surface = (x, z)->y` may be provided to define a road surface. The function should return the height of the road at `(x, z)`. Note: if a function that depends on parameters is provided, make sure the parameters are scoped appropriately using, e.g., `ParentScope`.
280284
"""
281285
@component function SlipWheelJoint(; name, radius, angles = zeros(3), der_angles=zeros(3), x0=0, y0 = radius, z0=0, sequence = [2, 3, 1], iscut=false, surface = nothing, vAdhesion_min = 0.1, vSlide_min = 0.1, sAdhesion = 0.1, sSlide = 0.1, mu_A = 0.8, mu_S = 0.6, phi_roll = 0, w_roll = 0)
282286
@parameters begin
@@ -436,6 +440,13 @@ Joint for a wheel with slip rolling on a surface.
436440
zeros(3) .~ collect(frame_a.f) + resolve2(Ra, f_wheel_0)
437441
zeros(3) .~ collect(frame_a.tau) +
438442
resolve2(Ra, cross(delta_0, f_wheel_0))]
443+
444+
# continuous_events = [
445+
# v_slip~vAdhesion
446+
# v_slip~vSlide
447+
# v_slip~mu_A
448+
# v_slip~mu_S
449+
# ]
439450
compose(ODESystem(equations, t; name), frame_a)
440451
end
441452

0 commit comments

Comments
 (0)