Skip to content

Commit 1fa9629

Browse files
fix: avoid generating massive functions in get_mtkparameters_reconstructor
This used to be the lion's share of the compile time in large models. The infrastructure added here might be useful in other places too.
1 parent c5e214a commit 1fa9629

1 file changed

Lines changed: 241 additions & 58 deletions

File tree

lib/ModelingToolkitBase/src/systems/problem_utils.jl

Lines changed: 241 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,223 @@ function (pca::PConstructorApplicator)(x::AbstractArray{<:AbstractArray})
721721
return pca.p_constructor(pca.(x))
722722
end
723723

724+
"""
725+
$TYPEDEF
726+
727+
Callable struct designed for use by `MTKParametersReconstructor`. Uses a fixed set of templates to
728+
act as a very dynamic (and limited) observed function returning an array. See `__apply_copy_template`
729+
for the supported templates.
730+
"""
731+
struct CopyParamsByTemplate{IsRoot, T, N}
732+
"""
733+
List of templates.
734+
"""
735+
template::T # TODO: This field is parametric because I thought we might want to specialize it in some cases.
736+
"""
737+
Size of the returned buffer.
738+
"""
739+
size::NTuple{N, Int}
740+
end
741+
742+
function CopyParamsByTemplate{IR}(temp::T, size::NTuple{N, Int}) where {IR, T, N}
743+
return CopyParamsByTemplate{IR, T, N}(temp, size)
744+
end
745+
746+
function __apply_copy_template(valp, template)
747+
p = parameter_values(valp)
748+
u = state_values(valp)
749+
if template isa ParameterIndex{SciMLStructures.Tunable, UnitRange{Int}}
750+
if p isa MTKParameters
751+
return p.tunable[template.idx]
752+
else
753+
return p[template.idx]
754+
end
755+
elseif template isa ParameterIndex{SciMLStructures.Initials, UnitRange{Int}}
756+
return p.initials[template.idx]
757+
elseif template isa ParameterIndex{SciMLStructures.Discrete, Tuple{Int, UnitRange{Int}}}
758+
return p.discrete[template.idx[1]][template.idx[2]]
759+
elseif template isa ParameterIndex{SciMLStructures.Constants, Tuple{Int, UnitRange{Int}}}
760+
return p.constant[template.idx[1]][template.idx[2]]
761+
elseif template isa ParameterIndex{Nonnumeric, Tuple{Int, UnitRange{Int}}}
762+
return p.nonnumeric[template.idx[1]][template.idx[2]]
763+
elseif template isa UnitRange{Int}
764+
return u[template]
765+
elseif template isa ObservedWrapper
766+
return template(valp)
767+
elseif template isa CopyParamsByTemplate
768+
return template(valp)
769+
elseif template isa IndepVarTemplate
770+
return current_time(valp)
771+
else
772+
# MethodError because this is a manual dispatch chain
773+
throw(MethodError(__apply_copy_template, (valp, template)))
774+
end
775+
end
776+
777+
function (cp::CopyParamsByTemplate{IsRoot})(src) where {IsRoot}
778+
if IsRoot
779+
reshape(mapreduce(Base.Fix1(__apply_copy_template, src), vcat, cp.template), cp.size)
780+
else
781+
buffers = map(Base.Fix1(__apply_copy_template, src), cp.template)
782+
if cp.template isa Tuple
783+
buffers = collect(buffers)
784+
end
785+
reshape(buffers, cp.size)
786+
end
787+
end
788+
789+
struct IndepVarTemplate end
790+
const IV_TEMPLATE = IndepVarTemplate()
791+
792+
Base.@nospecializeinfer function __specialize_templates(template::Vector{Any}, elem_types::Set{DataType})
793+
if length(template) <= 4
794+
return Tuple(template)
795+
elseif length(elem_types) <= 4
796+
return Vector{Union{collect(elem_types)...}}(template)
797+
else
798+
return template
799+
end
800+
end
801+
802+
function CopyParamsByTemplate(srcsys::AbstractSystem, syms::AbstractArray{SymbolicT}; kws...)
803+
template = []
804+
elem_types = Set{DataType}()
805+
iv = get_iv(srcsys)
806+
for sym in syms
807+
if iv isa SymbolicT && isequal(iv, sym)
808+
push!(template, IV_TEMPLATE)
809+
push!(elem_types, IndepVarTemplate)
810+
continue
811+
end
812+
symidx = parameter_index(srcsys, sym)
813+
if symidx === nothing
814+
symidx = variable_index(srcsys, sym)
815+
if symidx === nothing
816+
if isempty(template)
817+
push!(elem_types, Vector{SymbolicT})
818+
push!(template, SymbolicT[sym])
819+
continue
820+
end
821+
prev = template[end]
822+
if prev isa Vector{SymbolicT}
823+
push!(prev, sym)
824+
else
825+
push!(elem_types, Vector{SymbolicT})
826+
push!(template, SymbolicT[sym])
827+
end
828+
continue
829+
end
830+
if isempty(template)
831+
push!(elem_types, UnitRange{Int})
832+
push!(template, symidx:symidx)
833+
continue
834+
end
835+
prev = template[end]
836+
if prev isa UnitRange{Int} && last(prev) + 1 == symidx
837+
template[end] = first(prev):symidx
838+
else
839+
push!(elem_types, UnitRange{Int})
840+
push!(template, symidx:symidx)
841+
end
842+
continue
843+
elseif symidx isa Int
844+
symidx = ParameterIndex(SciMLStructures.Tunable(), symidx)
845+
end
846+
portion = symidx.portion
847+
_bufidx = symidx.idx
848+
bufidx::UnitRange{Int} = if _bufidx isa AbstractVector{Int}
849+
@assert isequal(vec(_bufidx), first(_bufidx):last(_bufidx))
850+
subidx = nothing
851+
first(_bufidx):last(_bufidx)
852+
elseif _bufidx isa Int
853+
subidx = nothing
854+
_bufidx:_bufidx
855+
elseif _bufidx isa NTuple{2, Int}
856+
subidx = _bufidx[1]
857+
_bufidx[2]:_bufidx[2]
858+
else
859+
# Will error due to the typeassert on `bufidx`
860+
nothing
861+
end
862+
if isempty(template)
863+
pidx = if subidx === nothing
864+
ParameterIndex(symidx.portion, bufidx)
865+
else
866+
ParameterIndex(symidx.portion, (subidx, bufidx))
867+
end
868+
push!(template, pidx)
869+
push!(elem_types, typeof(pidx))
870+
continue
871+
end
872+
prev = template[end]
873+
if prev isa ParameterIndex && prev.portion === symidx.portion && (subidx === nothing && last(prev.idx) + 1 == first(bufidx) || subidx == prev.idx[1] && last(prev.idx[2]) + 1 == first(bufidx))
874+
if subidx === nothing
875+
template[end] = ParameterIndex(prev.portion, first(prev.idx):last(bufidx))
876+
else
877+
template[end] = ParameterIndex(prev.portion, (subidx, first(prev.idx[2]):last(bufidx)))
878+
end
879+
elseif subidx === nothing
880+
push!(template, ParameterIndex(symidx.portion, bufidx))
881+
push!(elem_types, typeof(template[end]))
882+
else
883+
push!(template, ParameterIndex(symidx.portion, (subidx, bufidx)))
884+
push!(elem_types, typeof(template[end]))
885+
end
886+
end
887+
888+
for i in eachindex(template)
889+
if template[i] isa Vector{SymbolicT}
890+
template[i] = concrete_getu(srcsys, template[i]; wrap_as_any = true, kws...)
891+
delete!(elem_types, Vector{SymbolicT})
892+
push!(elem_types, typeof(template[i]))
893+
end
894+
end
895+
896+
return CopyParamsByTemplate{true}(__specialize_templates(template, elem_types), size(syms))
897+
end
898+
899+
function CopyParamsByTemplate(srcsys::AbstractSystem, syms::AbstractArray; kws...)
900+
template = []
901+
elem_types = Set{DataType}()
902+
for sym in syms
903+
push!(template, CopyParamsByTemplate(srcsys, sym; kws...))
904+
push!(elem_types, typeof(template[end]))
905+
end
906+
return CopyParamsByTemplate{false}(__specialize_templates(template, elem_types), size(syms))
907+
end
908+
909+
struct MTKParametersReconstructor{T, I, D, C, N}
910+
tunables_fn::T
911+
initials_fn::I
912+
discretes_fn::D
913+
consts_fn::C
914+
nonnumerics_fn::N
915+
diffcache_buffer_idx::Int
916+
end
917+
918+
# TODO: make this infer when the nonnumerics are non-trivial
919+
function (recon::MTKParametersReconstructor)(src, dst)
920+
src_ps = parameter_values(src)
921+
dst_ps = parameter_values(dst)
922+
oldcache = dst_ps.caches
923+
# I don't know why but this makes it infer properly
924+
if recon.tunables_fn isa ComposedFunction
925+
tunablevals = recon.tunables_fn.outer(recon.tunables_fn.inner(src))
926+
else
927+
tunablevals = recon.tunables_fn(src)
928+
end
929+
initialvals = recon.initials_fn(src)
930+
nonnumerics = recon.nonnumerics_fn(src)::typeof(dst_ps.nonnumeric)
931+
(; diffcache_buffer_idx) = recon
932+
if !iszero(diffcache_buffer_idx)
933+
@set! nonnumerics[diffcache_buffer_idx] = DiffCacheAllocatorAPIWrapper{ForwardDiff.valtype(eltype(initialvals))}.(nonnumerics[diffcache_buffer_idx])
934+
end
935+
return MTKParameters(
936+
tunablevals, initialvals, recon.discretes_fn(src),
937+
recon.consts_fn(src), nonnumerics, oldcache isa Tuple{} ? () : copy.(oldcache)
938+
)
939+
end
940+
724941
"""
725942
$(TYPEDSIGNATURES)
726943
@@ -735,10 +952,10 @@ takes a value provider of `srcsys` and a value provider of `dstsys` and returns
735952
unwrapped.
736953
- `p_constructor`: The `p_constructor` argument to `process_SciMLProblem`.
737954
"""
738-
function get_mtkparameters_reconstructor(
955+
function MTKParametersReconstructor(
739956
srcsys::AbstractSystem, dstsys::AbstractSystem;
740957
initials = false, unwrap_initials = false, p_constructor = identity,
741-
eval_expression = false, eval_module = @__MODULE__, force_time_independent = false,
958+
force_time_independent = false,
742959
kwargs...
743960
)
744961
_p_constructor = p_constructor
@@ -757,32 +974,27 @@ function get_mtkparameters_reconstructor(
757974
tunable_getter = if isempty(tunable_syms)
758975
Returns(SVector{0, Float64}())
759976
else
760-
p_constructor concrete_getu(
761-
srcsys, tunable_syms; eval_expression, eval_module,
762-
force_time_independent, kwargs...
763-
)
977+
p_constructor CopyParamsByTemplate(srcsys, tunable_syms; kwargs...)
764978
end
765979
initials_getter = if initials && !isempty(syms[2])
766-
initsyms = Vector{Any}(syms[2])
767-
allsyms = Set(variable_symbols(srcsys))
980+
initsyms = syms[2]::Vector{SymbolicT}
981+
allsyms = Set{SymbolicT}(variable_symbols(srcsys))
768982
if unwrap_initials
769983
for i in eachindex(initsyms)
770984
sym = initsyms[i]
771-
innersym = if operation(sym) === getindex
772-
sym, idxs... = arguments(sym)
773-
only(arguments(sym))[idxs...]
985+
arr, isarr = split_indexed_var(sym)
986+
innersym = if isarr
987+
sidx = get_stable_index(sym)
988+
first(arguments(arr))[sidx]
774989
else
775-
only(arguments(sym))
990+
first(arguments(arr))
776991
end
777992
if innersym in allsyms
778993
initsyms[i] = innersym
779994
end
780995
end
781996
end
782-
p_constructor concrete_getu(
783-
srcsys, initsyms; eval_expression, eval_module,
784-
force_time_independent, kwargs...
785-
)
997+
p_constructor CopyParamsByTemplate(srcsys, initsyms; kwargs...)
786998
else
787999
Returns(SVector{0, Float64}())
7881000
end
@@ -795,29 +1007,25 @@ function get_mtkparameters_reconstructor(
7951007
p_constructor(map(x -> x.length, bufsizes))
7961008
end
7971009
)
1010+
7981011
# discretes need to be blocked arrays
7991012
# the `getu` returns a tuple of arrays corresponding to `p.discretes`
8001013
# `Base.Fix1(...)` applies `p_constructor` to each of the arrays in the tuple
8011014
# `Base.Fix2(...)` does `BlockedArray.(tuple_of_arrs, blockarrsizes)` returning a
8021015
# tuple of `BlockedArray`s
8031016
Base.Fix2(Broadcast.BroadcastFunction(BlockedArray), blockarrsizes)
804-
Base.Fix1(broadcast, p_constructor)
1017+
Base.Fix1(broadcast, p_constructor) Tuple
8051018
# This `broadcast.(collect, ...)` avoids `ReshapedArray`/`SubArray`s from
8061019
# appearing in the result.
807-
concrete_getu(
808-
srcsys, Tuple(broadcast.(collect, syms[3]));
809-
eval_expression, eval_module, force_time_independent, kwargs...
810-
)
1020+
CopyParamsByTemplate(srcsys, broadcast.(collect, syms[3]); kwargs...)
8111021
end
812-
const_getter = if syms[4] == ()
1022+
const_getter = if isempty(syms[4])
8131023
Returns(())
8141024
else
815-
Base.Fix1(broadcast, p_constructor) concrete_getu(
816-
srcsys, Tuple(syms[4]);
817-
eval_expression, eval_module, force_time_independent, kwargs...
818-
)
1025+
Base.Fix1(broadcast, p_constructor) Tuple CopyParamsByTemplate(srcsys, syms[4]; kwargs...)
8191026
end
820-
nonnumeric_getter = if syms[5] == ()
1027+
diffcache_buffer_idx = 0
1028+
nonnumeric_getter = if isempty(syms[5])
8211029
Returns(())
8221030
else
8231031
ic = get_index_cache(dstsys)
@@ -828,44 +1036,19 @@ function get_mtkparameters_reconstructor(
8281036
)
8291037

8301038
diffcache_params = SU.getmetadata(dstsys, DiffCacheParams, Dict{SymbolicT, Int}())::Dict{SymbolicT, Int}
831-
diffcache_buffer_idx = 0
8321039
if !isempty(diffcache_params)
8331040
representative = first(keys(diffcache_params))
8341041
diffcache_buffer_idx, _ = ic.nonnumeric_idx[representative]
8351042
@set! buftypes[diffcache_buffer_idx] = identity
1043+
for (i, sym) in enumerate(syms[5][diffcache_buffer_idx])
1044+
end
8361045
end
8371046
# nonnumerics retain the assigned buffer type without narrowing
8381047
Base.Fix1(broadcast, _p_constructor)
839-
Base.Fix1(Broadcast.BroadcastFunction(call), buftypes)
840-
concrete_getu(
841-
srcsys, Tuple(syms[5]);
842-
eval_expression, eval_module, force_time_independent, kwargs...
843-
)
844-
end
845-
getters = (
846-
tunable_getter, initials_getter, discs_getter, const_getter, nonnumeric_getter,
847-
)
848-
getter = let getters = getters, diffcache_buffer_idx = diffcache_buffer_idx
849-
function _getter(valp, initprob)
850-
oldcache = parameter_values(initprob).caches
851-
tunablevals = getters[1](valp)
852-
initialvals = getters[2](valp)
853-
nonnumerics = getters[5](valp)
854-
if !iszero(diffcache_buffer_idx)
855-
@set! nonnumerics[diffcache_buffer_idx] = DiffCacheAllocatorAPIWrapper{ForwardDiff.valtype(eltype(initialvals))}.(nonnumerics[diffcache_buffer_idx])
856-
end
857-
return promote_with_nothing(
858-
promote_type_with_nothing(eltype(tunablevals), initialvals),
859-
MTKParameters(
860-
tunablevals, initialvals, getters[3](valp),
861-
getters[4](valp), nonnumerics, oldcache isa Tuple{} ? () :
862-
copy.(oldcache)
863-
)
864-
)
865-
end
1048+
Base.Fix1(Broadcast.BroadcastFunction(call), buftypes) Tuple CopyParamsByTemplate(srcsys, syms[5]; kwargs...)
8661049
end
8671050

868-
return getter
1051+
return MTKParametersReconstructor(tunable_getter, initials_getter, discs_getter, const_getter, nonnumeric_getter, diffcache_buffer_idx)
8691052
end
8701053

8711054
function call(f, args...)
@@ -893,7 +1076,7 @@ function ReconstructInitializeprob(
8931076
kwargs...
8941077
)
8951078
if is_split(dstsys)
896-
pgetter = get_mtkparameters_reconstructor(
1079+
pgetter = MTKParametersReconstructor(
8971080
srcsys, dstsys; p_constructor, eval_expression, eval_module,
8981081
force_time_independent = is_steadystateprob, kwargs...
8991082
)
@@ -973,7 +1156,7 @@ function construct_initializeprobpmap(
9731156
)
9741157
@assert is_initializesystem(initsys)
9751158
if is_split(sys)
976-
return let getter = get_mtkparameters_reconstructor(
1159+
return let getter = MTKParametersReconstructor(
9771160
initsys, sys; initials = true, unwrap_initials = true, p_constructor,
9781161
eval_expression, eval_module, kwargs...
9791162
)

0 commit comments

Comments
 (0)