Skip to content

Commit a8a917e

Browse files
authored
Merge pull request #161 from JuliaComputing/carup
release car from origin
2 parents 4c897d6 + 724c361 commit a8a917e

File tree

4 files changed

+57
-62
lines changed

4 files changed

+57
-62
lines changed

docs/src/examples/suspension.md

Lines changed: 46 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ using ModelingToolkitStandardLibrary.Mechanical.Rotational
296296
@components begin
297297
chassis_frame = Frame()
298298
suspension = QuarterCarSuspension(; spring=true, mirror, rod_radius)
299-
wheel_rotation = Revolute(n = [0, 0, 1], axisflange=true) # Wheel rotation axis
299+
wheel_rotation = Revolute(n = [0, 0, dir], axisflange=true) # Wheel rotation axis
300300
rotational_losses = Rotational.Damper(d = 0.1)
301301
wheel = SlippingWheel(
302302
radius = 0.2,
@@ -306,6 +306,7 @@ using ModelingToolkitStandardLibrary.Mechanical.Rotational
306306
x0 = 0.0,
307307
z0 = 0.0,
308308
state = false,
309+
# iscut = true,
309310
# Note the ParentScope qualifier, without this, the parameters are treated as belonging to the wheel.wheel_joint component instead of the ExcitedWheelAssembly
310311
surface = (x,z)->ParentScope(ParentScope(amplitude))*(sin(2pi*ParentScope(ParentScope(freq))*t)), # Excitation from a time-varying surface profile
311312
)
@@ -346,18 +347,19 @@ ssys = structural_simplify(IRSystem(model))
346347
display([unknowns(ssys) diag(ssys.mass_matrix)])
347348
348349
defs = [
349-
model.excited_suspension.amplitude => 0.05
350-
model.excited_suspension.freq => 10
351-
model.excited_suspension.suspension.ks => 30*44000
352-
model.excited_suspension.suspension.cs => 30*4000
350+
model.excited_suspension.amplitude => 0.02
351+
model.excited_suspension.freq => 3
352+
model.excited_suspension.suspension.ks => 5*44000
353+
model.excited_suspension.suspension.cs => 5*4000
353354
model.excited_suspension.suspension.r2.phi => -0.6031
354355
model.body_upright.s => 0.17
355356
model.body_upright.v => 0.14
356357
]
357358
prob = ODEProblem(ssys, defs, (0, 4))
358359
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
360+
@assert all(sol[model.excited_suspension.wheel.wheeljoint.f_n] .> 0) "Model not valid for negative normal forces"
359361
@test SciMLBase.successful_retcode(sol)
360-
Multibody.render(model, sol, show_axis=false, x=-1.3, y=0.3, z=0.0, lookat=[0,0.1,0.0], timescale=3, filename="suspension_wheel.gif") # Video
362+
Multibody.render(model, sol, show_axis=false, x=-1.3, y=0.3, z=0.0, lookat=[0,0.1,0.0], filename="suspension_wheel.gif") # Video
361363
nothing # hide
362364
```
363365
![suspension with wheel](suspension_wheel.gif)
@@ -399,14 +401,14 @@ model = complete(model)
399401
ssys = structural_simplify(IRSystem(model))
400402
401403
defs = [
402-
model.excited_suspension_r.amplitude => 0.05
403-
model.excited_suspension_r.freq => 10
404+
model.excited_suspension_r.amplitude => 0.015
405+
model.excited_suspension_r.freq => 3
404406
model.excited_suspension_r.suspension.ks => 30*44000
405407
model.excited_suspension_r.suspension.cs => 30*4000
406408
model.excited_suspension_r.suspension.r2.phi => -0.6031
407409
408-
model.excited_suspension_l.amplitude => 0.05
409-
model.excited_suspension_l.freq => 9.5
410+
model.excited_suspension_l.amplitude => 0.015
411+
model.excited_suspension_l.freq => 2.9
410412
model.excited_suspension_l.suspension.ks => 30*44000
411413
model.excited_suspension_l.suspension.cs => 30*4000
412414
model.excited_suspension_l.suspension.r2.phi => -0.6031
@@ -428,7 +430,9 @@ display(sort(unknowns(ssys), by=string))
428430
prob = ODEProblem(ssys, defs, (0, 4))
429431
sol = solve(prob, Rodas5P(autodiff=false), initializealg = ShampineCollocationInit()) # FBDF is inefficient for models including the `SlippingWheel` component due to the discontinuous second-order derivative of the slip model
430432
@test SciMLBase.successful_retcode(sol)
431-
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_halfcar_wheels.gif") # Video
433+
@assert all(sol[model.excited_suspension_r.wheel.wheeljoint.f_n] .> 0) "Model not valid for negative normal forces"
434+
@assert all(sol[model.excited_suspension_l.wheel.wheeljoint.f_n] .> 0) "Model not valid for negative normal forces"
435+
Multibody.render(model, sol, show_axis=false, x=-1.5, y=0.3, z=0.0, lookat=[0,0.1,0.0], filename="suspension_halfcar_wheels.gif") # Video
432436
nothing # hide
433437
```
434438

