Coulomb and static friction with ModelingToolkit.jl and DifferentialEquations.jl

I am trying to create a model of a damped oscillator with friction. It works fine with only Coulomb friction, but all my attempts to add a static friction term have failed so far. Below is the most promising, but I can’t figure out why it fails.

The failure itself would be good to understand, but I would also like to understant the expresion in the traceback. It looks like my expression, but has an implementation of sign(x(t)) in it. I would love to be able to use that, but when I add anything with a Boolean to my expression, I get ERROR: TypeError: non-boolean (Term{Real}) used in boolean context

What is going on here? What else should I try? I have seen this problem solved by adding constraints that lock motion until the force is large enough to overcome static friction when the velocity falls to zero, Is there a way to do that?

using ModelingToolkit, DifferentialEquations

@parameters t k M σs σd vmin
@variables x(t) v(t)
@derivatives D'~t

tsgn(x) = tanh(x*10)
Ht(x) = (1 + tsgn(x)) / 2

eqs = [
    D(x) ~ v,
    D(v) ~ (Ht(abs(v) - vmin) * (k * x + σd * tsgn(v)) +
            Ht(vmin - abs(v)) * Ht(k * abs(x) - σs) * (k * x - tsgn(x) * σs)) / M
]

sys = ODESystem(eqs, t)

u0 = [x=>0, v=>10]

p = [k=>4.0, M=>0.25, σd=>0.1, σs=>1.0, vmin=>0.01]

tspan = (0.0, 50.0)

prob = ODEProblem(sys, u0, tspan, p, jac=true)

sol = solve(prob, Rodas4P());

plot(sol, vars=(0,2))

This results in the following stack trace when creating the ODEProblem.

ERROR: Failed to apply rule (*)(~(~x)) => if length(~(~x)) > N
        (*)((*)((~(~x))[1:N]...) * (*)((~(~x))[N + 1:end]...))
    else
        nothing
    end on expression 2.5 * (1 + tanh(10 * ((-1 * abs(v⦗t⦘)) + vmin))) * ((k * x⦗t⦘) + (-1 * tanh(10 * x⦗t⦘) * σs)) * (1 + (-1 * (tanh(10 * ((k * abs(x⦗t⦘)) + (-1 * σs))) ^ 2))) * ifelse(signbit(x⦗t⦘), -1, 1) * k
