Skip to content

Commit 1848db7

Browse files
authored
Merge pull request #124 from JuliaComputing/subs_const
add compile-time optimization based on parameter values
2 parents b71a49b + f366903 commit 1848db7

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

src/Multibody.jl

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ using StaticArrays
1414
export Rotational, Translational
1515

1616
export render, render!
17+
export subs_constants
1718

1819
"""
1920
A helper function that calls `collect` on all parameters in a vector of parameters in a smart way
@@ -67,6 +68,64 @@ function get_systemtype(sys)
6768
eval(meta.type)
6869
end
6970

71+
"""
72+
subs_constants(model, c=[0, 1]; ssys = structural_simplify(IRSystem(model)), defs = defaults(model))
73+
74+
A value-dependent compile-time optimization. Replace parameters in the model that have a default value contained in `c` with their value.
75+
76+
Many parameters in multibody models have a sparse structure, e.g., rotation axes like `[1, 0, 0]`, or diagonal mass matrices etc. Specializing the generated code to this structure may increase performance, sometimes up to 2x. This function replaces all parameters that have a value contained in `c`, which defaults to `[0, 1]`, with their value.
77+
78+
This performance optimization is primarily beneficial when the runtime of the simulation exceeds the compilation time of the model. For non-multibody models, sparse parameter structure is less common and this optimization is this not likely to be beneficial.
79+
80+
# Drawbacks
81+
There are two main drawbacks to performing this optimization
82+
- Parameters that have been replaced **cannot be changed** after the optimization has been performed, without recompiling the model using `structural_simplify`.
83+
- The value of the repalced parameters are no longer accessible in the solution object. This typically means that **3D animations cannot be rendered** for models with replaced parameters.
84+
85+
# Example usage
86+
```
87+
@named robot = Robot6DOF()
88+
robot = complete(robot)
89+
ssys = structural_simplify(IRSystem(robot))
90+
ssys = Multibody.subs_constants(model, [0, 1]; ssys)
91+
```
92+
93+
If this optimization is to be performed repeatedly for several simulations of the same model, the indices of the substituted parameters can be stored and reused, call the lower-level function `Multibody.find_defaults_with_val` with the same signature as this function to obtain these indices, and then call `JuliaSimCompiler.freeze_parameters(ssys, inds)` with the indices to freeze the parameters.
94+
"""
95+
function subs_constants(model, c=[0, 1]; ssys = structural_simplify(IRSystem(model)), kwargs...)
96+
inds = find_defaults_with_val(model, c; ssys, kwargs...)
97+
ssys = JuliaSimCompiler.freeze_parameters(ssys, inds)
98+
end
99+
100+
function find_defaults_with_val(model, c=[0, 1]; defs = defaults(model), ssys = structural_simplify(IRSystem(model)))
101+
kvpairs = map(collect(pairs(defs))) do (key, val)
102+
if val isa AbstractArray
103+
string.(collect(key)) .=> collect(val)
104+
else
105+
string(key) => val
106+
end
107+
end
108+
kvpairs = reduce(vcat, kvpairs)
109+
110+
p = parameters(ssys)
111+
stringp = string.(p)
112+
113+
inds = map(c) do ci
114+
sdefs = map(kvpairs) do (key, val)
115+
if (val isa Bool) && !(ci isa Bool)
116+
# We do not want to treat bools as an integer to prevent subsituting false when c contains 0
117+
return key => false
118+
end
119+
key => isequal(val, ci)
120+
end |> Dict
121+
map(eachindex(p)) do i
122+
get(sdefs, stringp[i], false)
123+
end |> findall
124+
end
125+
sort(reduce(vcat, inds))
126+
end
127+
128+
70129
export multibody
71130

72131
"""

test/test_robot.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,3 +316,36 @@ end
316316
@test maximum(abs, control_error) < 0.01
317317
end
318318

319+
320+
321+
@testset "subs constants" begin
322+
@info "Testing subs constants"
323+
@named robot = Robot6DOF()
324+
robot = complete(robot)
325+
ssys = structural_simplify(IRSystem(robot))
326+
ssys = Multibody.subs_constants(robot; ssys)
327+
prob = ODEProblem(ssys, [
328+
robot.mechanics.r1.phi => deg2rad(-60)
329+
robot.mechanics.r2.phi => deg2rad(20)
330+
robot.mechanics.r3.phi => deg2rad(90)
331+
robot.mechanics.r4.phi => deg2rad(0)
332+
robot.mechanics.r5.phi => deg2rad(-110)
333+
robot.mechanics.r6.phi => deg2rad(0)
334+
335+
robot.axis1.motor.Jmotor.phi => deg2rad(-60) * (-105) # Multiply by gear ratio
336+
robot.axis2.motor.Jmotor.phi => deg2rad(20) * (210)
337+
robot.axis3.motor.Jmotor.phi => deg2rad(90) * (60)
338+
], (0.0, 2.0))
339+
sol2 = solve(prob, Rodas5P(autodiff=false))
340+
341+
@test SciMLBase.successful_retcode(sol2)
342+
343+
tv = 0:0.1:2
344+
# control_error = sol2(tv, idxs=robot.pathPlanning.controlBus.axisControlBus1.angle_ref-robot.pathPlanning.controlBus.axisControlBus1.angle)
345+
# @test maximum(abs, control_error) < 0.002
346+
347+
# using BenchmarkTools
348+
# @btime solve(prob, Rodas5P(autodiff=false));
349+
# 152.225 ms (2272926 allocations: 40.08 MiB)
350+
# 114.113 ms (1833534 allocations: 33.38 MiB) # sub 0, 1
351+
end

0 commit comments

Comments
 (0)