Skip to content

Commit d29b21b

Browse files
authored
introduce stateless option in slip wheels (#150)
* introduce stateless option in slip wheels * add damping to wheel rotation * add some tests * tweak tol * move from self-hosted to ubuntu-latest since we made repo public * update docs manifest * rm explicit dev * not ssh
1 parent d03849f commit d29b21b

File tree

8 files changed

+167
-133
lines changed

8 files changed

+167
-133
lines changed

.github/workflows/CI.yml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,30 @@ env:
1313
jobs:
1414
test:
1515
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
16-
runs-on: self-hosted
16+
runs-on: ubuntu-latest
1717
strategy:
1818
fail-fast: false
1919
matrix:
2020
version:
2121
- '1'
2222
os:
23-
- self-hosted
23+
- ubuntu-latest
2424
arch:
2525
- x64
2626
steps:
2727
- name: Set debug env
2828
run: export JULIA_DEBUG="loading"
2929
- uses: actions/[email protected]
30+
- uses: julia-actions/cache@v1
3031
- uses: julia-actions/setup-julia@v1
3132
with:
3233
version: ${{ matrix.version }}
3334
arch: ${{ matrix.arch }}
34-
- uses: webfactory/[email protected]
35-
with:
36-
ssh-private-key: ${{ secrets.JULIASIM_REGISTRY_SSH_KEY }}
3735
- uses: PumasAI/add-private-registry@main
3836
with:
3937
juliahub_token_encoded: ${{ secrets.JULIAHUB_TOKEN_ENCODED }}
40-
private_registry_name: JuliaSimRegistry
41-
private_registry_uuid: 309a7822-a73e-4490-9504-7d1983f27685
38+
private_registry_name: JuliaHubRegistry
39+
private_registry_uuid: de52bcdf-fcb2-40cf-a397-3d64b64f4d9c
4240
- uses: julia-actions/julia-buildpkg@v1
4341
- uses: julia-actions/julia-runtest@v1
4442
- uses: julia-actions/julia-processcoverage@v1

.github/workflows/CompatHelper.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
workflow_dispatch:
66
jobs:
77
CompatHelper:
8-
runs-on: self-hosted
8+
runs-on: ubuntu-latest
99
steps:
1010
- name: Pkg.add("CompatHelper")
1111
run: julia -e 'using Pkg; Pkg.add("CompatHelper")'

.github/workflows/Documentation.yml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,21 @@ jobs:
1717
permissions:
1818
contents: write
1919
statuses: write
20-
runs-on: self-hosted
20+
runs-on: ubuntu-latest
2121
steps:
2222
- uses: actions/[email protected]
23+
- uses: julia-actions/cache@v1
2324
- uses: julia-actions/setup-julia@latest
2425
with:
2526
version: '1'
26-
- uses: webfactory/[email protected]
27-
with:
28-
ssh-private-key: ${{ secrets.JULIASIM_REGISTRY_SSH_KEY }}
2927
- uses: PumasAI/add-private-registry@main
3028
with:
3129
juliahub_token_encoded: ${{ secrets.JULIAHUB_TOKEN_ENCODED }}
32-
private_registry_name: JuliaSimRegistry
33-
private_registry_uuid: 309a7822-a73e-4490-9504-7d1983f27685
30+
private_registry_name: JuliaHubRegistry
31+
private_registry_uuid: de52bcdf-fcb2-40cf-a397-3d64b64f4d9c
3432
- name: Install dependencies
3533
run: DISPLAY=:0 xvfb-run --auto-servernum -s '-screen 0 1024x768x24' julia --project=docs -e 'using Pkg;
36-
Pkg.setprotocol!(; domain = "github.com", protocol = "ssh");
37-
Pkg.add(PackageSpec(name = "JuliaSimCompiler", url = "https://github.com/JuliaComputing/JuliaSimCompiler.jl", rev = "master"));
34+
Pkg.add(PackageSpec(name = "ModelingToolkitStandardLibrary", url = "https://github.com/SciML/ModelingToolkitStandardLibrary.jl", rev = "main"));
3835
Pkg.develop(PackageSpec(path=pwd()));
3936
Pkg.instantiate()'
4037
- name: Build and deploy

.github/workflows/TagBot.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
github.actor == 'ChrisRackauckas' &&
1313
startsWith(github.event.comment.body, 'Triggering TagBot')
1414
)
15-
runs-on: self-hosted
15+
runs-on: ubuntu-latest
1616
steps:
1717
- uses: JuliaRegistries/TagBot@v1
1818
with:

