# How to improve structural\_simplify performance?

**URL:** <https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260>\
**Category:** Modelling & Simulations\
**Tags:** question, performance, modelingtoolkit\
**Created:** [March 22, 2025, 11:37am UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260 "2025-03-22T11:37:01Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![Bart\_van\_de\_Lint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bart_van_de_lint/32/212161_2.png) [@Bart\_van\_de\_Lint](https://discourse.julialang.org/u/Bart_van_de_Lint)\
**Post date:** [March 22, 2025, 11:37am UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/1 "2025-03-22T11:37:01Z")

</div>

Modelingtoolkits `structural_simplify` can be very slow on bigger systems. What can I do to improve it’s performance? Should I write my equations in a certain way, should I split up big equations into smaller ones, or should I use certain keyword arguments?

---

<div class="post-metadata">

**Author:** ![ufechner7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ufechner7/32/51363_2.png) [@ufechner7](https://discourse.julialang.org/u/ufechner7)\
**Post date:** [March 22, 2025, 11:53am UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/2 "2025-03-22T11:53:27Z")

</div>

I guess we need an MWE to be able to comment on this question.

---

<div class="post-metadata">

**Author:** ![Bart\_van\_de\_Lint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bart_van_de_lint/32/212161_2.png) [@Bart\_van\_de\_Lint](https://discourse.julialang.org/u/Bart_van_de_Lint)\
**Post date:** [March 22, 2025, 12:51pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/3 "2025-03-22T12:51:07Z")

</div>

Here is my MWE. I used profileview on `structural_simplify` and saw that a lot of time is spent in `alias elimination`: `ModelingToolkit/ZOG3I/src/systems/alias_elimination.jl:46, alias_elimination!`

> **MWE**
>
> ```julia
> # MWE of tether system creation and simplification
> using ModelingToolkit, LinearAlgebra
> using ModelingToolkit: t_nounits as t, D_nounits as D
> 
> struct Point
> type::Symbol
> position::Union{Vector{Float64}, Nothing}
> velocity::Union{Vector{Float64}, Nothing}
> mass::Union{Float64, Nothing}
> force::Vector{Float64}
> end
> 
> struct Segment
> points::Tuple{Int, Int}
> l0::Union{Float64, Nothing}
> stiffness::Float64
> damping::Float64
> end
> 
> struct Pulley
> segments::Tuple{Int, Int}
> sum_length::Float64
> end
> 
> function main()
> """
> Add or remove points, segments and pulleys to these lists to configure your system
> """
> # protune-speed-system.jpg
> points = [
> Point(:fixed, [0, 0, 0], zeros(3), nothing, zeros(3)), # Fixed point
> Point(:quasi_static, [-1, 0, 0], zeros(3), 1.0, zeros(3)),
> Point(:quasi_static, [-2, 0, 0], zeros(3), 1.0, zeros(3)),
> Point(:quasi_static, [-3, 0, 0], zeros(3), 1.0, zeros(3)),
> Point(:dynamic, [-4, 0, 0], zeros(3), 100.0, [0., 0., 0.]),
> ]
> 
> stiffness = 614600
> damping = 4730
> segments = [
> Segment((1, 2), norm(points[1].position - points[2].position), stiffness, damping),
> Segment((2, 3), norm(points[2].position - points[3].position), stiffness, damping),
> Segment((3, 4), norm(points[3].position - points[4].position), stiffness, damping),
> Segment((4, 5), norm(points[4].position - points[5].position), stiffness, damping),
> ]
> 
> pulleys = []
> 
> g_earth::Vector{Float64} = [0.0, 0.0, -9.81] # gravitational acceleration [m/s²]
> l0 = 50 # initial tether length [m]
> c_spring = 614600 # unit spring constant [N]
> rel_compression_stiffness = 0.01 # relative compression stiffness [-]
> damping = 473 # unit damping constant [Ns]
> 
> function calc_initial_state(points, segments, pulleys)
> POS0 = zeros(3, length(points))
> VEL0 = zeros(3, length(points))
> L0 = zeros(length(pulleys))
> V0 = zeros(length(pulleys))
> for i in eachindex(points)
> POS0[:, i] .= points[i].position
> VEL0[:, i] .= points[i].velocity
> end
> for i in eachindex(pulleys)
> L0[i] = segments[pulleys[i].segments[1]].l0
> V0[i] = 0.0
> end
> POS0, VEL0, L0, V0
> end
> 
> function calc_spring_forces(pos::AbstractMatrix{T}, vel, pulley_l0) where T
> # loop over all segments to calculate spring forces
> spring_force = zeros(T, length(segments))
> spring_force_vec = zeros(T, 3, length(segments))
> segment = zeros(T, 3)
> unit_vector = zeros(T, 3)
> rel_vel = zeros(T, 3)
> for (tether_idx, tether) in enumerate(segments)
> found = false
> for (pulley_idx, pulley) in enumerate(pulleys)
> if tether_idx == pulley.segments[1] # each tether should only be part of one pulley
> l0 = pulley_l0[pulley_idx]
> found = true
> break
> elseif tether_idx == pulley.segments[2]
> l0 = pulley.sum_length - pulley_l0[pulley_idx]
> found = true
> break
> end
> end
> if !found
> l0 = tether.l0
> end
> p1, p2 = tether.points[1], tether.points[2]
> 
> segment .= pos[:, p2] .- pos[:, p1]
> len = norm(segment)
> unit_vector .= segment ./ len
> rel_vel .= vel[:, p1] .- vel[:, p2]
> spring_vel = rel_vel ⋅ unit_vector
> spring_force[tether_idx] = (tether.stiffness * tether.l0 * (len - l0) - tether.damping * tether.l0 * spring_vel)
> spring_force_vec[:, tether_idx] .= spring_force[tether_idx] .* unit_vector
> end
> return spring_force_vec, spring_force
> end
> 
> function calc_acc(pos::AbstractMatrix{T}, vel, pulley_l0) where T
> spring_force_vec, spring_force = calc_spring_forces(pos, vel, pulley_l0)
>         
> pulley_acc = zeros(T, length(pulleys))
> for (pulley_idx, pulley) in enumerate(pulleys)
> M = 3.1
> pulley_force = spring_force[pulley.segments[1]] - spring_force[pulley.segments[2]]
> pulley_acc[pulley_idx] = pulley_force / M
> end
> 
> acc = zeros(T, 3, length(points))
> force = zeros(T, 3)
> for (point_idx, point) in enumerate(points)
> if point.type === :fixed
> acc[:, point_idx] .= 0.0
> else
> force .= 0.0
> for (j, tether) in enumerate(segments)
> if point_idx in tether.points
> inverted = tether.points[2] == point_idx
> if inverted
> force .-= spring_force_vec[:, j]
> else
> force .+= spring_force_vec[:, j]
> end
> end
> end
> force .+= point.force
> acc[:, point_idx] .= force ./ point.mass .+ g_earth
> end
> end
> return acc, pulley_acc
> end
> 
> function model()
> @parameters rel_compression_stiffness = rel_compression_stiffness
> @variables begin
> pos(t)[1:3, eachindex(points)]
> vel(t)[1:3, eachindex(points)]
> acc(t)[1:3, eachindex(points)]
> force(t)[1:3, eachindex(points)]
> 
> pulley_force(t)[eachindex(pulleys)]
> pulley_l0(t)[eachindex(pulleys)] # first tether length in pulley
> pulley_vel(t)[eachindex(pulleys)]
> pulley_acc(t)[eachindex(pulleys)]
> 
> segment_vec(t)[1:3, eachindex(segments)]
> unit_vector(t)[1:3, eachindex(segments)]
> l_spring(t), c_spring(t), damping(t), m_tether_particle(t)
> len(t)[eachindex(segments)]
> l0(t)[eachindex(segments)]
> rel_vel(t)[1:3, eachindex(segments)]
> spring_vel(t)[eachindex(segments)]
> spring_force(t)[eachindex(segments)]
> spring_force_vec(t)[1:3, eachindex(segments)] # spring force from spring p1 to spring p2
> end
> 
> POS0, VEL0, L0, V0 = calc_initial_state(points, segments, pulleys)
> 
> defaults = Pair{Num, Real}[]
> guesses = Pair{Num, Real}[]
> eqs = [
> D(pulley_l0) ~ pulley_vel
> D(pulley_vel) ~ pulley_acc - 10pulley_vel
> ]
> 
> for (point_idx, point) in enumerate(points)
> if point.type === :fixed
> eqs = [
> eqs
> pos[:, point_idx] ~ point.position
> vel[:, point_idx] ~ zeros(3)
> ]
> elseif point.type === :dynamic
> eqs = [
> eqs
> D(pos[:, point_idx]) ~ vel[:, point_idx]
> D(vel[:, point_idx]) ~ acc[:, point_idx]
> ]
> defaults = [
> defaults
> [pos[j, point_idx] => POS0[j, point_idx] for j in 1:3]
> [vel[j, point_idx] => 0 for j in 1:3]
> ]
> elseif point.type === :quasi_static
> eqs = [
> eqs
> acc[:, point_idx] ~ zeros(3)
> vel[:, point_idx] ~ zeros(3)
> ]
> guesses = [
> guesses
> [pos[j, point_idx] => POS0[j, point_idx] for j in 1:3]
> [vel[j, point_idx] => 0 for j in 1:3]
> ]
> else
> println("wrong type")
> end
> end
> 
> defaults = [
> defaults
> pulley_l0 => L0
> pulley_vel => V0
> ]
> 
> eqs = [
> eqs
> vec(acc) .~ vec(calc_acc(pos, vel, pulley_l0)[1])
> vec(pulley_acc) .~ vec(calc_acc(pos, vel, pulley_l0)[2])
> ]
>         
> eqs = reduce(vcat, Symbolics.scalarize.(eqs))
> @named sys = ODESystem(eqs, t)
> @time sys = structural_simplify(sys)
> sys, pos, vel, defaults, guesses
> end
> model()
> end
> 
> sys, pos, vel, defaults, guesses = main()
> nothing
> 
> ```

---

<div class="post-metadata">

**Author:** ![Bart\_van\_de\_Lint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bart_van_de_lint/32/212161_2.png) [@Bart\_van\_de\_Lint](https://discourse.julialang.org/u/Bart_van_de_Lint)\
**Post date:** [March 22, 2025, 1:09pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/4 "2025-03-22T13:09:09Z")

</div>

Long equations are killing `structural_simplify` performance. This becomes clear from the revised MWE, where I generate more short equations instead of just a few very big ones. Structural\_simplify is almost 10x faster now.

> **Revised MWE with more shorter equations**
>
> ```julia
> # MWE of tether system creation and simplification
> using ModelingToolkit, LinearAlgebra
> using ModelingToolkit: t_nounits as t, D_nounits as D
> 
> struct Point
> type::Symbol
> position::Union{Vector{Float64}, Nothing}
> velocity::Union{Vector{Float64}, Nothing}
> mass::Union{Float64, Nothing}
> force::Vector{Float64}
> end
> 
> struct Segment
> points::Tuple{Int, Int}
> l0::Union{Float64, Nothing}
> stiffness::Float64
> damping::Float64
> end
> 
> struct Pulley
> segments::Tuple{Int, Int}
> sum_length::Float64
> end
> 
> function main()
> """
> Add or remove points, segments and pulleys to these lists to configure your system
> """
> # protune-speed-system.jpg
> points = [
> Point(:fixed, [0, 0, 0], zeros(3), nothing, zeros(3)), # Fixed point
> Point(:quasi_static, [-1, 0, 0], zeros(3), 1.0, zeros(3)),
> Point(:quasi_static, [-2, 0, 0], zeros(3), 1.0, zeros(3)),
> Point(:quasi_static, [-3, 0, 0], zeros(3), 1.0, zeros(3)),
> Point(:dynamic, [-4, 0, 0], zeros(3), 100.0, [0., 0., 0.]),
> ]
> 
> stiffness = 614600
> damping = 4730
> segments = [
> Segment((1, 2), norm(points[1].position - points[2].position), stiffness, damping),
> Segment((2, 3), norm(points[2].position - points[3].position), stiffness, damping),
> Segment((3, 4), norm(points[3].position - points[4].position), stiffness, damping),
> Segment((4, 5), norm(points[4].position - points[5].position), stiffness, damping),
> ]
> 
> pulleys = []
> 
> g_earth::Vector{Float64} = [0.0, 0.0, -9.81] # gravitational acceleration [m/s²]
> l0 = 50 # initial tether length [m]
> c_spring = 614600 # unit spring constant [N]
> rel_compression_stiffness = 0.01 # relative compression stiffness [-]
> damping = 473 # unit damping constant [Ns]
> 
> function calc_initial_state(points, segments, pulleys)
> POS0 = zeros(3, length(points))
> VEL0 = zeros(3, length(points))
> L0 = zeros(length(pulleys))
> V0 = zeros(length(pulleys))
> for i in eachindex(points)
> POS0[:, i] .= points[i].position
> VEL0[:, i] .= points[i].velocity
> end
> for i in eachindex(pulleys)
> L0[i] = segments[pulleys[i].segments[1]].l0
> V0[i] = 0.0
> end
> POS0, VEL0, L0, V0
> end
> 
> function calc_spring_forces(eqs, pos, vel, pulley_l0)
> # loop over all segments to calculate spring forces
> @variables begin
> spring_force(t)[eachindex(segments)]
> spring_force_vec(t)[1:3, eachindex(segments)]
> segment(t)[1:3, eachindex(segments)]
> unit_vector(t)[1:3, eachindex(segments)]
> rel_vel(t)[1:3, eachindex(segments)]
> len(t)[eachindex(segments)]
> spring_vel(t)[eachindex(segments)]
> end
> for (segment_idx, tether) in enumerate(segments)
> found = false
> for (pulley_idx, pulley) in enumerate(pulleys)
> if segment_idx == pulley.segments[1] # each tether should only be part of one pulley
> l0 = pulley_l0[pulley_idx]
> found = true
> break
> elseif segment_idx == pulley.segments[2]
> l0 = pulley.sum_length - pulley_l0[pulley_idx]
> found = true
> break
> end
> end
> if !found
> l0 = tether.l0
> end
> p1, p2 = tether.points[1], tether.points[2]
> 
> eqs = [
> eqs
> segment[:, segment_idx] ~ pos[:, p2] - pos[:, p1]
> len[segment_idx] ~ norm(segment[:, segment_idx])
> unit_vector[:, segment_idx] ~ segment[:, segment_idx] / len[segment_idx]
> rel_vel[:, segment_idx] ~ vel[:, p1] .- vel[:, p2]
> spring_vel[segment_idx] ~ rel_vel[:, segment_idx] ⋅ unit_vector[:, segment_idx]
> spring_force[segment_idx] ~ (tether.stiffness * tether.l0 * (len[segment_idx] - l0) - tether.damping * tether.l0 * spring_vel[segment_idx])
> spring_force_vec[:, segment_idx] ~ spring_force[segment_idx] * unit_vector[:, segment_idx]
> ]
> end
> return eqs, spring_force_vec, spring_force
> end
> 
> function calc_acc(eqs, pos, vel, pulley_l0)
> eqs, spring_force_vec, spring_force = calc_spring_forces(eqs, pos, vel, pulley_l0)
>         
> @variables pulley_acc(t)[eachindex(pulleys)]
> @variables pulley_force(t)[eachindex(pulleys)]
> for (pulley_idx, pulley) in enumerate(pulleys)
> M = 3.1
> eqs = [
> eqs
> pulley_force[pulley_idx] ~ spring_force[pulley.segments[1]] - spring_force[pulley.segments[2]]
> pulley_acc[pulley_idx] ~ pulley_force[pulley_force] / M
> ]
> end
> 
> @variables acc(t)[1:3, eachindex(points)]
> @variables force(t)[1:3, eachindex(points)]
> for (point_idx, point) in enumerate(points)
> if point.type === :fixed
> eqs = [
> eqs
> acc[:, point_idx] ~ zeros(3)
> ]
> else
> F = zeros(Num, 3)
> for (j, tether) in enumerate(segments)
> if point_idx in tether.points
> inverted = tether.points[2] == point_idx
> if inverted
> F .-= spring_force_vec[:, j]
> else
> F .+= spring_force_vec[:, j]
> end
> end
> end
> eqs = [
> eqs
> force[:, point_idx] ~ F
> acc[:, point_idx] ~ force[:, point_idx] / point.mass + g_earth
> ]
> end
> end
> return eqs, acc, pulley_acc
> end
> 
> function model()
> @parameters rel_compression_stiffness = rel_compression_stiffness
> @variables begin
> pos(t)[1:3, eachindex(points)]
> vel(t)[1:3, eachindex(points)]
> acc(t)[1:3, eachindex(points)]
> force(t)[1:3, eachindex(points)]
> 
> pulley_force(t)[eachindex(pulleys)]
> pulley_l0(t)[eachindex(pulleys)] # first tether length in pulley
> pulley_vel(t)[eachindex(pulleys)]
> pulley_acc(t)[eachindex(pulleys)]
> end
> 
> POS0, VEL0, L0, V0 = calc_initial_state(points, segments, pulleys)
> 
> defaults = Pair{Num, Real}[]
> guesses = Pair{Num, Real}[]
> eqs = [
> D(pulley_l0) ~ pulley_vel
> D(pulley_vel) ~ pulley_acc - 10pulley_vel
> ]
> 
> for (point_idx, point) in enumerate(points)
> if point.type === :fixed
> eqs = [
> eqs
> pos[:, point_idx] ~ point.position
> vel[:, point_idx] ~ zeros(3)
> ]
> elseif point.type === :dynamic
> eqs = [
> eqs
> D(pos[:, point_idx]) ~ vel[:, point_idx]
> D(vel[:, point_idx]) ~ acc[:, point_idx]
> ]
> defaults = [
> defaults
> [pos[j, point_idx] => POS0[j, point_idx] for j in 1:3]
> [vel[j, point_idx] => 0 for j in 1:3]
> ]
> elseif point.type === :quasi_static
> eqs = [
> eqs
> acc[:, point_idx] ~ zeros(3)
> vel[:, point_idx] ~ zeros(3)
> ]
> guesses = [
> guesses
> [pos[j, point_idx] => POS0[j, point_idx] for j in 1:3]
> [vel[j, point_idx] => 0 for j in 1:3]
> ]
> else
> println("wrong type")
> end
> end
> 
> defaults = [
> defaults
> pulley_l0 => L0
> pulley_vel => V0
> ]
> 
> eqs, acc, pulley_acc = calc_acc(eqs, pos, vel, pulley_l0)
>         
> eqs = reduce(vcat, Symbolics.scalarize.(eqs))
> @named sys = ODESystem(eqs, t)
> @time sys = structural_simplify(sys)
> sys, pos, vel, defaults, guesses
> end
> model()
> end
> 
> sys, pos, vel, defaults, guesses = main()
> nothing
> 
> ```

---

<div class="post-metadata">

**Author:** ![Bart\_van\_de\_Lint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bart_van_de_lint/32/212161_2.png) [@Bart\_van\_de\_Lint](https://discourse.julialang.org/u/Bart_van_de_Lint)\
**Post date:** [March 22, 2025, 2:20pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/5 "2025-03-22T14:20:08Z")

</div>

But still, on the bigger problem (so not the MWE), the performance of `structural_simplify` is bad: 533 seconds for a system with 191 equations. @ChrisRackauckas are there any plans to improve this?

```julia
julia> @time sys = structural_simplify(sys)
533.625644 seconds (464.60 M allocations: 18.985 GiB, 0.35% gc time, 1.52% compilation time: 36% of which was recompilation)
julia> sys
Model sys:
Equations (191):
  191 standard: see equations(sys)
Unknowns (191): see unknowns(sys)
  (pos(t))[1, 9]
  (pos(t))[2, 9]
  (pos(t))[3, 9]
  (vel(t))[1, 9]
  (vel(t))[2, 9]
  ⋮
Parameters (13): see parameters(sys)
  aero_kite_moment_b [defaults to [0.0346076, 31.6809, 0.0212127]]
  moment_dist [defaults to [0.0350009, 0.039114, 0.0231755, 0.00880667, -0.0116542, -0.0407203, -0.0507423, -0.0718568, -0.13384, -0.0962616 … -0.127448, -0.101881, -0.0725175, -0.0515411, -0.0423484, -0.0135425, 0.00530942, 0.0204002, 0.0382887, 0.0347389]]
  set_values(t) [defaults to [0.0, 0.0, 0.0]]
  aero_kite_force_b [defaults to [-10.4389, -0.126359, 108.13]]
  stiffness_frac [defaults to 0.1]
  ⋮
Observed (2496): see observed(sys)
julia> length(sys.eqs)
2687

```

---

<div class="post-metadata">

**Author:** ![ufechner7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ufechner7/32/51363_2.png) [@ufechner7](https://discourse.julialang.org/u/ufechner7)\
**Post date:** [March 22, 2025, 5:00pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/6 "2025-03-22T17:00:32Z")

</div>

Any comments from one of the MTK developers?

I was running the two examples of Bart and got the following results:

```julia
Results of second run:

include("bench1.jl")
2.280316 seconds (22.52 M allocations: 923.465 MiB, 4.77% gc time)
Simplifying 45 eqs to 15 eqs

include("bench2.jl")
0.159549 seconds (1.31 M allocations: 53.773 MiB, 12.25% gc time)
Simplifying 117 eqs to 15 eqs

```

This is just the time for executing structural\_simplify() on a Ryzen 7950X CPU on Linux. Using one or 16 threads did not make any difference.

It is indeed strange that simplifying a system of 117 equations is much faster than simplifying a system of 45 equations. Perhaps caching plays a role here, because this in both cases the timing of the second run?

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [March 23, 2025, 3:42pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/7 "2025-03-23T15:42:50Z")

</div>

Some improvements are coming. Others will require the sequel to JuliaSimCompiler.

---

<div class="post-metadata">

**Author:** ![ufechner7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ufechner7/32/51363_2.png) [@ufechner7](https://discourse.julialang.org/u/ufechner7)\
**Post date:** [March 24, 2025, 6:53pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/8 "2025-03-24T18:53:01Z")

</div>

> [@ChrisRackauckas](#):
>
> Others will require the sequel to JuliaSimCompiler.

I thought I give [JuliaSimCompiler.jl](https://help.juliahub.com/juliasimcompiler/stable/) a try, but it fails to pre-compile. This seams to be a known bug that is not fixed since months: [Can't precompile in Julia v1.11.1 · Issue #6 · JuliaComputing/JuliaSimIssues · GitHub](https://github.com/JuliaComputing/JuliaSimIssues/issues/6)

Strange enough, when using Julia 1.10 the pre-compilation also fails, but with a different error.

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [March 24, 2025, 10:31pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/9 "2025-03-24T22:31:21Z")

</div>

The sequel is a different repo

---

<div class="post-metadata">

**Author:** ![Bart\_van\_de\_Lint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bart_van_de_lint/32/212161_2.png) [@Bart\_van\_de\_Lint](https://discourse.julialang.org/u/Bart_van_de_Lint)\
**Post date:** [March 25, 2025, 10:49am UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/10 "2025-03-25T10:49:04Z")

</div>

Is the sequel publicly available? If so, where can I find it?

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [March 25, 2025, 1:16pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/11 "2025-03-25T13:16:50Z")

</div>

Not yet. So we are in the middle of a transition where the older JuliaSimCompiler needs to stay on older LLVM/MTK versions if you’re going to use it, but the new stuff currently needs an unreleased version of Julia. We’re working through getting this going… but also there’s other performance improvements coming to Symbolics and MTK as well, so there’s just lots of performance stuff going on and I don’t want to give a date to any of it because it’s more like there’s 40 things to do and each will be big or small depending on the kind of code, so it’s impossible to tell when your code will do better.

---

<div class="post-metadata">

**Author:** ![Bart\_van\_de\_Lint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bart_van_de_lint/32/212161_2.png) [@Bart\_van\_de\_Lint](https://discourse.julialang.org/u/Bart_van_de_Lint)\
**Post date:** [March 25, 2025, 1:18pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/12 "2025-03-25T13:18:59Z")

</div>

Exciting, looking forward to try these improvements!

---

<div class="post-metadata">

**Author:** ![ufechner7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ufechner7/32/51363_2.png) [@ufechner7](https://discourse.julialang.org/u/ufechner7)\
**Post date:** [March 25, 2025, 5:14pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/13 "2025-03-25T17:14:08Z")

</div>

> [@ChrisRackauckas](#):
>
> the older JuliaSimCompiler needs to stay on older LLVM/MTK versions

Which version of Julia/ MTK is the latest version that works?

---

<div class="post-metadata">

**Author:** ![kkakosim](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kkakosim/32/21546_2.png) [@kkakosim](https://discourse.julialang.org/u/kkakosim)\
**Post date:** [April 21, 2026, 3:06pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/14 "2026-04-21T15:06:01Z")

</div>

hello, is there any progress with this or other ideas to speed up structural\_simplify() ?

---

<div class="post-metadata">

**Author:** ![ufechner7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ufechner7/32/51363_2.png) [@ufechner7](https://discourse.julialang.org/u/ufechner7)\
**Post date:** [April 21, 2026, 3:16pm UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/15 "2026-04-21T15:16:04Z")

</div>

If you use MTK 11, it is very fast (like 50 times faster). But it was renamed to mtkcompile().

---

<div class="post-metadata">

**Author:** ![cstjean](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cstjean/32/1444_2.png) [@cstjean](https://discourse.julialang.org/u/cstjean)\
**Post date:** [April 23, 2026, 12:46am UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/16 "2026-04-23T00:46:56Z")

</div>

As additional data point, on my Windows laptop, TTFX got slightly worse on MTK11 (still minutes), though of course that includes more than just `structural_simplify`.

---

<div class="post-metadata">

**Author:** ![ufechner7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ufechner7/32/51363_2.png) [@ufechner7](https://discourse.julialang.org/u/ufechner7)\
**Post date:** [April 23, 2026, 2:20am UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/17 "2026-04-23T02:20:52Z")

</div>

> [@cstjean](#):
>
> TTFX got slightly worse on MTK11 (still minutes)

I am using a custom system image to avoid the time required to compile the Julia code.

---

<div class="post-metadata">

**Author:** ![cstjean](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cstjean/32/1444_2.png) [@cstjean](https://discourse.julialang.org/u/cstjean)\
**Post date:** [April 23, 2026, 4:08am UTC](https://discourse.julialang.org/t/how-to-improve-structural-simplify-performance/127260/18 "2026-04-23T04:08:24Z")

</div>

When you revise your model’s equations (with Revise.jl), how long does it take to get an answer? On my side, execution time is \<1s, but I still have to wait about 30seconds of JIT time.

Lately I’ve been evaluating InfiniteOpt, and while it doesn’t have the same breadth as MTK of course, JIT is basically a non-issue and development is going smoothly.
