diff --git a/lib/ModelingToolkitBase/src/problems/bvproblem.jl b/lib/ModelingToolkitBase/src/problems/bvproblem.jl index 13bacc9c9c..c6e8b8c4de 100644 --- a/lib/ModelingToolkitBase/src/problems/bvproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/bvproblem.jl @@ -5,8 +5,42 @@ expression = Val{false}, guesses = Dict(), callback = nothing, kwargs... ) where {iip, spec} + fn_opts = SciMLFunctionOptions(; + t = tspan !== nothing ? tspan[1] : tspan, eval_expression, eval_module, + checkbounds, check_compatibility, expression, kwargs... + ) + opts = SciMLProblemOptions( + sys; + fn_opts, guesses, time_dependent_init = false, + build_initializeprob = supports_initialization(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return BVProblem{iip, spec}(sys, op, tspan, opts; guesses, callback, kwargs...) +end + +""" + SciMLBase.BVProblem{iip, spec}(sys::System, op, tspan, opts::SciMLProblemOptions; guesses = Dict(), callback = nothing, kwargs...) + +Public entry point that builds a `BVProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. + +`opts.fn_opts.check_compatibility` governs the `BVProblem`-level compatibility check. The +inner `ODEFunction` build always uses `check_compatibility = false` regardless (it must +skip its own, unrelated ODE-compatibility check) and derives `t` from `tspan[1]` when +`opts.fn_opts.t` isn't already set — both applied via `ConstructionBase.setproperties` on +a copy of `opts`, so a caller who builds `opts` directly (without going through the +keyword wrapper above) gets the same defaulting. `guesses` is kept separate from +`opts.guesses`: the latter is normalized (into a `SymmapT`) for `process_SciMLProblem`'s +own use, while this method needs the raw, unnormalized value to merge into `_op` exactly +as before. +""" +function SciMLBase.BVProblem{iip, spec}( + sys::System, op, tspan, opts::SciMLProblemOptions{E}; + guesses = Dict(), callback = nothing, kwargs... + ) where {iip, spec, E} check_complete(sys, BVProblem) - check_compatibility && check_compatible_system(BVProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(BVProblem, sys) isnothing(callback) || error("BVP solvers do not support callbacks.") _iip = resolve_iip(iip, op) @@ -16,13 +50,17 @@ # for initialization. _op = has_alg_eqs(sys) ? op : merge(Dict(op), Dict(guesses)) + inner_opts = maybe_derive_t_from_tspan(opts, tspan) + inner_opts = setproperties( + inner_opts; fn_opts = setproperties(inner_opts.fn_opts; check_compatibility = false) + ) fode, u0, p = process_SciMLProblem( - ODEFunction{_iip, spec}, sys, _op; guesses, - t = tspan !== nothing ? tspan[1] : tspan, check_compatibility = false, - checkbounds, time_dependent_init = false, expression, kwargs... + ODEFunction{_iip, spec}, sys, _op, inner_opts; options_struct = Val(true), kwargs... ) + (; eval_expression, eval_module) = opts.fn_opts.codegen + checkbounds = opts.fn_opts.codegen.codegen.checkbounds fcost = generate_bvp_cost( sys, GeneratedFunctionOptions(; @@ -52,10 +90,10 @@ @warn "The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by op) exceeds the total number of states. The BVP solvers will default to doing a nonlinear least-squares optimization." end - kwargs = process_kwargs(sys; expression, tspan, kwargs...) + kwargs = process_kwargs(sys; expression = Val{E}, tspan, kwargs...) args = (; bvpfn, u0, tspan, p) - return maybe_codegen_scimlproblem(expression, BVProblem{_iip}, args; kwargs...) + return maybe_codegen_scimlproblem(Val{E}, BVProblem{_iip}, args; kwargs...) end function check_compatible_system(T::Type{BVProblem}, sys::System) diff --git a/lib/ModelingToolkitBase/src/problems/daeproblem.jl b/lib/ModelingToolkitBase/src/problems/daeproblem.jl index 8bf07103f4..abc34897d5 100644 --- a/lib/ModelingToolkitBase/src/problems/daeproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/daeproblem.jl @@ -125,20 +125,46 @@ end eval_module = @__MODULE__, check_compatibility = true, expression = Val{false}, kwargs... ) where {iip, spec} + fn_opts = SciMLFunctionOptions(; + t = tspan !== nothing ? tspan[1] : tspan, eval_expression, eval_module, + check_compatibility, expression, kwargs... + ) + opts = SciMLProblemOptions( + sys; + fn_opts, check_length, implicit_dae = true, + build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return DAEProblem{iip, spec}(sys, op, tspan, opts; callback, kwargs...) +end + +""" + SciMLBase.DAEProblem{iip, spec}(sys::System, op, tspan, opts::SciMLProblemOptions; callback = nothing, kwargs...) + +Public entry point that builds a `DAEProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. +""" +function SciMLBase.DAEProblem{iip, spec}( + sys::System, op, tspan, opts::SciMLProblemOptions{E}; + callback = nothing, kwargs... + ) where {iip, spec, E} check_complete(sys, DAEProblem) - check_compatibility && check_compatible_system(DAEProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(DAEProblem, sys) + + opts = maybe_derive_t_from_tspan(opts, tspan) _iip = resolve_iip(iip, op) f, du0, u0, p = process_SciMLProblem( - DAEFunction{_iip, spec}, sys, op; - t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, - eval_module, check_compatibility, implicit_dae = true, expression, kwargs... + DAEFunction{_iip, spec}, sys, op, opts; options_struct = Val(true), kwargs... ) + (; eval_expression, eval_module) = opts.fn_opts.codegen kwargs = process_kwargs( - sys; expression, callback, eval_expression, eval_module, + sys; expression = Val{E}, callback, eval_expression, eval_module, op, tspan, kwargs... ) @@ -149,5 +175,5 @@ end args = (; f, du0, u0, tspan, p) kwargs = (; differential_vars, kwargs...) - return maybe_codegen_scimlproblem(expression, DAEProblem{_iip}, args; kwargs...) + return maybe_codegen_scimlproblem(Val{E}, DAEProblem{_iip}, args; kwargs...) end diff --git a/lib/ModelingToolkitBase/src/problems/ddeproblem.jl b/lib/ModelingToolkitBase/src/problems/ddeproblem.jl index db234fb1bc..e64731a294 100644 --- a/lib/ModelingToolkitBase/src/problems/ddeproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/ddeproblem.jl @@ -62,29 +62,63 @@ end sys::System, op, tspan; callback = nothing, check_length = true, checkbounds = false, eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, - u0_constructor = identity, expression = Val{false}, kwargs... + u0_constructor = identity, expression = Val{false}, constant_lags = missing, + kwargs... ) where {iip, spec} + fn_opts = SciMLFunctionOptions(; + t = tspan !== nothing ? tspan[1] : tspan, eval_expression, eval_module, + checkbounds, check_compatibility, expression, kwargs... + ) + opts = SciMLProblemOptions( + sys; + fn_opts, check_length, symbolic_u0 = true, u0_constructor, + build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return DDEProblem{iip, spec}(sys, op, tspan, opts; callback, constant_lags, kwargs...) +end + +""" + SciMLBase.DDEProblem{iip, spec}(sys::System, op, tspan, opts::SciMLProblemOptions; callback = nothing, kwargs...) + +Public entry point that builds a `DDEProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. + +`constant_lags` (default `missing`, meaning "not explicitly provided by the caller") is an +explicit keyword here — not part of `kwargs` — since it's relevant only to the final +`SciMLBase.DDEProblem` construction, not the inner `DDEFunction` build; the opts-accepting +`DDEFunction` method has no generic `kwargs...` sink to harmlessly absorb it the way its +keyword-based wrapper does. +""" +function SciMLBase.DDEProblem{iip, spec}( + sys::System, op, tspan, opts::SciMLProblemOptions{E}; + callback = nothing, constant_lags = missing, kwargs... + ) where {iip, spec, E} check_complete(sys, DDEProblem) - check_compatibility && check_compatible_system(DDEProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(DDEProblem, sys) + + opts = maybe_derive_t_from_tspan(opts, tspan) _iip = resolve_iip(iip, op) f, u0, p = process_SciMLProblem( - DDEFunction{_iip, spec}, sys, op; - t = tspan !== nothing ? tspan[1] : tspan, check_length, checkbounds, - eval_expression, eval_module, check_compatibility, symbolic_u0 = true, - expression, u0_constructor, kwargs... + DDEFunction{_iip, spec}, sys, op, opts; options_struct = Val(true), kwargs... ) + (; u0_constructor) = opts + (; eval_expression, eval_module) = opts.fn_opts.codegen + checkbounds = opts.fn_opts.codegen.codegen.checkbounds h = generate_history( sys, u0, GeneratedFunctionOptions(; - expression, wrap_gfw = Val{true}, eval_expression, eval_module, + expression = Val{E}, wrap_gfw = Val{true}, eval_expression, eval_module, codegen_function_options = Symbolics.CodegenFunctionOptions(; checkbounds) ) ) - if expression == Val{true} + if E if u0 !== nothing u0 = :($u0_constructor($map($float, h(p, tspan[1])))) end @@ -95,11 +129,14 @@ end end kwargs = process_kwargs( - sys; expression, callback, eval_expression, eval_module, op, tspan, kwargs... + sys; expression = Val{E}, callback, eval_expression, eval_module, op, tspan, kwargs... ) args = (; f, u0, h, tspan, p) + constant_lags = resolve_constant_lags(sys, constant_lags, p) + constant_lags_kw = constant_lags === missing ? (;) : (; constant_lags) + kwargs = (; constant_lags_kw..., kwargs...) - return maybe_codegen_scimlproblem(expression, DDEProblem{_iip}, args; kwargs...) + return maybe_codegen_scimlproblem(Val{E}, DDEProblem{_iip}, args; kwargs...) end function check_compatible_system(T::Union{Type{DDEFunction}, Type{DDEProblem}}, sys::System) diff --git a/lib/ModelingToolkitBase/src/problems/discreteproblem.jl b/lib/ModelingToolkitBase/src/problems/discreteproblem.jl index c913cd5c88..ea36a11605 100644 --- a/lib/ModelingToolkitBase/src/problems/discreteproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/discreteproblem.jl @@ -58,8 +58,30 @@ end sys::System, op, tspan; check_compatibility = true, expression = Val{false}, kwargs... ) where {iip, spec} + fn_opts = SciMLFunctionOptions(; + t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, expression, kwargs... + ) + opts = SciMLProblemOptions( + sys; + fn_opts, build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return DiscreteProblem{iip, spec}(sys, op, tspan, opts; kwargs...) +end + +""" + SciMLBase.DiscreteProblem{iip, spec}(sys::System, op, tspan, opts::SciMLProblemOptions; kwargs...) + +Public entry point that builds a `DiscreteProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. +""" +function SciMLBase.DiscreteProblem{iip, spec}( + sys::System, op, tspan, opts::SciMLProblemOptions{E}; kwargs... + ) where {iip, spec, E} check_complete(sys, DiscreteProblem) - check_compatibility && check_compatible_system(DiscreteProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(DiscreteProblem, sys) _iip = resolve_iip(iip, op) dvs = unknowns(sys) @@ -67,12 +89,10 @@ end add_toterms!(op; replace = true) f, u0, p = process_SciMLProblem( - DiscreteFunction{_iip, spec}, sys, op; - t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, expression, - kwargs... + DiscreteFunction{_iip, spec}, sys, op, opts; options_struct = Val(true), kwargs... ) - if expression == Val{true} + if E u0 = :(f($u0, p, tspan[1])) else u0 = f(u0, p, tspan[1]) @@ -81,7 +101,7 @@ end kwargs = process_kwargs(sys; kwargs...) args = (; f, u0, tspan, p) - return maybe_codegen_scimlproblem(expression, DiscreteProblem{_iip}, args; kwargs...) + return maybe_codegen_scimlproblem(Val{E}, DiscreteProblem{_iip}, args; kwargs...) end function check_compatible_system( diff --git a/lib/ModelingToolkitBase/src/problems/homotopyproblem.jl b/lib/ModelingToolkitBase/src/problems/homotopyproblem.jl index dbc50f0df6..7dfb3448a8 100644 --- a/lib/ModelingToolkitBase/src/problems/homotopyproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/homotopyproblem.jl @@ -34,7 +34,7 @@ be wrong mid-sweep. expression = Val{false}, λspan = (0.0, 1.0), check_length = true, check_compatibility = true, eval_expression = false, eval_module = @__MODULE__, - checkbounds = false, cse = true, kwargs... + checkbounds = false, kwargs... ) where {iip, spec} if expression !== Val{false} throw( @@ -45,11 +45,46 @@ be wrong mid-sweep. ) ) end + if is_time_dependent(sys) + sys = NonlinearSystem(sys) + end + fn_opts = SciMLFunctionOptions(; + eval_expression, eval_module, checkbounds, check_compatibility, expression, kwargs... + ) + opts = SciMLProblemOptions( + sys; + fn_opts, check_length, build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return HomotopyProblem{iip, spec}(sys, op, opts; λspan, kwargs...) +end + +""" + SciMLBase.HomotopyProblem{iip, spec}(sys::System, op, opts::SciMLProblemOptions; λspan = (0.0, 1.0), kwargs...) + +Public entry point that builds a `HomotopyProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. +""" +function SciMLBase.HomotopyProblem{iip, spec}( + sys::System, op, opts::SciMLProblemOptions{E}; + λspan = (0.0, 1.0), kwargs... + ) where {iip, spec, E} + if E + throw( + ArgumentError( + "`HomotopyProblem(sys, op)` does not yet support " * + "`expression = Val{true}`; build the problem directly " * + "(the default `expression = Val{false}`)." + ) + ) + end check_complete(sys, SciMLBase.HomotopyProblem) if is_time_dependent(sys) sys = NonlinearSystem(sys) end - check_compatibility && check_compatible_system(SciMLBase.NonlinearProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(SciMLBase.NonlinearProblem, sys) if !has_any_homotopy(sys) throw( ArgumentError( @@ -63,18 +98,19 @@ be wrong mid-sweep. _iip = resolve_iip(iip, op) f, u0, p = process_SciMLProblem( - SciMLBase.NonlinearFunction{_iip, spec}, sys, op; - check_length, check_compatibility, expression, - eval_expression, eval_module, checkbounds, cse, kwargs... + SciMLBase.NonlinearFunction{_iip, spec}, sys, op, opts; options_struct = Val(true), + kwargs... ) # Swap the opaque-`actual` residual for the homotopy-swept `f(u, p, λ)`. The # observed function and residual prototype carry over; `initialization_data`, # jacobian, and sparsity are deliberately not carried (the latter two encode # the `λ = 1` system and would be wrong mid-sweep). + (; eval_expression, eval_module) = opts.fn_opts.codegen + checkbounds = opts.fn_opts.codegen.codegen.checkbounds shadow, λ = lower_homotopy(sys) hf = generate_homotopy_residual( - shadow, λ; eval_expression, eval_module, checkbounds, cse + shadow, λ; eval_expression, eval_module, checkbounds ) swept_f = SciMLBase.NonlinearFunction{_iip}( hf; sys = f.sys, observed = f.observed, resid_prototype = f.resid_prototype @@ -83,6 +119,6 @@ be wrong mid-sweep. kwargs = process_kwargs(sys; kwargs...) args = (; f = swept_f, u0, p) return maybe_codegen_scimlproblem( - expression, SciMLBase.HomotopyProblem{_iip}, args; λspan, kwargs... + Val{E}, SciMLBase.HomotopyProblem{_iip}, args; λspan, kwargs... ) end diff --git a/lib/ModelingToolkitBase/src/problems/implicitdiscreteproblem.jl b/lib/ModelingToolkitBase/src/problems/implicitdiscreteproblem.jl index b941ea3ba5..2e15826d4f 100644 --- a/lib/ModelingToolkitBase/src/problems/implicitdiscreteproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/implicitdiscreteproblem.jl @@ -67,8 +67,30 @@ end sys::System, op, tspan; check_compatibility = true, expression = Val{false}, kwargs... ) where {iip, spec} + fn_opts = SciMLFunctionOptions(; + t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, expression, kwargs... + ) + opts = SciMLProblemOptions( + sys; + fn_opts, build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return ImplicitDiscreteProblem{iip, spec}(sys, op, tspan, opts; kwargs...) +end + +""" + SciMLBase.ImplicitDiscreteProblem{iip, spec}(sys::System, op, tspan, opts::SciMLProblemOptions; kwargs...) + +Public entry point that builds an `ImplicitDiscreteProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. +""" +function SciMLBase.ImplicitDiscreteProblem{iip, spec}( + sys::System, op, tspan, opts::SciMLProblemOptions{E}; kwargs... + ) where {iip, spec, E} check_complete(sys, ImplicitDiscreteProblem) - check_compatibility && check_compatible_system(ImplicitDiscreteProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(ImplicitDiscreteProblem, sys) _iip = resolve_iip(iip, op) dvs = unknowns(sys) @@ -76,15 +98,14 @@ end add_toterms!(op; replace = true) f, u0, p = process_SciMLProblem( - ImplicitDiscreteFunction{_iip, spec}, sys, op; - t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, - expression, kwargs... + ImplicitDiscreteFunction{_iip, spec}, sys, op, opts; options_struct = Val(true), + kwargs... ) kwargs = process_kwargs(sys; kwargs...) args = (; f, u0, tspan, p) return maybe_codegen_scimlproblem( - expression, ImplicitDiscreteProblem{_iip}, args; kwargs... + Val{E}, ImplicitDiscreteProblem{_iip}, args; kwargs... ) end diff --git a/lib/ModelingToolkitBase/src/problems/intervalnonlinearproblem.jl b/lib/ModelingToolkitBase/src/problems/intervalnonlinearproblem.jl index 94965a7cb5..3a943c800c 100644 --- a/lib/ModelingToolkitBase/src/problems/intervalnonlinearproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/intervalnonlinearproblem.jl @@ -47,21 +47,40 @@ function SciMLBase.IntervalNonlinearProblem( sys::System, uspan::NTuple{2}, parammap = SciMLBase.NullParameters(); check_compatibility = true, expression = Val{false}, kwargs... ) + fn_opts = SciMLFunctionOptions(; check_compatibility, expression, kwargs...) + opts = SciMLProblemOptions( + sys; + fn_opts, build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return SciMLBase.IntervalNonlinearProblem(sys, uspan, parammap, opts; kwargs...) +end + +""" + SciMLBase.IntervalNonlinearProblem(sys::System, uspan, parammap, opts::SciMLProblemOptions; kwargs...) + +Public entry point that builds an `IntervalNonlinearProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. +""" +function SciMLBase.IntervalNonlinearProblem( + sys::System, uspan::NTuple{2}, parammap, opts::SciMLProblemOptions{E}; kwargs... + ) where {E} check_complete(sys, IntervalNonlinearProblem) - check_compatibility && check_compatible_system(IntervalNonlinearProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(IntervalNonlinearProblem, sys) u0map = unknowns(sys) .=> uspan[1] op = anydict([unknowns(sys)[1] => uspan[1]]) merge!(op, to_varmap(parammap, parameters(sys))) f, u0, p = process_SciMLProblem( - IntervalNonlinearFunction, sys, op; - check_compatibility, expression, kwargs... + IntervalNonlinearFunction, sys, op, opts; options_struct = Val(true), kwargs... ) kwargs = process_kwargs(sys; kwargs...) args = (; f, uspan, p) - return maybe_codegen_scimlproblem(expression, IntervalNonlinearProblem, args; kwargs...) + return maybe_codegen_scimlproblem(Val{E}, IntervalNonlinearProblem, args; kwargs...) end function check_compatible_system( diff --git a/lib/ModelingToolkitBase/src/problems/linearproblem.jl b/lib/ModelingToolkitBase/src/problems/linearproblem.jl index 529030964e..ac16080796 100644 --- a/lib/ModelingToolkitBase/src/problems/linearproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/linearproblem.jl @@ -98,20 +98,42 @@ function SciMLBase.LinearProblem{iip}( eval_module = @__MODULE__, u0_constructor = identity, u0_eltype = nothing, kwargs... ) where {iip} + fn_opts = SciMLFunctionOptions(; + eval_expression, eval_module, check_compatibility, sparse, expression, kwargs... + ) + opts = SciMLProblemOptions( + sys; + fn_opts, check_length, build_initializeprob = false, symbolic_u0 = true, + u0_constructor, u0_eltype, return_operating_point = true, + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return LinearProblem{iip}(sys, op, opts; kwargs...) +end + +""" + SciMLBase.LinearProblem{iip}(sys::System, op, opts::SciMLProblemOptions; kwargs...) + +Public entry point that builds a `LinearProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. +""" +function SciMLBase.LinearProblem{iip}( + sys::System, op, opts::SciMLProblemOptions{E}; kwargs... + ) where {iip, E} check_complete(sys, LinearProblem) - check_compatibility && check_compatible_system(LinearProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(LinearProblem, sys) u0Type = typeof(op) f, u0, p, op = process_SciMLProblem( - LinearFunction{iip}, sys, op; check_length, expression, - build_initializeprob = false, symbolic_u0 = true, u0_constructor, u0_eltype, - return_operating_point = true, sparse, kwargs... + LinearFunction{iip}, sys, op, opts; options_struct = Val(true), kwargs... ) if u0 !== nothing && any(x -> symbolic_type(x) != NotSymbolic() || x === nothing, u0) u0 = nothing end + (; u0_constructor, u0_eltype) = opts floatT = if u0 === nothing calculate_float_type(op, u0Type) else @@ -120,18 +142,19 @@ function SciMLBase.LinearProblem{iip}( u0_eltype = something(u0_eltype, floatT) u0_constructor = get_p_constructor(u0_constructor, u0Type, u0_eltype) + (; eval_expression, eval_module) = opts.fn_opts.codegen symbolic_interface = f.interface A, b = get_A_b_from_LinearFunction( - sys, f, op; eval_expression, eval_module, expression, u0_constructor + sys, f, op; eval_expression, eval_module, expression = Val{E}, u0_constructor ) - if expression === Val{false} + if !E symbolic_interface = wrap_symbolic_linear_interface(symbolic_interface, iip, A, b, p) end kwargs = (; u0, process_kwargs(sys; kwargs...)..., f = symbolic_interface) args = (; A, b, p) - return maybe_codegen_scimlproblem(expression, LinearProblem{iip}, args; kwargs...) + return maybe_codegen_scimlproblem(Val{E}, LinearProblem{iip}, args; kwargs...) end function __make_fww(@nospecialize(f), retT::DataType, argsT::DataType) diff --git a/lib/ModelingToolkitBase/src/problems/nonlinearproblem.jl b/lib/ModelingToolkitBase/src/problems/nonlinearproblem.jl index 715e0668d9..e3e10ce8dc 100644 --- a/lib/ModelingToolkitBase/src/problems/nonlinearproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/nonlinearproblem.jl @@ -117,17 +117,40 @@ end sys::System, op; expression = Val{false}, lb = nothing, ub = nothing, check_length = true, check_compatibility = true, kwargs... ) where {iip, spec} + if is_time_dependent(sys) + sys = NonlinearSystem(sys) + end + fn_opts = SciMLFunctionOptions(; check_compatibility, expression, kwargs...) + opts = SciMLProblemOptions( + sys; + fn_opts, check_length, build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return NonlinearProblem{iip, spec}(sys, op, opts; lb, ub, kwargs...) +end + +""" + SciMLBase.NonlinearProblem{iip, spec}(sys::System, op, opts::SciMLProblemOptions; lb = nothing, ub = nothing, kwargs...) + +Public entry point that builds a `NonlinearProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. +""" +function SciMLBase.NonlinearProblem{iip, spec}( + sys::System, op, opts::SciMLProblemOptions{E}; + lb = nothing, ub = nothing, kwargs... + ) where {iip, spec, E} check_complete(sys, NonlinearProblem) if is_time_dependent(sys) sys = NonlinearSystem(sys) end - check_compatibility && check_compatible_system(NonlinearProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(NonlinearProblem, sys) _iip = resolve_iip(iip, op) f, u0, p = process_SciMLProblem( - NonlinearFunction{_iip, spec}, sys, op; - check_length, check_compatibility, expression, kwargs... + NonlinearFunction{_iip, spec}, sys, op, opts; options_struct = Val(true), kwargs... ) if lb === nothing && ub === nothing @@ -139,7 +162,7 @@ end args = (; f, u0, p, ptype) return maybe_codegen_scimlproblem( - expression, NonlinearProblem{_iip}, args; lb, ub, kwargs... + Val{E}, NonlinearProblem{_iip}, args; lb, ub, kwargs... ) end @@ -172,14 +195,39 @@ end sys::System, op; check_length = false, lb = nothing, ub = nothing, check_compatibility = true, expression = Val{false}, kwargs... ) where {iip, spec} + fn_opts = SciMLFunctionOptions(; check_compatibility, expression, kwargs...) + opts = SciMLProblemOptions( + sys; + fn_opts, check_length, build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return NonlinearLeastSquaresProblem{iip, spec}(sys, op, opts; lb, ub, kwargs...) +end + +""" + SciMLBase.NonlinearLeastSquaresProblem{iip, spec}(sys::System, op, opts::SciMLProblemOptions; lb = nothing, ub = nothing, kwargs...) + +Public entry point that builds a `NonlinearLeastSquaresProblem` directly from a +pre-assembled [`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. +""" +function SciMLBase.NonlinearLeastSquaresProblem{iip, spec}( + sys::System, op, opts::SciMLProblemOptions{E}; + lb = nothing, ub = nothing, kwargs... + ) where {iip, spec, E} check_complete(sys, NonlinearLeastSquaresProblem) - check_compatibility && check_compatible_system(NonlinearLeastSquaresProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(NonlinearLeastSquaresProblem, sys) _iip = resolve_iip(iip, op) f, u0, p = process_SciMLProblem( - NonlinearFunction{_iip}, sys, op; - check_length, expression, kwargs... + # `NonlinearFunction{_iip}` (bare, no `spec`) would otherwise resolve to `{_iip, + # AutoSpecialize}` via its keyword-based fallback method; the opts-accepting + # method has no such `{iip}`-only fallback, so `AutoSpecialize` must be named + # explicitly here to match. + NonlinearFunction{_iip, SciMLBase.AutoSpecialize}, sys, op, opts; + options_struct = Val(true), kwargs... ) if lb === nothing && ub === nothing @@ -190,7 +238,7 @@ end args = (; f, u0, p) return maybe_codegen_scimlproblem( - expression, NonlinearLeastSquaresProblem{_iip}, args; lb, ub, kwargs... + Val{E}, NonlinearLeastSquaresProblem{_iip}, args; lb, ub, kwargs... ) end diff --git a/lib/ModelingToolkitBase/src/problems/odeproblem.jl b/lib/ModelingToolkitBase/src/problems/odeproblem.jl index 887f0f8367..da576c669b 100644 --- a/lib/ModelingToolkitBase/src/problems/odeproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/odeproblem.jl @@ -120,33 +120,65 @@ Base.@nospecializeinfer function _ode_problem( ::Type{ODEProblem{iip, spec}}, sys::System, @nospecialize(op), tspan; @nospecialize(callback = nothing), check_length = true, eval_expression = false, expression = Val{false}, eval_module = @__MODULE__, check_compatibility = true, - _skip_events = false, kwargs... + _skip_events = false, _skip_tstops = false, kwargs... ) where {iip, spec} + fn_opts = SciMLFunctionOptions(; + t = tspan !== nothing ? tspan[1] : tspan, eval_expression, eval_module, + check_compatibility, expression, kwargs... + ) + opts = SciMLProblemOptions( + sys; + fn_opts, check_length, build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return ODEProblem{iip, spec}( + sys, op, tspan, opts; callback, _skip_events, _skip_tstops, kwargs... + ) +end + +""" + SciMLBase.ODEProblem{iip, spec}(sys::System, op, tspan, opts::SciMLProblemOptions; kwargs...) + +Public entry point that builds an `ODEProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. + +`_skip_events`/`_skip_tstops` (default `false`) are explicit keywords here — not part of +`kwargs` — since they're relevant only to `process_kwargs` below, not the inner `ODEFunction` +build; the opts-accepting `ODEFunction` method has no generic `kwargs...` sink to harmlessly +absorb them the way its keyword-based wrapper does. +""" +Base.@nospecializeinfer function SciMLBase.ODEProblem{iip, spec}( + sys::System, @nospecialize(op), tspan, opts::SciMLProblemOptions{E}; + @nospecialize(callback = nothing), _skip_events = false, _skip_tstops = false, + kwargs... + ) where {iip, spec, E} check_complete(sys, ODEProblem) - check_compatibility && check_compatible_system(ODEProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(ODEProblem, sys) + + opts = maybe_derive_t_from_tspan(opts, tspan) _iip = resolve_iip(iip, op) if _iip === true f, u0, p = process_SciMLProblem( - ODEFunction{true, spec}, sys, op; - t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, - eval_module, expression, check_compatibility, kwargs... + ODEFunction{true, spec}, sys, op, opts; options_struct = Val(true), kwargs... ) else f, u0, p = process_SciMLProblem( - ODEFunction{false, spec}, sys, op; - t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, - eval_module, expression, check_compatibility, kwargs... + ODEFunction{false, spec}, sys, op, opts; options_struct = Val(true), kwargs... ) end + (; eval_expression, eval_module) = opts.fn_opts.codegen kwargs = process_kwargs( - sys; expression, callback, eval_expression, eval_module, op, _skip_events, tspan, kwargs... + sys; expression = Val{E}, callback, eval_expression, eval_module, op, _skip_events, + _skip_tstops, tspan, kwargs... ) ptype = getmetadata(sys, ProblemTypeCtx, StandardODEProblem()) args = (; f, u0, tspan, p, ptype) - return maybe_codegen_scimlproblem(expression, ODEProblem{_iip}, args; kwargs...) + maybe_codegen_scimlproblem(Val{E}, ODEProblem{_iip}, args; kwargs...) end Base.@nospecializeinfer @fallback_iip_specialize function SciMLBase.ODEProblem{iip, spec}( @@ -182,21 +214,45 @@ end sys::System, op; check_length = true, check_compatibility = true, expression = Val{false}, kwargs... ) where {iip, spec} + fn_opts = SciMLFunctionOptions(; check_compatibility, expression, kwargs...) + opts = SciMLProblemOptions( + sys; + fn_opts, check_length, is_steadystateprob = true, + build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return SteadyStateProblem{iip, spec}(sys, op, opts; kwargs...) +end + +""" + DiffEqBase.SteadyStateProblem{iip, spec}(sys::System, op, opts::SciMLProblemOptions; kwargs...) + +Public entry point that builds a `SteadyStateProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. +""" +function DiffEqBase.SteadyStateProblem{iip, spec}( + sys::System, op, opts::SciMLProblemOptions{E}; kwargs... + ) where {iip, spec, E} check_complete(sys, SteadyStateProblem) - check_compatibility && check_compatible_system(SteadyStateProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(SteadyStateProblem, sys) _iip = resolve_iip(iip, op) f, u0, p = process_SciMLProblem( - ODEFunction{_iip}, sys, op; - steady_state = true, check_length, check_compatibility, expression, - is_steadystateprob = true, kwargs... + # `ODEFunction{_iip}` (bare, no `spec`) would otherwise resolve to `{_iip, + # AutoSpecialize}` via its keyword-based fallback method; the opts-accepting + # method has no such `{iip}`-only fallback, so `AutoSpecialize` must be named + # explicitly here to match. + ODEFunction{_iip, SciMLBase.AutoSpecialize}, sys, op, opts; + options_struct = Val(true), steady_state = true, kwargs... ) - kwargs = process_kwargs(sys; expression, tspan = (0, Inf), kwargs...) + kwargs = process_kwargs(sys; expression = Val{E}, tspan = (0, Inf), kwargs...) args = (; f, u0, p) - maybe_codegen_scimlproblem(expression, SteadyStateProblem{_iip}, args; kwargs...) + maybe_codegen_scimlproblem(Val{E}, SteadyStateProblem{_iip}, args; kwargs...) end function check_compatible_system( diff --git a/lib/ModelingToolkitBase/src/problems/optimizationproblem.jl b/lib/ModelingToolkitBase/src/problems/optimizationproblem.jl index 3666dc923f..7f2dae04f5 100644 --- a/lib/ModelingToolkitBase/src/problems/optimizationproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/optimizationproblem.jl @@ -113,13 +113,34 @@ function SciMLBase.OptimizationProblem{iip}( ub = nothing, check_compatibility = true, expression = Val{false}, kwargs... ) where {iip} + fn_opts = SciMLFunctionOptions(; check_compatibility, expression, kwargs...) + opts = SciMLProblemOptions( + sys; + fn_opts, tofloat = false, check_length = false, + build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return OptimizationProblem{iip}(sys, op, opts; lb, ub, kwargs...) +end + +""" + SciMLBase.OptimizationProblem{iip}(sys::System, op, opts::SciMLProblemOptions; lb = nothing, ub = nothing, kwargs...) + +Public entry point that builds an `OptimizationProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. +""" +function SciMLBase.OptimizationProblem{iip}( + sys::System, op, opts::SciMLProblemOptions{E}; + lb = nothing, ub = nothing, kwargs... + ) where {iip, E} check_complete(sys, OptimizationProblem) - check_compatibility && check_compatible_system(OptimizationProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(OptimizationProblem, sys) f, u0, p = process_SciMLProblem( - OptimizationFunction{iip}, sys, op; - check_compatibility, tofloat = false, check_length = false, expression, kwargs... + OptimizationFunction{iip}, sys, op, opts; options_struct = Val(true), kwargs... ) dvs = unknowns(sys) @@ -164,7 +185,7 @@ function SciMLBase.OptimizationProblem{iip}( kwargs = process_kwargs(sys; kwargs...) kwargs = (; lb, ub, int, lcons, ucons, kwargs...) args = (; f, u0, p) - return maybe_codegen_scimlproblem(expression, OptimizationProblem{iip}, args; kwargs...) + return maybe_codegen_scimlproblem(Val{E}, OptimizationProblem{iip}, args; kwargs...) end function check_compatible_system( diff --git a/lib/ModelingToolkitBase/src/problems/sddeproblem.jl b/lib/ModelingToolkitBase/src/problems/sddeproblem.jl index 1fbe1be5a4..b579b42895 100644 --- a/lib/ModelingToolkitBase/src/problems/sddeproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/sddeproblem.jl @@ -65,29 +65,65 @@ end callback = nothing, check_length = true, checkbounds = false, eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, u0_constructor = identity, sparse = false, sparsenoise = sparse, - expression = Val{false}, kwargs... + expression = Val{false}, seed = missing, constant_lags = missing, kwargs... ) where {iip, spec} + fn_opts = SciMLFunctionOptions(; + t = tspan !== nothing ? tspan[1] : tspan, eval_expression, eval_module, + checkbounds, check_compatibility, sparse, expression, kwargs... + ) + opts = SciMLProblemOptions( + sys; + fn_opts, check_length, symbolic_u0 = true, u0_constructor, + build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return SDDEProblem{iip, spec}( + sys, op, tspan, opts; callback, sparsenoise, seed, constant_lags, kwargs... + ) +end + +""" + SciMLBase.SDDEProblem{iip, spec}(sys::System, op, tspan, opts::SciMLProblemOptions; callback = nothing, sparsenoise = opts.fn_opts.sparse, kwargs...) + +Public entry point that builds an `SDDEProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. + +`seed`/`constant_lags` (default `missing`, meaning "not explicitly provided by the caller") +are explicit keywords here — not part of `kwargs` — since they're relevant only to the final +`SciMLBase.SDDEProblem` construction, not the inner `SDDEFunction` build; the opts-accepting +`SDDEFunction` method has no generic `kwargs...` sink to harmlessly absorb them the way its +keyword-based wrapper does. +""" +function SciMLBase.SDDEProblem{iip, spec}( + sys::System, op, tspan, opts::SciMLProblemOptions{E}; + callback = nothing, sparsenoise = opts.fn_opts.sparse, seed = missing, + constant_lags = missing, kwargs... + ) where {iip, spec, E} check_complete(sys, SDDEProblem) - check_compatibility && check_compatible_system(SDDEProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(SDDEProblem, sys) + + opts = maybe_derive_t_from_tspan(opts, tspan) _iip = resolve_iip(iip, op) f, u0, p = process_SciMLProblem( - SDDEFunction{_iip, spec}, sys, op; - t = tspan !== nothing ? tspan[1] : tspan, check_length, checkbounds, - eval_expression, eval_module, check_compatibility, sparse, symbolic_u0 = true, - expression, u0_constructor, kwargs... + SDDEFunction{_iip, spec}, sys, op, opts; options_struct = Val(true), kwargs... ) + (; u0_constructor) = opts + (; eval_expression, eval_module) = opts.fn_opts.codegen + checkbounds = opts.fn_opts.codegen.codegen.checkbounds h = generate_history( sys, u0, GeneratedFunctionOptions(; - expression, wrap_gfw = Val{true}, eval_expression, eval_module, + expression = Val{E}, wrap_gfw = Val{true}, eval_expression, eval_module, codegen_function_options = Symbolics.CodegenFunctionOptions(; checkbounds) ) ) - if expression == Val{true} + if E if u0 !== nothing u0 = :($u0_constructor($map($float, h(p, tspan[1])))) end @@ -100,15 +136,18 @@ end noise, noise_rate_prototype = calculate_noise_and_rate_prototype(sys, u0; sparsenoise) kwargs = process_kwargs(sys; callback, eval_expression, eval_module, op, tspan, kwargs...) - if expression == Val{true} + if E g = :(f.g) else g = f.g end args = (; f, g, u0, h, tspan, p) - kwargs = (; noise, noise_rate_prototype, kwargs...) + seed_kw = seed === missing ? (;) : (; seed) + constant_lags = resolve_constant_lags(sys, constant_lags, p) + constant_lags_kw = constant_lags === missing ? (;) : (; constant_lags) + kwargs = (; noise, noise_rate_prototype, seed_kw..., constant_lags_kw..., kwargs...) - return maybe_codegen_scimlproblem(expression, SDDEProblem{_iip}, args; kwargs...) + return maybe_codegen_scimlproblem(Val{E}, SDDEProblem{_iip}, args; kwargs...) end function check_compatible_system( diff --git a/lib/ModelingToolkitBase/src/problems/sdeproblem.jl b/lib/ModelingToolkitBase/src/problems/sdeproblem.jl index 98cf7a5d3a..4f32e54c99 100644 --- a/lib/ModelingToolkitBase/src/problems/sdeproblem.jl +++ b/lib/ModelingToolkitBase/src/problems/sdeproblem.jl @@ -86,42 +86,78 @@ end sys::System, op, tspan; callback = nothing, check_length = true, eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, sparse = false, - sparsenoise = sparse, expression = Val{false}, _skip_events = false, kwargs... + sparsenoise = sparse, expression = Val{false}, _skip_events = false, + _skip_tstops = false, + noise = missing, noise_rate_prototype = missing, seed = missing, kwargs... ) where {iip, spec} + fn_opts = SciMLFunctionOptions(; + t = tspan !== nothing ? tspan[1] : tspan, eval_expression, eval_module, + check_compatibility, sparse, expression, kwargs... + ) + opts = SciMLProblemOptions( + sys; + fn_opts, check_length, build_initializeprob = supports_initialization(sys), + time_dependent_init = is_time_dependent(sys), + circular_dependency_max_cycle_length = length(all_symbols(sys)), + kwargs... + ) + return SDEProblem{iip, spec}( + sys, op, tspan, opts; callback, sparsenoise, _skip_events, _skip_tstops, noise, + noise_rate_prototype, seed, kwargs... + ) +end + +""" + SciMLBase.SDEProblem{iip, spec}(sys::System, op, tspan, opts::SciMLProblemOptions; kwargs...) + +Public entry point that builds an `SDEProblem` directly from a pre-assembled +[`SciMLProblemOptions`](@ref), bypassing the `kwargs...` wrapper above. + +`noise`/`noise_rate_prototype`/`seed` (default `missing`, meaning "not explicitly provided +by the caller") are explicit keywords here — not part of `kwargs` — since they're relevant +only to this outer `SDEProblem` construction (`seed` reaches the final +`SciMLBase.SDEProblem` call only), not the inner `SDEFunction` build; the opts-accepting +`SDEFunction` method has no generic `kwargs...` sink to harmlessly absorb them the way its +keyword-based wrapper does. `_skip_events`/`_skip_tstops` are likewise explicit since they're +relevant only to `process_kwargs` below. +""" +function SciMLBase.SDEProblem{iip, spec}( + sys::System, op, tspan, opts::SciMLProblemOptions{E}; + callback = nothing, sparsenoise = opts.fn_opts.sparse, _skip_events = false, + _skip_tstops = false, + noise = missing, noise_rate_prototype = missing, seed = missing, kwargs... + ) where {iip, spec, E} check_complete(sys, SDEProblem) - check_compatibility && check_compatible_system(SDEProblem, sys) + opts.fn_opts.check_compatibility && check_compatible_system(SDEProblem, sys) + + opts = maybe_derive_t_from_tspan(opts, tspan) _iip = resolve_iip(iip, op) f, u0, p = process_SciMLProblem( - SDEFunction{_iip, spec}, sys, op; - t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, - eval_module, check_compatibility, sparse, expression, kwargs... + SDEFunction{_iip, spec}, sys, op, opts; options_struct = Val(true), kwargs... ) # Only calculate noise and noise_rate_prototype if not provided by user - if !haskey(kwargs, :noise) && !haskey(kwargs, :noise_rate_prototype) + if noise === missing && noise_rate_prototype === missing noise, noise_rate_prototype = calculate_noise_and_rate_prototype(sys, u0; sparsenoise) - elseif !haskey(kwargs, :noise) + elseif noise === missing noise, _ = calculate_noise_and_rate_prototype(sys, u0; sparsenoise) - noise_rate_prototype = kwargs[:noise_rate_prototype] - elseif !haskey(kwargs, :noise_rate_prototype) + elseif noise_rate_prototype === missing _, noise_rate_prototype = calculate_noise_and_rate_prototype(sys, u0; sparsenoise) - noise = kwargs[:noise] - else - noise = kwargs[:noise] - noise_rate_prototype = kwargs[:noise_rate_prototype] end + (; eval_expression, eval_module) = opts.fn_opts.codegen kwargs = process_kwargs( - sys; expression, callback, eval_expression, eval_module, - op, _skip_events, tspan, kwargs... + sys; expression = Val{E}, callback, eval_expression, eval_module, + op, _skip_events, _skip_tstops, tspan, kwargs... ) args = (; f, u0, tspan, p) - kwargs = (; noise, noise_rate_prototype, kwargs...) + seed_kw = seed === missing ? (;) : (; seed) + kwargs = (; noise, noise_rate_prototype, seed_kw..., kwargs...) - return maybe_codegen_scimlproblem(expression, SDEProblem{_iip}, args; kwargs...) + return maybe_codegen_scimlproblem(Val{E}, SDEProblem{_iip}, args; kwargs...) end function check_compatible_system(T::Union{Type{SDEFunction}, Type{SDEProblem}}, sys::System) diff --git a/lib/ModelingToolkitBase/src/systems/callbacks.jl b/lib/ModelingToolkitBase/src/systems/callbacks.jl index 452b195381..8b1dba0638 100644 --- a/lib/ModelingToolkitBase/src/systems/callbacks.jl +++ b/lib/ModelingToolkitBase/src/systems/callbacks.jl @@ -1539,7 +1539,11 @@ Called from [`compile_equational_affect`](@ref) when `!isempty(equations(system( Base.@nospecializeinfer function compile_implicit_affect( @nospecialize(aff::AffectSystem), sys; reset_jumps = false, eval_expression = false, eval_module = @__MODULE__, - @nospecialize(op = nothing), kwargs... + @nospecialize(op = nothing), + # `checkvars` is only meaningful for `compile_explicit_affect`'s + # `Symbolics.CodegenFunctionOptions`; intercept and discard it here so it isn't + # forwarded to `ImplicitDiscreteProblem` below, which has no keyword of that name. + checkvars = false, kwargs... ) affsys = system(aff) ps_to_update = discretes(aff) diff --git a/lib/ModelingToolkitBase/src/systems/problem_utils.jl b/lib/ModelingToolkitBase/src/systems/problem_utils.jl index 490c169572..ffe32580fc 100644 --- a/lib/ModelingToolkitBase/src/systems/problem_utils.jl +++ b/lib/ModelingToolkitBase/src/systems/problem_utils.jl @@ -1763,6 +1763,11 @@ Like `SciMLFunctionOptions`, this struct does not attempt to hold every keyword `*Function` constructor might recognize (e.g. `jac`, `steady_state`, or any other constructor-specific extra) — `__process_SciMLProblem` still takes a trailing `kwargs...` for those, forwarded blindly to `constructor` exactly as before. + +The keyword constructor takes (and drops) a trailing `kwargs...`, matching +`SciMLFunctionOptions`: this lets a `*Problem` constructor's own kwargs wrapper splat its +full keyword bag in here to pick up any recognized override, without erroring on the +constructor-specific extras that live outside this struct. """ struct SciMLProblemOptions{expression} fn_opts::SciMLFunctionOptions{expression} @@ -1797,6 +1802,25 @@ struct SciMLProblemOptions{expression} init_compiler_options::CompilerOptions end +""" + $TYPEDSIGNATURES + +Every field name of [`SciMLProblemOptions`](@ref)/[`SciMLFunctionOptions`](@ref)/ +`GeneratedFunctionOptions`/`Symbolics.CodegenFunctionOptions`, plus `:ddvs`. Used by +`__process_SciMLProblem` to strip redundant option-struct keywords out of a `*Problem` +constructor's residual `kwargs` before calling a `*Function`'s opts-accepting method +directly (which, unlike its keyword-based wrapper, has no generic `kwargs...` sink to +silently absorb them). This situation arises because `*Problem` constructors reachable as +`InitializationProblem`'s `TProb` (e.g. `NonlinearProblem`) receive a `kwargs` bag that +legitimately carries these names for their own opts construction, which then becomes +redundant baggage once `opts` itself is built and passed along. +""" +const SCIML_FN_OPTS_KWARG_NAMES = ( + fieldnames(SciMLProblemOptions)..., fieldnames(SciMLFunctionOptions)..., + fieldnames(GeneratedFunctionOptions)..., fieldnames(Symbolics.CodegenFunctionOptions)..., + :ddvs, +) + function SciMLProblemOptions( sys::AbstractSystem; fn_opts::SciMLFunctionOptions{E}, @@ -1818,6 +1842,7 @@ function SciMLProblemOptions( allow_incomplete::Bool = false, is_initializeprob::Bool = false, is_steadystateprob::Bool = false, return_operating_point::Bool = false, init_compiler_options::CompilerOptions = CompilerOptions(), + kwargs... ) where {E} if !(guesses isa SymmapT) guesses = anydict(guesses) @@ -1835,6 +1860,39 @@ function SciMLProblemOptions( ) end +""" + $(TYPEDSIGNATURES) + +Return `opts` with `opts.fn_opts.t` derived from `tspan[1]` (or `tspan` itself, when it is +`nothing`) if it isn't already set. The keyword-based `*Problem` constructors always set +`t` explicitly when building `opts` from `tspan`, so this only matters for a caller that +builds `opts` directly and calls the `(sys, op, tspan, opts::SciMLProblemOptions)` method; +it lets that caller skip `t` and still get the same defaulting. +""" +function maybe_derive_t_from_tspan(opts::SciMLProblemOptions, tspan) + opts.fn_opts.t === nothing || return opts + t = tspan !== nothing ? tspan[1] : tspan + return setproperties(opts; fn_opts = setproperties(opts.fn_opts; t)) +end + +""" + $(TYPEDSIGNATURES) + +Return `constant_lags` with every symbolic entry (e.g. a reference to a parameter of `sys`, +such as `sys.osc1.τ`) replaced by its concrete value as given by `p`. Non-symbolic entries +(plain numbers baked in by the caller) are returned unchanged. Returns `missing` as-is. +Used by `DDEProblem`/`SDDEProblem`, which accept `constant_lags` as a bespoke keyword that +bypasses `process_SciMLProblem`'s `op`-based substitution entirely (it is never part of the +system's unknowns or the operating point), so it must be resolved separately here using the +already-built `p`. +""" +function resolve_constant_lags(sys::AbstractSystem, constant_lags, p) + constant_lags === missing && return constant_lags + return map(constant_lags) do cl + symbolic_type(cl) == NotSymbolic() ? cl : getp(sys, cl)(p) + end +end + """ $(TYPEDSIGNATURES) @@ -1851,7 +1909,7 @@ function maybe_build_initialization_problem( sys::AbstractSystem, iip::Bool, op::SymmapT, t, guesses, opts::SciMLProblemOptions; # Intercept `expression` because we don't support it here yet - expression = Val{false}, kwargs... + expression = Val{false}, ) (; floatT, implicit_dae, warn_initialize_determined, initialization_eqs, @@ -1871,7 +1929,7 @@ function maybe_build_initialization_problem( orig_op = copy(op) initializeprob = ModelingToolkitBase.InitializationProblem{iip}( - sys, t, op, opts; guesses, fast_path = true, kwargs... + sys, t, op, opts; guesses, fast_path = true ) initsys = initializeprob.f.sys::System needs_remake = false @@ -1920,7 +1978,7 @@ function maybe_build_initialization_problem( end get_initial_unknowns = if time_dependent_init - GetUpdatedU0(sys, initsys, op; eval_expression, eval_module, kwargs...) + GetUpdatedU0(sys, initsys, op; eval_expression, eval_module) else nothing end @@ -1931,7 +1989,7 @@ function maybe_build_initialization_problem( use_scc, time_dependent_init, ReconstructInitializeprob( sys, initsys; u0_constructor, - p_constructor, eval_expression, eval_module, is_steadystateprob, kwargs... + p_constructor, eval_expression, eval_module, is_steadystateprob ), get_initial_unknowns, SetInitialUnknowns(sys), missing_guess_value ) @@ -1942,7 +2000,7 @@ function maybe_build_initialization_problem( if isempty(solved_unknowns) initializeprobmap = nothing else - initializeprobmap = u0_constructor ∘ PromoteToTunableEltype(CopyParamsByTemplate(initializeprob.f.sys, solved_unknowns; eval_expression, eval_module, kwargs...), floatT) + initializeprobmap = u0_constructor ∘ PromoteToTunableEltype(CopyParamsByTemplate(initializeprob.f.sys, solved_unknowns; eval_expression, eval_module), floatT) if iip initializeprobmap = __iip_u0_ad_wrapper ∘ initializeprobmap end @@ -1960,7 +2018,7 @@ function maybe_build_initialization_problem( initializeprobpmap = nothing else initializeprobpmap = construct_initializeprobpmap( - sys, initsys; p_constructor, eval_expression, eval_module, kwargs... + sys, initsys; p_constructor, eval_expression, eval_module ) end @@ -2122,7 +2180,7 @@ end function __process_SciMLProblem( @nospecialize(constructor), sys::AbstractSystem, op::AnyDict, - opts::SciMLProblemOptions; kwargs... + opts::SciMLProblemOptions; options_struct = Val(false), kwargs... ) (; fn_opts, floatT, u0Type, u0_eltype, build_initializeprob, implicit_dae, guesses, @@ -2164,9 +2222,15 @@ function __process_SciMLProblem( end if build_initializeprob + # `kwargs` here is never anything but bespoke, `constructor`-specific extras (e.g. + # `steady_state`, `resid_prototype`) — everything `maybe_build_initialization_problem` + # and the `InitializationProblem`/`TProb` machinery it drives actually need is already + # captured by `opts`. Forwarding it further only serves to leak those extras deep + # into the (possibly quite different) `*Function` built for the initialization + # sub-problem, where they're meaningless at best and a hard error at worst. kws = maybe_build_initialization_problem( sys, constructor <: SciMLBase.AbstractSciMLFunction{true}, - op, t, guesses, opts; kwargs... + op, t, guesses, opts ) kwargs = merge(kwargs, kws) @@ -2269,13 +2333,34 @@ function __process_SciMLProblem( ) end - f = constructor( - sys; u0 = u0, p = p, t = t, - eval_expression = eval_expression, - eval_module = eval_module, - compiler_options, - kwargs... - ) + if options_struct === Val(true) + # Call the `*Function`'s own opts-accepting method directly with `fn_opts`, rather + # than its keyword-based wrapper: `fn_opts` already holds every `SciMLFunctionOptions` + # field correctly, sidestepping the fragile "re-derive and re-forward each field by + # keyword" approach entirely. `u0`/`p`/`t` must be refreshed since `fn_opts.u0` etc. + # still hold the original, pre-processing request values; `initialization_data` + # (freshly computed by `maybe_build_initialization_problem` above, if it ran) must be + # injected the same way, since — unlike the keyword-based wrappers — the + # opts-accepting methods have no generic `kwargs...` sink to catch it as a loose + # keyword. The rest of `kwargs` is stripped of anything matching a known + # option-struct field name (see `SCIML_FN_OPTS_KWARG_NAMES`) — `*Problem` + # constructors reachable as `InitializationProblem`'s `TProb` receive a `kwargs` + # bag that redundantly carries these (legitimate for their own opts construction, + # meaningless here now that `opts`/`fn_opts` already reflect them) — keeping only + # genuinely bespoke extras (`resid_prototype`, `steady_state`, `nlstep`, ...). + initialization_data = get(kwargs, :initialization_data, fn_opts.initialization_data) + fn_opts = setproperties(fn_opts; u0, p, t, initialization_data) + kwargs = Base.structdiff(kwargs, NamedTuple{SCIML_FN_OPTS_KWARG_NAMES}) + f = constructor(sys, fn_opts; kwargs...) + else + f = constructor( + sys; u0 = u0, p = p, t = t, + eval_expression = eval_expression, + eval_module = eval_module, + compiler_options, + kwargs... + ) + end if return_operating_point return implicit_dae ? (f, du0, u0, p, op) : (f, u0, p, op) else diff --git a/lib/ModelingToolkitBase/test/dde.jl b/lib/ModelingToolkitBase/test/dde.jl index d46adc0644..865e0e9a69 100644 --- a/lib/ModelingToolkitBase/test/dde.jl +++ b/lib/ModelingToolkitBase/test/dde.jl @@ -151,7 +151,7 @@ if @isdefined(ModelingToolkit) end end sys = mtkcompile(coupledOsc) -prob = DDEProblem(sys, [], (0.0, 10.0); constant_lags = [sys.osc1.τ, sys.osc2.τ]) +prob = DDEProblem(sys, [], (0.0, 10.0)) sol = solve(prob, MethodOfSteps(@isdefined(ModelingToolkit) ? Tsit5() : Rodas5P())) obsfn = ModelingToolkitBase.build_explicit_observed_function( sys, [sys.osc1.delx, sys.osc2.delx] diff --git a/lib/ModelingToolkitBase/test/dq_units.jl b/lib/ModelingToolkitBase/test/dq_units.jl index c22d6e8b17..04de253dfb 100644 --- a/lib/ModelingToolkitBase/test/dq_units.jl +++ b/lib/ModelingToolkitBase/test/dq_units.jl @@ -233,7 +233,7 @@ maj2 = SymbolicMassActionJump(γ, [S => 1], [S => -1]) p = [pend.g => 1.0, pend.L => 1.0] guess = [pend.λ => 0.0] @test prob = ODEProblem( - pend, [u0; p], (0.0, 1.0); guesses = guess, check_units = false + pend, [u0; p], (0.0, 1.0); guesses = guess ) isa Any end diff --git a/lib/ModelingToolkitBase/test/serialization.jl b/lib/ModelingToolkitBase/test/serialization.jl index 16814a6830..0de8d214ee 100644 --- a/lib/ModelingToolkitBase/test/serialization.jl +++ b/lib/ModelingToolkitBase/test/serialization.jl @@ -50,7 +50,7 @@ sol_ = solve(prob_, ImplicitEuler()) # build the observable function expression # ODEProblemExpr with observedfun_exp included -probexpr = ODEProblem{true}(ss, [capacitor.v => 0.0], (0, 0.1); expr = Val{true}, missing_guess_value); +probexpr = ODEProblem{true}(ss, [capacitor.v => 0.0], (0, 0.1); expression = Val{true}, missing_guess_value); prob_obs = eval(probexpr) sol_obs = solve(prob_obs, ImplicitEuler()) @test sol_obs[all_obs] == sol[all_obs]