Stacktrace:
 [1] (::SymbolicUtils.Rule{Term{Any},SymbolicUtils.var"#term_matcher#46"{Tuple{SymbolicUtils.var"#literal_matcher#42"{typeof(*)},SymbolicUtils.var"#segment_matcher#44"{SymbolicUtils.Segment{typeof(SymbolicUtils.alwaystrue)}}}},ModelingToolkit.var"#453#455"{Int64}})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rule.jl:142
 [2] (::SymbolicUtils.Rewriters.Chain)(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:59
 [3] (::SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:140
 [4] (::SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:128
 [5] iterate at ./generator.jl:47 [inlined]
 [6] collect_to!(::Array{Term{Number},1}, ::Base.Generator{Array{Term{Number},1},SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}}, ::Int64, ::Int64) at ./array.jl:732
 [7] collect_to_with_first!(::Array{Term{Number},1}, ::Term{Number}, ::Base.Generator{Array{Term{Number},1},SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}}, ::Int64) at ./array.jl:710
 [8] _collect(::Array{Term{Number},1}, ::Base.Generator{Array{Term{Number},1},SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}}, ::Base.EltypeUnknown, ::Base.HasShape{1}) at ./array.jl:704
 [9] collect_similar at ./array.jl:628 [inlined]
 [10] map(::SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}, ::Array{Term{Number},1}) at ./abstractarray.jl:2162
 [11] (::SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:138
 [12] (::SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:128
 [13] iterate at ./generator.jl:47 [inlined]
 [14] _collect(::Array{Term,1}, ::Base.Generator{Array{Term,1},SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}}, ::Base.EltypeUnknown, ::Base.HasShape{1}) at ./array.jl:699
 [15] collect_similar at ./array.jl:628 [inlined]
 [16] map(::SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}, ::Array{Term,1}) at ./abstractarray.jl:2162
 [17] (::SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:138
 [18] (::SymbolicUtils.Rewriters.Fixpoint{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:101
 [19] unflatten_long_ops(::Num, ::Int64) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/build_function.jl:97
 [20] unflatten_long_ops at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/build_function.jl:91 [inlined]
 [21] #62 at ./operators.jl:875 [inlined]
 [22] iterate at ./generator.jl:47 [inlined]
 [23] collect_to!(::Array{Int64,2}, ::Base.Generator{Array{Num,2},Base.var"#62#63"{typeof(ModelingToolkit.unflatten_long_ops),ModelingToolkit.var"#476#495"{Dict{Any,Any}}}}, ::Int64, ::Int64) at ./array.jl:732
 [24] collect_to_with_first!(::Array{Int64,2}, ::Int64, ::Base.Generator{Array{Num,2},Base.var"#62#63"{typeof(ModelingToolkit.unflatten_long_ops),ModelingToolkit.var"#476#495"{Dict{Any,Any}}}}, ::Int64) at ./array.jl:710
 [25] collect(::Base.Generator{Array{Num,2},Base.var"#62#63"{typeof(ModelingToolkit.unflatten_long_ops),ModelingToolkit.var"#476#495"{Dict{Any,Any}}}}) at ./array.jl:691
 [26] _build_function(::ModelingToolkit.JuliaTarget, ::Array{Num,2}, ::Array{Term{Real},1}, ::Vararg{Any,N} where N; conv::ModelingToolkit.ODEToExpr, expression::Type{T} where T, checkbounds::Bool, linenumbers::Bool, multithread::Nothing, headerfun::typeof(ModelingToolkit.addheader), outputidxs::Nothing, convert_oop::Bool, force_SA::Bool, skipzeros::Bool, fillzeros::Bool, parallel::ModelingToolkit.SerialForm, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/build_function.jl:254
 [27] build_function(::Array{Num,2}, ::Vararg{Any,N} where N; target::ModelingToolkit.JuliaTarget, kwargs::Base.Iterators.Pairs{Symbol,Any,NTuple{5,Symbol},NamedTuple{(:conv, :expression, :checkbounds, :linenumbers, :parallel),Tuple{ModelingToolkit.ODEToExpr,DataType,Bool,Bool,ModelingToolkit.SerialForm}}}) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/build_function.jl:48
 [28] generate_jacobian(::ODESystem, ::Array{Term{Real},1}, ::Array{Sym{ModelingToolkit.Parameter{Real}},1}; simplify::Bool, sparse::Bool, kwargs::Base.Iterators.Pairs{Symbol,Any,NTuple{4,Symbol},NamedTuple{(:expression, :checkbounds, :linenumbers, :parallel),Tuple{DataType,Bool,Bool,ModelingToolkit.SerialForm}}}) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/systems/diffeqs/abstractodesystem.jl:67
 [29] ODEFunction{true,F,TMM,Ta,Tt,TJ,JVP,VJP,JP,SP,TW,TWt,TPJ,S,TCV} where TCV where S where TPJ where TWt where TW where SP where JP where VJP where JVP where TJ where Tt where Ta where TMM where F(::ODESystem, ::Array{Term{Real},1}, ::Array{Sym{ModelingToolkit.Parameter{Real}},1}, ::Array{Int64,1}; version::Nothing, tgrad::Bool, jac::Bool, eval_expression::Bool, sparse::Bool, simplify::Bool, kwargs::Base.Iterators.Pairs{Symbol,Any,Tuple{Symbol,Symbol,Symbol},NamedTuple{(:checkbounds, :linenumbers, :parallel),Tuple{Bool,Bool,ModelingToolkit.SerialForm}}}) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/systems/diffeqs/abstractodesystem.jl:147
 [30] ODEProblem{true,tType,isinplace,P,F,K,PT} where PT where K where F where P where isinplace where tType(::ODESystem, ::Array{Pair{Num,Int64},1}, ::Tuple{Float64,Float64}, ::Array{Pair{Num,Float64},1}; version::Nothing, tgrad::Bool, jac::Bool, checkbounds::Bool, sparse::Bool, simplify::Bool, linenumbers::Bool, parallel::ModelingToolkit.SerialForm, eval_expression::Bool, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/systems/diffeqs/abstractodesystem.jl:277
 [31] #ODEProblem#206 at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/systems/diffeqs/abstractodesystem.jl:240 [inlined]
 [32] top-level scope at REPL[17]:1
caused by [exception 1]
MethodError: no method matching *(::Term{Real}, ::Symbol)
Closest candidates are:
  *(::Any, ::Any, ::Any, ::Any...) at operators.jl:538
  *(::LightGraphs.LinAlg.Noop, ::Any) at /home/russel/.julia/packages/LightGraphs/HsNig/src/linalg/graphmatrices.jl:226
  *(::ChainRulesCore.One, ::Any) at /home/russel/.julia/packages/ChainRulesCore/UayCG/src/differential_arithmetic.jl:98
  ...
Stacktrace:
 [1] (::ModelingToolkit.var"#453#455"{Int64})(::Base.ImmutableDict{Symbol,Any}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rule.jl:274
 [2] macro expansion at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/utils.jl:11 [inlined]
 [3] (::SymbolicUtils.var"#34#35"{ModelingToolkit.var"#453#455"{Int64}})(::Base.ImmutableDict{Symbol,Any}, ::Int64) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rule.jl:139
 [4] (::SymbolicUtils.var"#loop#47"{SymbolicUtils.var"#34#35"{ModelingToolkit.var"#453#455"{Int64}}})(::SymbolicUtils.LL{Array{Any,1}}, ::Base.ImmutableDict{Symbol,Any}, ::SymbolicUtils.LL{Tuple{SymbolicUtils.var"#literal_matcher#42"{typeof(*)},SymbolicUtils.var"#segment_matcher#44"{SymbolicUtils.Segment{typeof(SymbolicUtils.alwaystrue)}}}}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/matchers.jl:96
 [5] (::SymbolicUtils.var"#45#48"{SymbolicUtils.LL{Array{Any,1}},SymbolicUtils.LL{Tuple{SymbolicUtils.var"#literal_matcher#42"{typeof(*)},SymbolicUtils.var"#segment_matcher#44"{SymbolicUtils.Segment{typeof(SymbolicUtils.alwaystrue)}}}}})(::Base.ImmutableDict{Symbol,Any}, ::Int64) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/matchers.jl:101
 [6] (::SymbolicUtils.var"#segment_matcher#44"{SymbolicUtils.Segment{typeof(SymbolicUtils.alwaystrue)}})(::SymbolicUtils.var"#45#48"{SymbolicUtils.LL{Array{Any,1}},SymbolicUtils.LL{Tuple{SymbolicUtils.var"#literal_matcher#42"{typeof(*)},SymbolicUtils.var"#segment_matcher#44"{SymbolicUtils.Segment{typeof(SymbolicUtils.alwaystrue)}}}}}, ::SymbolicUtils.LL{Array{Any,1}}, ::Base.ImmutableDict{Symbol,Any}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/matchers.jl:74
 [7] (::SymbolicUtils.var"#loop#47"{SymbolicUtils.var"#34#35"{ModelingToolkit.var"#453#455"{Int64}}})(::SymbolicUtils.LL{Array{Any,1}}, ::Base.ImmutableDict{Symbol,Any}, ::SymbolicUtils.LL{Tuple{SymbolicUtils.var"#literal_matcher#42"{typeof(*)},SymbolicUtils.var"#segment_matcher#44"{SymbolicUtils.Segment{typeof(SymbolicUtils.alwaystrue)}}}}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/matchers.jl:100
 [8] (::SymbolicUtils.var"#45#48"{Term{Number},Tuple{SymbolicUtils.var"#literal_matcher#42"{typeof(*)},SymbolicUtils.var"#segment_matcher#44"{SymbolicUtils.Segment{typeof(SymbolicUtils.alwaystrue)}}}})(::Base.ImmutableDict{Symbol,Any}, ::Int64) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/matchers.jl:101
 [9] (::SymbolicUtils.var"#literal_matcher#42"{typeof(*)})(::SymbolicUtils.var"#45#48"{Term{Number},Tuple{SymbolicUtils.var"#literal_matcher#42"{typeof(*)},SymbolicUtils.var"#segment_matcher#44"{SymbolicUtils.Segment{typeof(SymbolicUtils.alwaystrue)}}}}, ::Term{Number}, ::Base.ImmutableDict{Symbol,Any}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/matchers.jl:10
 [10] (::SymbolicUtils.var"#loop#47"{SymbolicUtils.var"#34#35"{ModelingToolkit.var"#453#455"{Int64}}})(::Term{Number}, ::Base.ImmutableDict{Symbol,Any}, ::Tuple{SymbolicUtils.var"#literal_matcher#42"{typeof(*)},SymbolicUtils.var"#segment_matcher#44"{SymbolicUtils.Segment{typeof(SymbolicUtils.alwaystrue)}}}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/matchers.jl:100
 [11] (::SymbolicUtils.var"#term_matcher#46"{Tuple{SymbolicUtils.var"#literal_matcher#42"{typeof(*)},SymbolicUtils.var"#segment_matcher#44"{SymbolicUtils.Segment{typeof(SymbolicUtils.alwaystrue)}}}})(::SymbolicUtils.var"#34#35"{ModelingToolkit.var"#453#455"{Int64}}, ::Tuple{Term{Number}}, ::Base.ImmutableDict{Symbol,Any}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/matchers.jl:105
 [12] (::SymbolicUtils.Rule{Term{Any},SymbolicUtils.var"#term_matcher#46"{Tuple{SymbolicUtils.var"#literal_matcher#42"{typeof(*)},SymbolicUtils.var"#segment_matcher#44"{SymbolicUtils.Segment{typeof(SymbolicUtils.alwaystrue)}}}},ModelingToolkit.var"#453#455"{Int64}})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rule.jl:137
 [13] (::SymbolicUtils.Rewriters.Chain)(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:59
 [14] (::SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:140
 [15] (::SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:128
 [16] iterate at ./generator.jl:47 [inlined]
 [17] collect_to!(::Array{Term{Number},1}, ::Base.Generator{Array{Term{Number},1},SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}}, ::Int64, ::Int64) at ./array.jl:732
 [18] collect_to_with_first!(::Array{Term{Number},1}, ::Term{Number}, ::Base.Generator{Array{Term{Number},1},SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}}, ::Int64) at ./array.jl:710
 [19] _collect(::Array{Term{Number},1}, ::Base.Generator{Array{Term{Number},1},SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}}, ::Base.EltypeUnknown, ::Base.HasShape{1}) at ./array.jl:704
 [20] collect_similar at ./array.jl:628 [inlined]
 [21] map(::SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}, ::Array{Term{Number},1}) at ./abstractarray.jl:2162
 [22] (::SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:138
 [23] (::SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:128
 [24] iterate at ./generator.jl:47 [inlined]
 [25] _collect(::Array{Term,1}, ::Base.Generator{Array{Term,1},SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}}, ::Base.EltypeUnknown, ::Base.HasShape{1}) at ./array.jl:699
 [26] collect_similar at ./array.jl:628 [inlined]
 [27] map(::SymbolicUtils.Rewriters.PassThrough{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}}, ::Array{Term,1}) at ./abstractarray.jl:2162
 [28] (::SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:138
 [29] (::SymbolicUtils.Rewriters.Fixpoint{SymbolicUtils.Rewriters.Walk{:post,SymbolicUtils.Rewriters.Chain,false}})(::Term{Number}) at /home/russel/.julia/packages/SymbolicUtils/K1sGK/src/rewriters.jl:101
 [30] unflatten_long_ops(::Num, ::Int64) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/build_function.jl:97
 [31] unflatten_long_ops at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/build_function.jl:91 [inlined]
 [32] #62 at ./operators.jl:875 [inlined]
 [33] iterate at ./generator.jl:47 [inlined]
 [34] collect_to!(::Array{Int64,2}, ::Base.Generator{Array{Num,2},Base.var"#62#63"{typeof(ModelingToolkit.unflatten_long_ops),ModelingToolkit.var"#476#495"{Dict{Any,Any}}}}, ::Int64, ::Int64) at ./array.jl:732
 [35] collect_to_with_first!(::Array{Int64,2}, ::Int64, ::Base.Generator{Array{Num,2},Base.var"#62#63"{typeof(ModelingToolkit.unflatten_long_ops),ModelingToolkit.var"#476#495"{Dict{Any,Any}}}}, ::Int64) at ./array.jl:710
 [36] collect(::Base.Generator{Array{Num,2},Base.var"#62#63"{typeof(ModelingToolkit.unflatten_long_ops),ModelingToolkit.var"#476#495"{Dict{Any,Any}}}}) at ./array.jl:691
 [37] _build_function(::ModelingToolkit.JuliaTarget, ::Array{Num,2}, ::Array{Term{Real},1}, ::Vararg{Any,N} where N; conv::ModelingToolkit.ODEToExpr, expression::Type{T} where T, checkbounds::Bool, linenumbers::Bool, multithread::Nothing, headerfun::typeof(ModelingToolkit.addheader), outputidxs::Nothing, convert_oop::Bool, force_SA::Bool, skipzeros::Bool, fillzeros::Bool, parallel::ModelingToolkit.SerialForm, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/build_function.jl:254
 [38] build_function(::Array{Num,2}, ::Vararg{Any,N} where N; target::ModelingToolkit.JuliaTarget, kwargs::Base.Iterators.Pairs{Symbol,Any,NTuple{5,Symbol},NamedTuple{(:conv, :expression, :checkbounds, :linenumbers, :parallel),Tuple{ModelingToolkit.ODEToExpr,DataType,Bool,Bool,ModelingToolkit.SerialForm}}}) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/build_function.jl:48
 [39] generate_jacobian(::ODESystem, ::Array{Term{Real},1}, ::Array{Sym{ModelingToolkit.Parameter{Real}},1}; simplify::Bool, sparse::Bool, kwargs::Base.Iterators.Pairs{Symbol,Any,NTuple{4,Symbol},NamedTuple{(:expression, :checkbounds, :linenumbers, :parallel),Tuple{DataType,Bool,Bool,ModelingToolkit.SerialForm}}}) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/systems/diffeqs/abstractodesystem.jl:67
 [40] ODEFunction{true,F,TMM,Ta,Tt,TJ,JVP,VJP,JP,SP,TW,TWt,TPJ,S,TCV} where TCV where S where TPJ where TWt where TW where SP where JP where VJP where JVP where TJ where Tt where Ta where TMM where F(::ODESystem, ::Array{Term{Real},1}, ::Array{Sym{ModelingToolkit.Parameter{Real}},1}, ::Array{Int64,1}; version::Nothing, tgrad::Bool, jac::Bool, eval_expression::Bool, sparse::Bool, simplify::Bool, kwargs::Base.Iterators.Pairs{Symbol,Any,Tuple{Symbol,Symbol,Symbol},NamedTuple{(:checkbounds, :linenumbers, :parallel),Tuple{Bool,Bool,ModelingToolkit.SerialForm}}}) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/systems/diffeqs/abstractodesystem.jl:147
 [41] ODEProblem{true,tType,isinplace,P,F,K,PT} where PT where K where F where P where isinplace where tType(::ODESystem, ::Array{Pair{Num,Int64},1}, ::Tuple{Float64,Float64}, ::Array{Pair{Num,Float64},1}; version::Nothing, tgrad::Bool, jac::Bool, checkbounds::Bool, sparse::Bool, simplify::Bool, linenumbers::Bool, parallel::ModelingToolkit.SerialForm, eval_expression::Bool, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/systems/diffeqs/abstractodesystem.jl:277
 [42] #ODEProblem#206 at /home/russel/.julia/packages/ModelingToolkit/iu31N/src/systems/diffeqs/abstractodesystem.jl:240 [inlined]
 [43] top-level scope at REPL[17]:1

It looks like simplify failed, and @shashi would be the one to take a look at it. Thanks for the example: that should be very useful.

You can use IfElse.ifelse(x,y,z) as a stand-in for the standard Core.ifelse and it’ll be something that is symbolically recognized. We may need to fix a few derivative rules to do this.

Ah, IfElse is a great trick, I didn’t know about that one.

I almost got things working using that idea and then I realized this is not a good model of friction anyway, since it sets D(v) ~ 0, not v ~ 0, when the static friction becomes important. I need to think more about what I really mean here. Maybe I can do something fancy with callbacks.

OK, I got the bad model working with IfElse.


isign(x) = IfElse.ifelse(signbit(x), -1, 1)

eqs = [
    D(x) ~ v,
    D(v) ~ IfElse.ifelse(signbit(abs(v) - vmin),
                         IfElse.ifelse(signbit(k*abs(x) - σs),
                                       0,
                                       -k * x + isign(x) * σs),
                         -k * x - σd * isign(v)) / M
]

When I try to express this more naturally with <, I get

ERROR: MethodError: no method matching isless(::Term{Number}, ::Sym{Real})

SymbolicUtils only defines isless for arguments of matching type, Is there some way to turn that Term{Number}into a Term{Real}?

This is probably worth an issue.

Hmm, this already looks pretty well thought out. Number was explicitly skipped for all comparisons besides equality, and it does allow isless for T<:Real. Looking at the tests, I think this was to leave out Complex.

It does not define it on Number because there’s no way to compare complex numbers but maybe we should allow Number x Real and Real x Number

https://github.com/SciML/ModelingToolkit.jl/pull/648 this should fix the issue in the OP

That works, thanks! I’m surprised how similar the two solutions are, I would have expected the smooth approximation to have a much more noticeable effect.

The above code does not seem to be working in Win10 Julia 1.5.2. Tried to add ModelingToolkit#master to get latest fixes. I always get the following error in the eqs definition:

ERROR: MethodError: no method matching signbit(::Operation)

Thank you.

Operation shouldn’t exist anymore. Check ]st: do you have MTK v4? This is a very recent release and a very recent fix.

1 Like

@ChrisRackauckas, thank you. After a lengthy package maintenance/hygiene work, managed to update from MTK v3.1 to v3.20.1. The code runs with no errors now.

PS: How can we update to v4?

You probably need some updates which may not be released yet. Some downstream stuff are still in the release process (it’s that new still)

1 Like

Thanks for sharing this very interesting physics problem. One observation regarding the friction model equations, if you will.
One could define the static friction region as k*abs(x) < σs*N where the spring force is cancelled (N is the normal force). And the kinematic friction region over k*abs(x) >= σs*N. The equations simplify then to:

const g = 9.8;  # m/s^2
eqs = [
    D(x) ~ v,
    D(v) ~
        IfElse.ifelse(signbit.(k*abs(x) - σs*M*g), 0,  -k*x -σd*M*g*isign(v)) / M,
]

Also, the equations using the smooth step function trick would then become:

eqs1 = [
    D(x) ~ v,
    D(v) ~
        ( Ht(k*abs(x) - σs*M*g) * (-k*x - σd*M*g*tsgn(v)) ) / M,
]

Best regards.

3 Likes