docs/Manifest.toml

Lines changed: 82 additions & 80 deletions
Large diffs are not rendered by default.

docs/src/examples/suspension.md

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ nothing # hide
183183

184184
![suspension](suspension.gif)
185185

186+
Due to the high excitation frequency, we make use of the argument `timescale = 3` when we render the 3D animation to slow down the animation by a factor of 3.
187+
186188

187189
# Half-car suspension
188190

@@ -235,13 +237,13 @@ defs = [
235237
model.excited_suspension_r.freq => 10
236238
model.excited_suspension_r.suspension.ks => 30*44000
237239
model.excited_suspension_r.suspension.cs => 30*4000
238-
model.excited_suspension_r.suspension.r2.phi => -0.6031*(1)
240+
model.excited_suspension_r.suspension.r2.phi => -0.6031
239241
240242
model.excited_suspension_l.amplitude => 0.05
241243
model.excited_suspension_l.freq => 9.5
242244
model.excited_suspension_l.suspension.ks => 30*44000
243245
model.excited_suspension_l.suspension.cs => 30*4000
244-
model.excited_suspension_l.suspension.r2.phi => -0.6031*(+1)
246+
model.excited_suspension_l.suspension.r2.phi => -0.6031
245247
246248
model.ms => 1500/2
247249
@@ -269,10 +271,15 @@ nothing # hide
269271

270272
## Adding wheels
271273
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.
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.
274+
The connection between the wheels and the ground form two kinematic loops together with the `body_upright` joint, which we can handle in one of two ways.
275+
1. include a cut joint in the loop, e.g., by setting all wheels to be cut joints using `iscut=true`.
276+
2. Remove angular state variables from the wheels by passing `state = false`. Since we do not need the angular state variables in this case, we choose this option which reduces the total number of unknowns to solve for.
277+
278+
We start by adding a wheel to the quarter-car setup and then to the half-car setup.
273279

274280
### Quarter car
275281
```@example suspension
282+
using ModelingToolkitStandardLibrary.Mechanical.Rotational
276283
@mtkmodel ExcitedWheelAssembly begin
277284
@structural_parameters begin
278285
mirror = false
@@ -289,24 +296,28 @@ The connection between the wheels and the ground form two kinematic loops togeth
289296
@components begin
290297
chassis_frame = Frame()
291298
suspension = QuarterCarSuspension(; spring=true, mirror, rod_radius)
292-
299+
wheel_rotation = Revolute(n = [0, 0, 1], axisflange=true) # Wheel rotation axis
300+
rotational_losses = Rotational.Damper(d = 0.1)
293301
wheel = SlippingWheel(
294302
radius = 0.2,
295303
m = 15,
296-
I_axis = 0.06,
297-
I_long = 0.12,
304+
I_axis = 0.8,
305+
I_long = 0.8,
298306
x0 = 0.0,
299307
z0 = 0.0,
300-
der_angles = [0, 0, 0],
301-
iscut = iscut,
308+
state = false,
302309
# Note the ParentScope qualifier, without this, the parameters are treated as belonging to the wheel.wheel_joint component instead of the ExcitedWheelAssembly
303310
surface = (x,z)->ParentScope(ParentScope(amplitude))*(sin(2pi*ParentScope(ParentScope(freq))*t)), # Excitation from a time-varying surface profile
304311
)
305312
306313
end
307314
@equations begin
308-
connect(wheel.frame_a, suspension.r123.frame_ib)
315+
connect(wheel.frame_a, wheel_rotation.frame_b)
316+
connect(wheel_rotation.frame_a, suspension.r123.frame_ib)
309317
connect(chassis_frame, suspension.chassis_frame)
318+
319+
connect(rotational_losses.flange_a, wheel_rotation.axis)
320+
connect(rotational_losses.flange_b, wheel_rotation.support)
310321
end
311322
end
312323
@@ -339,7 +350,7 @@ defs = [
339350
model.excited_suspension.freq => 10
340351
model.excited_suspension.suspension.ks => 30*44000
341352
model.excited_suspension.suspension.cs => 30*4000
342-
model.excited_suspension.suspension.r2.phi => -0.6031*(1)
353+
model.excited_suspension.suspension.r2.phi => -0.6031
343354
model.body_upright.s => 0.17
344355
model.body_upright.v => 0.14
345356
]
@@ -392,13 +403,13 @@ defs = [
392403
model.excited_suspension_r.freq => 10
393404
model.excited_suspension_r.suspension.ks => 30*44000
394405
model.excited_suspension_r.suspension.cs => 30*4000
395-
model.excited_suspension_r.suspension.r2.phi => -0.6031*(1)
406+
model.excited_suspension_r.suspension.r2.phi => -0.6031
396407
397408
model.excited_suspension_l.amplitude => 0.05
398409
model.excited_suspension_l.freq => 9.5
399410
model.excited_suspension_l.suspension.ks => 30*44000
400411
model.excited_suspension_l.suspension.cs => 30*4000
401-
model.excited_suspension_l.suspension.r2.phi => -0.6031*(+1)
412+
model.excited_suspension_l.suspension.r2.phi => -0.6031
402413
403414
model.ms => 1500
404415
@@ -478,29 +489,33 @@ model = complete(model)
478489
@time "simplification" ssys = structural_simplify(IRSystem(model))
479490
480491
defs = [
492+
model.excited_suspension_br.wheel.wheeljoint.v_small => 10
481493
model.excited_suspension_br.amplitude => 0.02
482494
model.excited_suspension_br.freq => 10
483495
model.excited_suspension_br.suspension.ks => 30*44000
484496
model.excited_suspension_br.suspension.cs => 30*4000
485-
model.excited_suspension_br.suspension.r2.phi => -0.6031*(1)
497+
model.excited_suspension_br.suspension.r2.phi => -0.6031
486498
499+
model.excited_suspension_bl.wheel.wheeljoint.v_small => 10
487500
model.excited_suspension_bl.amplitude => 0.02
488501
model.excited_suspension_bl.freq => 10.5
489502
model.excited_suspension_bl.suspension.ks => 30*44000
490503
model.excited_suspension_bl.suspension.cs => 30*4000
491-
model.excited_suspension_bl.suspension.r2.phi => -0.6031*(+1)
504+
model.excited_suspension_bl.suspension.r2.phi => -0.6031
492505
506+
model.excited_suspension_fr.wheel.wheeljoint.v_small => 10
493507
model.excited_suspension_fr.amplitude => 0.02
494508
model.excited_suspension_fr.freq => 10
495509
model.excited_suspension_fr.suspension.ks => 30*44000
496510
model.excited_suspension_fr.suspension.cs => 30*4000
497-
model.excited_suspension_fr.suspension.r2.phi => -0.6031*(1)
511+
model.excited_suspension_fr.suspension.r2.phi => -0.6031
498512
513+
model.excited_suspension_fl.wheel.wheeljoint.v_small => 10
499514
model.excited_suspension_fl.amplitude => 0.02
500515
model.excited_suspension_fl.freq => 9.7
501516
model.excited_suspension_fl.suspension.ks => 30*44000
502517
model.excited_suspension_fl.suspension.cs => 30*4000
503-
model.excited_suspension_fl.suspension.r2.phi => -0.6031*(+1)
518+
model.excited_suspension_fl.suspension.r2.phi => -0.6031
504519
505520
model.ms => 100
506521
@@ -516,12 +531,12 @@ defs = [
516531
display(sort(unknowns(ssys), by=string))
517532
518533
prob = ODEProblem(ssys, defs, (0, 3))
519-
sol = solve(prob, Rodas5P(autodiff=false), initializealg = BrownFullBasicInit())
534+
sol = solve(prob, Rodas5P(autodiff=false), initializealg = BrownFullBasicInit(), u0 = prob.u0.+1e-6.*randn.())
520535
@test SciMLBase.successful_retcode(sol)
521536
import GLMakie
522-
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
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
538+
nothing # hide
523539
```
524540

525541
![suspension with 4 wheels](suspension_fullcar_wheels.gif)
526-
```
527542

src/wheels.jl

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,12 @@ Joint for a wheel with slip rolling on a surface.
281281
- `mu_A`: Friction coefficient at adhesion
282282
- `mu_S`: Friction coefficient at sliding
283283
- `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`.
284+
- `state`: (structural) whether or not the component has angular state variables. Default is `true`.
285+
286+
# State and iscut
287+
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.
284288
"""
285-
@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)
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)
286290
@parameters begin
287291
radius = radius, [description = "Radius of the wheel"]
288292
vAdhesion_min = vAdhesion_min, [description = "Minimum adhesion velocity"]
@@ -291,6 +295,7 @@ Joint for a wheel with slip rolling on a surface.
291295
sSlide = sSlide, [description = "Sliding slippage"]
292296
mu_A = mu_A, [description = "Friction coefficient at adhesion"]
293297
mu_S = mu_S, [description = "Friction coefficient at sliding"]
298+
v_small = v_small, [description = "Small value added to v_slip to avoid division by zero in slip model."]
294299
end
295300
@variables begin
296301
(x(t) = x0), [state_priority = 15, description = "x-position of the wheel axis"]
@@ -361,8 +366,8 @@ Joint for a wheel with slip rolling on a surface.
361366

362367
angles,der_angles,r_road_0,f_wheel_0,e_axis_0,delta_0,e_n_0,e_lat_0,e_long_0,e_s_0,v_0,w_0,vContact_0,aux = collect.((angles,der_angles,r_road_0,f_wheel_0,e_axis_0,delta_0,e_n_0,e_lat_0,e_long_0,e_s_0,v_0,w_0,vContact_0,aux))
363368

364-
@named frame_a = Frame(varw=true)
365-
Ra = ori(frame_a, true)
369+
@named frame_a = Frame(varw=state)
370+
Ra = ori(frame_a, state)
366371

367372
Rarot = axes_rotations(sequence, angles, -der_angles) # The - is the neg_w change
368373

@@ -386,18 +391,25 @@ Joint for a wheel with slip rolling on a surface.
386391

387392
equations = [
388393
equations;
389-
connect_orientation(Ra, Rarot; iscut) # Ra ~ Rarot
390-
Ra.w ~ Rarot.w
391394

392-
phi_roll ~ angles[2]
393-
w_roll ~ D(phi_roll)
395+
if state
396+
[
397+
connect_orientation(Ra, Rarot; iscut)
398+
Ra.w ~ Rarot.w
399+
phi_roll ~ angles[2]
400+
w_roll ~ D(phi_roll)
401+
der_angles .~ D.(angles)
402+
403+
]
404+
else
405+
w_roll ~ -Ra.w[3] # w: Absolute angular velocity of local frame, resolved in local frame
406+
end
394407

395408
# frame_a.R is computed from generalized coordinates
396409
collect(frame_a.r_0) .~ [x, y, z]
397-
der_angles .~ D.(angles)
398410

399411

400-
# Coordinate system at contact point (e_long_0, e_lat_0, e_n_0)
412+
# Coordinate system at contact point (e_long_0, e_lat_0, e_n_0), resolved on world frame
401413
e_axis_0 .~ resolve1(Ra, [0, 0, 1])
402414
aux .~ (cross(e_n_0, e_axis_0))
403415
e_long_0 .~ (aux ./ _norm(aux))
@@ -411,19 +423,19 @@ Joint for a wheel with slip rolling on a surface.
411423
# One holonomic positional constraint equation (no penetration in to the ground)
412424
0 ~ radius - delta_0'cross(e_long_0, e_axis_0)
413425

414-
# Slip velocities
426+
# Slip velocities (world frame)
415427
v_0 .~ D.(frame_a.r_0)
416428
w_0 .~ angular_velocity1(Ra)
417429
vContact_0 .~ v_0 + cross(w_0, delta_0)
418430

419-
# Contact dynamics =============================================
431+
# Contact dynamics (world frame) ===============================
420432

421433
v_slip_lat ~ vContact_0' * e_lat_0
422434
v_slip_long ~ vContact_0' * e_long_0
423435
# v_slip_lat ~ v_lat - 0
424436
# v_slip_long ~ v_long - radius * w_roll
425437

426-
v_slip ~ sqrt(v_slip_long^2 + v_slip_lat^2) + 0.00001
438+
v_slip ~ sqrt(v_slip_long^2 + v_slip_lat^2) + v_small
427439
# -f_long * radius ~ flange_a.tau # No longer needed?
428440
# frame_a.tau ~ 0
429441
vAdhesion ~ max(vAdhesion_min, sAdhesion * abs(radius * w_roll))
@@ -433,14 +445,16 @@ Joint for a wheel with slip rolling on a surface.
433445
f_long ~ -f * v_slip_long / v_slip
434446
f_lat ~ -f * v_slip_lat / v_slip
435447

436-
# Contact force
448+
# Contact force (world frame)
437449
f_wheel_0 .~ f_n * e_n_0 + f_lat * e_lat_0 + f_long * e_long_0
438450

439451
# Force and torque balance at the wheel center
440452
zeros(3) .~ collect(frame_a.f) + resolve2(Ra, f_wheel_0)
441453
zeros(3) .~ collect(frame_a.tau) +
442454
resolve2(Ra, cross(delta_0, f_wheel_0))]
443455

456+
457+
444458
# continuous_events = [
445459
# v_slip~vAdhesion
446460
# v_slip~vSlide
@@ -465,6 +479,7 @@ Wheel with slip rolling on a surface.
465479
- `width`: Width of the wheel (for rendering)
466480
- `x0`: Initial x-position of the wheel axis
467481
- `z0`: Initial z-position of the wheel axis
482+
- `state`: (structural) whether or not the component has angular state variables.
468483
469484
# Variables
470485
- `x`: x-position of the wheel axis
@@ -479,8 +494,8 @@ Wheel with slip rolling on a surface.
479494
See [Docs: Wheels](https://help.juliahub.com/multibody/dev/examples/wheel/)
480495
"""
481496
@component function SlippingWheel(; name, radius, m, I_axis, I_long, width = 0.035, x0=0, z0=0,
482-
angles = zeros(3), der_angles = zeros(3), kwargs...)
483-
@named wheeljoint = SlipWheelJoint(; radius, angles, x0, z0, der_angles, kwargs...)
497+
angles = zeros(3), der_angles = zeros(3), state = true, kwargs...)
498+
@named wheeljoint = SlipWheelJoint(; radius, angles, x0, z0, der_angles, state, kwargs...)
484499
@named begin
485500
frame_a = Frame()
486501
body = Body(r_cm = [0, 0, 0],
@@ -512,10 +527,13 @@ See [Docs: Wheels](https://help.juliahub.com/multibody/dev/examples/wheel/)
512527

513528
equations = Equation[wheeljoint.x ~ x
514529
wheeljoint.z ~ z
515-
collect(wheeljoint.angles) .~ collect(angles)
516-
collect(wheeljoint.der_angles) .~ collect(der_angles)
517530
connect(body.frame_a, frame_a)
518531
connect(wheeljoint.frame_a, frame_a)]
532+
if state
533+
append!(equations, [collect(wheeljoint.angles) .~ collect(angles)
534+
collect(wheeljoint.der_angles) .~ collect(der_angles)]
535+
)
536+
end
519537
compose(ODESystem(equations, t; name), frame_a, wheeljoint, body)
520538
end
521539

0 commit comments

Comments
 (0)