@@ -449,29 +453,16 @@ transparent_gray = [0.4, 0.4, 0.4, 0.3]
449453
@components begin
450454
world = W()
451455
front_axle = BodyShape(m=ms/4, r = [0,0,-wheel_base], radius=0.1, color=transparent_gray)
452-
back_front = BodyShape(m=ms/2, r = [2, 0, 0], radius=0.2, color=transparent_gray, isroot=false)
456+
back_front = BodyShape(m=ms/2, r = [2, 0, 0], radius=0.2, color=transparent_gray, isroot=true, state_priority=Inf, quat=false)
453457
back_axle = BodyShape(m=ms/4, r = [0,0,-wheel_base], radius=0.1, color=transparent_gray)
454458
455459
excited_suspension_fr = ExcitedWheelAssembly(; mirror=false, rod_radius, freq = 10)
456460
excited_suspension_fl = ExcitedWheelAssembly(; mirror=true, rod_radius, freq = 10.5)
457461
458462
excited_suspension_br = ExcitedWheelAssembly(; mirror=false, rod_radius, freq = 10)
459463
excited_suspension_bl = ExcitedWheelAssembly(; mirror=true, rod_radius, freq = 9.7)
460-
461-
body_upright = Prismatic(n = [0, 1, 0], render = false, state_priority=2000)
462-
# body_upright = Planar(n = [1, 0, 0], n_x = [0, 0, 1], render = false, state_priority=2000)
463-
body_upright2 = Universal(n_a = [1, 0, 0], n_b = [0, 0, 1], state_priority=2000)
464-
# body_upright2 = Revolute(n = [1, 0, 0], render = false, state_priority=2000, phi0=0, w0=0)
465-
# body_upright2 = Spherical(render = false)
466-
# body_upright = FreeMotion(state_priority=10)
467464
end
468465
@equations begin
469-
connect(world.frame_b, body_upright.frame_a)
470-
connect(body_upright.frame_b, body_upright2.frame_a)
471-
connect(body_upright2.frame_b, back_axle.frame_cm)
472-
473-
# connect(body_upright.frame_b, back_front.frame_cm)
474-
475466
connect(back_front.frame_a, front_axle.frame_cm)
476467
connect(back_front.frame_b, back_axle.frame_cm)
477468
@@ -489,52 +480,52 @@ model = complete(model)
489480
@time "simplification" ssys = structural_simplify(IRSystem(model))
490481
491482
defs = [
492-
model.excited_suspension_br.wheel.wheeljoint.v_small => 10
493-
model.excited_suspension_br.amplitude => 0.02
494-
model.excited_suspension_br.freq => 10
495-
model.excited_suspension_br.suspension.ks => 30*44000
496-
model.excited_suspension_br.suspension.cs => 30*4000
483+
model.excited_suspension_br.wheel.wheeljoint.v_small => 1e-3
484+
model.excited_suspension_br.amplitude => 0.015
485+
model.excited_suspension_br.freq => 3.1
486+
model.excited_suspension_br.suspension.ks => 5*44000
487+
model.excited_suspension_br.suspension.cs => 5*4000
497488
model.excited_suspension_br.suspension.r2.phi => -0.6031
498489
499-
model.excited_suspension_bl.wheel.wheeljoint.v_small => 10
500-
model.excited_suspension_bl.amplitude => 0.02
501-
model.excited_suspension_bl.freq => 10.5
502-
model.excited_suspension_bl.suspension.ks => 30*44000
503-
model.excited_suspension_bl.suspension.cs => 30*4000
490+
model.excited_suspension_bl.wheel.wheeljoint.v_small => 1e-3
491+
model.excited_suspension_bl.amplitude => 0.015
492+
model.excited_suspension_bl.freq => 3.2
493+
model.excited_suspension_bl.suspension.ks => 5*44000
494+
model.excited_suspension_bl.suspension.cs => 5*4000
504495
model.excited_suspension_bl.suspension.r2.phi => -0.6031
505496
506-
model.excited_suspension_fr.wheel.wheeljoint.v_small => 10
507-
model.excited_suspension_fr.amplitude => 0.02
508-
model.excited_suspension_fr.freq => 10
509-
model.excited_suspension_fr.suspension.ks => 30*44000
510-
model.excited_suspension_fr.suspension.cs => 30*4000
497+
model.excited_suspension_fr.wheel.wheeljoint.v_small => 1e-3
498+
model.excited_suspension_fr.amplitude => 0.015
499+
model.excited_suspension_fr.freq => 2.9
500+
model.excited_suspension_fr.suspension.ks => 5*44000
501+
model.excited_suspension_fr.suspension.cs => 5*4000
511502
model.excited_suspension_fr.suspension.r2.phi => -0.6031
512503
513-
model.excited_suspension_fl.wheel.wheeljoint.v_small => 10
514-
model.excited_suspension_fl.amplitude => 0.02
515-
model.excited_suspension_fl.freq => 9.7
516-
model.excited_suspension_fl.suspension.ks => 30*44000
517-
model.excited_suspension_fl.suspension.cs => 30*4000
504+
model.excited_suspension_fl.wheel.wheeljoint.v_small => 1e-3
505+
model.excited_suspension_fl.amplitude => 0.015
506+
model.excited_suspension_fl.freq => 2.8
507+
model.excited_suspension_fl.suspension.ks => 5*44000
508+
model.excited_suspension_fl.suspension.cs => 5*4000
518509
model.excited_suspension_fl.suspension.r2.phi => -0.6031
519510
520-
model.ms => 100
521-
522-
model.body_upright.s => 0.17
523-
model.body_upright.v => 0.14
524-
525-
# model.body_upright.prismatic_y.s => 0.17
526-
# model.body_upright.prismatic_y.v => 0.14
511+
model.ms => 1500
527512
528-
# vec(ori(model.mass.frame_a).R .=> I(3))
513+
model.back_front.body.r_0[1] => -2.0
514+
model.back_front.body.r_0[2] => 0.193
515+
model.back_front.body.r_0[3] => 0.0
516+
model.back_front.body.v_0[1] => 1
529517
]
530518
531519
display(sort(unknowns(ssys), by=string))
532520
533521
prob = ODEProblem(ssys, defs, (0, 3))
534-
sol = solve(prob, Rodas5P(autodiff=false), initializealg = BrownFullBasicInit(), u0 = prob.u0.+1e-6.*randn.())
522+
sol = solve(prob, Rodas5P(autodiff=false), initializealg = BrownFullBasicInit())
535523
@test SciMLBase.successful_retcode(sol)
524+
@assert all(reduce(hcat, sol[[model.excited_suspension_bl.wheel.wheeljoint.f_n, model.excited_suspension_br.wheel.wheeljoint.f_n, model.excited_suspension_fl.wheel.wheeljoint.f_n, model.excited_suspension_fr.wheel.wheeljoint.f_n]]) .> 0) "Model not valid for negative normal forces"
525+
526+
# plot(sol, layout=length(unknowns(ssys)), size=(1900,1200))
536527
import GLMakie
537-
Multibody.render(model, sol, show_axis=false, x=-3.5, y=0.5, z=0.15, lookat=[0,0.1,0.0], timescale=3, filename="suspension_fullcar_wheels.gif") # Video
528+
Multibody.render(model, sol, show_axis=false, x=-1.5, y=0.7, z=0.15, lookat=[0,0.1,0.0], timescale=1, filename="suspension_fullcar_wheels.gif") # Video
538529
nothing # hide
539530
```
540531

src/PlanarMechanics/utils.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ end
1818
Base.@doc """
1919
Frame(;name)
2020
21-
Coordinate system (2-dim.) fixed to the component with one cut-force and cut-torque
21+
Coordinate system (2-dim.) fixed to the component with one cut-force and cut-torque.
22+
All variables are resolved in the planar world frame.
2223
2324
# Variables:
2425
- `x`: [m] x position

src/components.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ end
302302
303303
Representing a body with 3 translational and 3 rotational degrees-of-freedom.
304304
305-
This component has a single frame, `frame_a`. To represent bodies with more than one frame, see [`BodyShape`](@ref), [`BodyCylinder`](@ref), [`BodyBox`](@ref).
305+
This component has a single frame, `frame_a`. To represent bodies with more than one frame, see [`BodyShape`](@ref), [`BodyCylinder`](@ref), [`BodyBox`](@ref). The inertia tensor is defined with respect to a coordinate system that is parallel to `frame_a` with the origin at the center of mass of the body.
306306
307307
# Performance optimization
308308
- `sparse_I`: If `true`, the zero elements of the inerita matrix are considered "structurally zero", and this fact is used to optimize performance. When this option is enabled, the elements of the inertia matrix that were zero when the component was created cannot changed without reinstantiating the component. This performance optimization may be useful, e.g., when the inertia matrix is known to be diagonal.

src/wheels.jl

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,13 +265,16 @@ end
265265

266266

267267
"""
268-
SlipWheelJoint(; name, radius, angles = zeros(3), der_angles = zeros(3), x0 = 0, y0 = radius, z0 = 0, sequence, 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)
268+
SlipWheelJoint(; name, radius, angles = zeros(3), der_angles = zeros(3), x0 = 0, y0 = radius, z0 = 0, sequence, iscut = false, surface = nothing, vAdhesion_min = 0.1, vSlide_min = 0.1, sAdhesion = 0.04, sSlide = 0.12, mu_A = 0.8, mu_S = 0.6, phi_roll = 0, w_roll = 0)
269269
270270
Joint for a wheel with slip rolling on a surface.
271271
272272
!!! tip "Integrator choice"
273273
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.
274274
275+
!!! warn "Normal force"
276+
The wheel cannot leave the ground. Make sure that the normal force `f_n` never becomes negative.
277+
275278
# Parameters
276279
- `radius`: Radius of the wheel
277280
- `vAdhesion_min`: Minimum adhesion velocity
@@ -286,7 +289,7 @@ Joint for a wheel with slip rolling on a surface.
286289
# State and iscut
287290
When the wheel is mounted on an axis that is rooted, one may either supply `state=false` or `iscut = true`. With `state = false`, the angular state variables are not included in the wheel and there is thus no kinematic chain introduced. This reduces the total number of variables in the system. if the angular variables are required, one may instead pass `iscut=true` to cut the kinematic loop that is introduced when coupling the angles of the wheel to the orientation of the `frame_a`, unless this is cut elsewhere.
288291
"""
289-
@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, v_small = 1e-5, state=true)
292+
@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.05, vSlide_min = 0.15, sAdhesion = 0.04, sSlide = 0.12, mu_A = 0.8, mu_S = 0.6, phi_roll = 0, w_roll = 0, v_small = 1e-5, state=true)
290293
@parameters begin
291294
radius = radius, [description = "Radius of the wheel"]
292295
vAdhesion_min = vAdhesion_min, [description = "Minimum adhesion velocity"]
@@ -402,18 +405,18 @@ When the wheel is mounted on an axis that is rooted, one may either supply `stat
402405

403406
]
404407
else
405-
w_roll ~ -Ra.w[3] # w: Absolute angular velocity of local frame, resolved in local frame
408+
w_roll ~ Ra.w[3] # w: Absolute angular velocity of local frame, resolved in local frame
406409
end
407410

408411
# frame_a.R is computed from generalized coordinates
409412
collect(frame_a.r_0) .~ [x, y, z]
410413

411414

412-
# Coordinate system at contact point (e_long_0, e_lat_0, e_n_0), resolved on world frame
415+
# Coordinate system at contact point (e_long_0, e_lat_0, e_n_0), resolved in world frame
413416
e_axis_0 .~ resolve1(Ra, [0, 0, 1])
414417
aux .~ (cross(e_n_0, e_axis_0))
415418
e_long_0 .~ (aux ./ _norm(aux))
416-
e_lat_0 .~ (cross(e_long_0, e_n_0))
419+
e_lat_0 .~ -(cross(e_long_0, e_n_0)) # wheel rotation axis and lateral axis are opposite
417420

418421
# Determine point on road where the wheel is in contact with the road
419422
delta_0 .~ r_road_0 - frame_a.r_0

0 commit comments

Comments
 (0)