diff --git a/lib/ModelingToolkitBase/src/derivative_dict.jl b/lib/ModelingToolkitBase/src/derivative_dict.jl index e76f88e85d..24aa11c6f2 100644 --- a/lib/ModelingToolkitBase/src/derivative_dict.jl +++ b/lib/ModelingToolkitBase/src/derivative_dict.jl @@ -12,51 +12,81 @@ struct DerivativeDict{K, D <: AbstractDict{K, SymbolicT}} <: AbstractDict{K, Sym dict::D end -DerivativeDict() = DerivativeDict(Dict{SymbolicT, SymbolicT}()) -DerivativeDict(args::Pair...) = DerivativeDict(Dict(args...)) -DerivativeDict{K}(args...) where {K} = DerivativeDict(Dict{K, SymbolicT}(args...)) +""" + $(TYPEDEF) -Base.copy(dd::DerivativeDict) = DerivativeDict(copy(dd.dict)) -function Base.empty(dd::DerivativeDict, ::Type{K}, ::Type{V}) where {K, V} - return DerivativeDict(empty(dd.dict, K, V)) +Identical to `DerivativeDict`, but `Differential(t, n)(x)` maps to +`expand_derivatives(Differential(t, n-1)(v))`. Mutates the wrapped dictionary in `get` +for efficiency - the `expand_derivatives` result is cached. This also means that `getindex` +mutates. +""" +struct ExpandDerivativeDict{K, D <: AbstractDict{K, SymbolicT}} <: AbstractDict{K, SymbolicT} + dict::D end struct __DDSentinel end const DD_SENTINEL = __DDSentinel() -function Base.get(def::Base.Callable, dd::DerivativeDict, k::SymbolicT) - res = get(dd.dict, k, DD_SENTINEL) - res === DD_SENTINEL || return res - return Moshi.Match.@match k begin - BSImpl.Term(; f, args) && if f isa Differential && f.order::Int > 1 end => begin - order = f.order::Int - res = get(dd.dict, Differential(f.x, 1)(args[1]), DD_SENTINEL) - res === DD_SENTINEL && return def() - res = res::SymbolicT - return Differential(f.x, order - 1)(res) +for T in [:DerivativeDict, :ExpandDerivativeDict] + @eval begin + $T() = $T(Dict{SymbolicT, SymbolicT}()) + $T(args::Pair...) = $T(Dict(args...)) + $T{K}(args...) where {K} = $T(Dict{K, SymbolicT}(args...)) + + Base.copy(dd::$T) = $T(copy(dd.dict)) + function Base.empty(dd::$T, ::Type{K}, ::Type{V}) where {K, V} + return $T(empty(dd.dict, K, V)) end - _ => return def() - end -end -function Base.get(f::Base.Callable, dd::DerivativeDict, k::Union{Num, Arr, CallAndWrap}) - return get(f, dd, unwrap(k)) -end -Base.get(f::Base.Callable, dd::DerivativeDict, k) = get(f, dd.dict, k) -Base.get(dd::DerivativeDict, k, default) = get(Returns(default), dd, k) + Base.haskey(dd::$T, k) = get(Returns(DD_SENTINEL), dd, k) !== DD_SENTINEL -Base.haskey(dd::DerivativeDict, k) = get(Returns(DD_SENTINEL), dd, k) !== DD_SENTINEL + Base.setindex!(dd::$T, v, k) = setindex!(dd.dict, unwrap(v), unwrap(k)) -function Base.getindex(dd::DerivativeDict, k) - res = get(Returns(DD_SENTINEL), dd, k) - res === DD_SENTINEL && throw(KeyError(k)) - return res::SymbolicT -end + Base.isempty(dd::$T) = isempty(dd.dict) + Base.length(dd::$T) = length(dd.dict) + Base.iterate(dd::$T, args...) = Base.iterate(dd.dict, args...) + Base.sizehint!(dd::$T, n; kw...) = sizehint!(dd.dict, n; kw...) + Base.empty!(dd::$T) = empty!(dd.dict) -Base.setindex!(dd::DerivativeDict, v, k) = setindex!(dd.dict, unwrap(v), unwrap(k)) + function Base.get(def::Base.Callable, dd::$T, k::SymbolicT) + res = get(dd.dict, k, DD_SENTINEL) + res === DD_SENTINEL || return res + return Moshi.Match.@match k begin + BSImpl.Term(; f, args) && if f isa Differential && f.order::Int > 1 end => begin + order = f.order::Int + res = get(dd.dict, Differential(f.x, 1)(args[1]), DD_SENTINEL) + res === DD_SENTINEL && return def() + res = res::SymbolicT + $( + if T === :DerivativeDict + :(return Differential(f.x, order - 1)(res)) + elseif T === :ExpandDerivativeDict + quote + res = expand_derivatives(Differential(f.x, order - 1)(res)) + dd.dict[k] = res + return res + end + else + error("Not handled") + end + ) + end + _ => return def() + end + end + function Base.get(f::Base.Callable, dd::$T, k::Union{Num, Arr, CallAndWrap}) + return get(f, dd, unwrap(k)) + end + Base.get(f::Base.Callable, dd::$T, k) = get(f, dd.dict, k) + + Base.get(dd::$T, k, default) = get(Returns(default), dd, k) + + function Base.getindex(dd::$T, k) + res = get(Returns(DD_SENTINEL), dd, k) + res === DD_SENTINEL && throw(KeyError(k)) + return res::SymbolicT + end + + end +end -Base.isempty(dd::DerivativeDict) = isempty(dd.dict) -Base.length(dd::DerivativeDict) = length(dd.dict) -Base.iterate(dd::DerivativeDict, args...) = Base.iterate(dd.dict, args...) -Base.sizehint!(dd::DerivativeDict, n; kw...) = sizehint!(dd.dict, n; kw...) -Base.empty!(dd::DerivativeDict) = empty!(dd.dict)