diff --git a/demo.jl b/demo.jl deleted file mode 100644 index 0a1bea54db..0000000000 --- a/demo.jl +++ /dev/null @@ -1,107 +0,0 @@ -macro mtkcompile(ex...) - quote - @mtkbuild $(ex...) - end -end - -function mtkcompile(args...; kwargs...) - structural_simplify(args...; kwargs...) -end - -################################# - -using ModelingToolkit, OrdinaryDiffEq, StochasticDiffEq -using ModelingToolkit: t_nounits as t, D_nounits as D - -## ODEs - -@parameters g -@variables x(t) y(t) λ(t) -eqs = [D(D(x)) ~ λ * x - D(D(y)) ~ λ * y - g - x^2 + y^2 ~ 1] -@mtkbuild pend = System(eqs, t) -prob = ODEProblem(pend, [x => -1, y => 0], (0.0, 10.0), [g => 1], guesses = [λ => 1]) - -sol = solve(prob, FBDF()) - -## SDEs and unified `System` - -@variables x(t) y(t) z(t) -@parameters σ ρ β -@brownian a - -eqs = [ - D(x) ~ σ * (y - x) + 0.1x * a, - D(y) ~ x * (ρ - z) - y + 0.1y * a, - D(z) ~ x * y - β * z + 0.1z * a -] - -@mtkbuild sys1 = System(eqs, t) - -eqs = [ - D(x) ~ σ * (y - x), - D(y) ~ x * (ρ - z) - y, - D(z) ~ x * y - β * z -] - -noiseeqs = [0.1*x; - 0.1*y; - 0.1*z;;] - -@mtkbuild sys2 = SDESystem(eqs, noiseeqs, t) - -u0 = [ - x => 1.0, - y => 0.0, - z => 0.0] - -p = [σ => 28.0, - ρ => 10.0, - β => 8 / 3] - -sdeprob = SDEProblem(sys1, u0, (0.0, 10.0), p) -sdesol = solve(sdeprob, ImplicitEM()) - -odeprob = ODEProblem(sys1, u0, (0.0, 10.0), p) # error! -odeprob = ODEProblem(sys1, u0, (0.0, 10.0), p; check_compatibility = false) - -@variables x y z -@parameters σ ρ β - -# Define a nonlinear system -eqs = [0 ~ σ * (y - x), - y ~ x * (ρ - z), - β * z ~ x * y] -@mtkbuild sys = System(eqs) - -## ImplicitDiscrete Affects - -@parameters g -@variables x(t) y(t) λ(t) -eqs = [D(D(x)) ~ λ * x - D(D(y)) ~ λ * y - g - x^2 + y^2 ~ 1] -c_evt = [t ~ 5.0] => [x ~ Pre(x) + 0.1] -@mtkbuild pend = System(eqs, t, continuous_events = c_evt) -prob = ODEProblem(pend, [x => -1, y => 0], (0.0, 10.0), [g => 1], guesses = [λ => 1]) - -sol = solve(prob, FBDF()) - -## `@named` and `ParentScope` - -function SysA(; name, var1) - @variables x(t) - return System([D(x) ~ var1], t; name) -end -function SysB(; name, var1) - @variables x(t) - @named subsys = SysA(; var1) - return System([D(x) ~ x], t; systems = [subsys], name) -end -function SysC(; name) - @variables x(t) - @named subsys = SysB(; var1 = x) - return System([D(x) ~ x], t; systems = [subsys], name) -end -@mtkbuild sys = SysC() diff --git a/src/deprecations.jl b/src/deprecations.jl index 6e8ea23b1c..093480a03d 100644 --- a/src/deprecations.jl +++ b/src/deprecations.jl @@ -23,8 +23,90 @@ for T in [:NonlinearSystem, :DiscreteSystem, :ImplicitDiscreteSystem] @eval @deprecate $T(args...; kwargs...) System(args...; kwargs...) end -for T in [:ODEProblem, :DDEProblem, :SDEProblem, :SDDEProblem, :DAEProblem, - :BVProblem, :DiscreteProblem, :ImplicitDiscreteProblem] +# Time-dependent problems with keyword tspan +for T in [:ODEProblem, :SDEProblem, :BVProblem, :DDEProblem, :SDDEProblem] + for (pType, pCanonical) in [ + (AbstractDict, :p), + (AbstractArray{<:Pair}, :(Dict(p))), + (AbstractArray, :(isempty(p) ? Dict() : Dict(parameters(sys) .=> p))) + ], + (uType, uCanonical) in [ + (Nothing, :(Dict())), + (AbstractDict, :u0), + (AbstractArray{<:Pair}, :(Dict(u0))), + (AbstractArray, :(isempty(u0) ? Dict() : Dict(unknowns(sys) .=> u0))) + ] + + @eval function SciMLBase.$T(sys::System, u0::$uType, tspan, p::$pType; kw...) + ctor = string($T) + uCan = string($(QuoteNode(uCanonical))) + pCan = string($(QuoteNode(pCanonical))) + @warn """ + `$ctor(sys, u0, tspan, p; kw...)` is deprecated. Use + `$ctor(sys, merge($uCan, $pCan); tspan=tspan)` instead. + """ + SciMLBase.$T(sys, merge($uCanonical, $pCanonical); tspan=tspan, kw...) + end + @eval function SciMLBase.$T{iip}( + sys::System, u0::$uType, tspan, p::$pType; kw...) where {iip} + ctor = string($T{iip}) + uCan = string($(QuoteNode(uCanonical))) + pCan = string($(QuoteNode(pCanonical))) + @warn """ + `$ctor(sys, u0, tspan, p; kw...)` is deprecated. Use + `$ctor(sys, merge($uCan, $pCan); tspan=tspan)` instead. + """ + return SciMLBase.$T{iip}(sys, merge($uCanonical, $pCanonical); tspan=tspan, kw...) + end + @eval function SciMLBase.$T{iip, spec}( + sys::System, u0::$uType, tspan, p::$pType; kw...) where {iip, spec} + ctor = string($T{iip, spec}) + uCan = string($(QuoteNode(uCanonical))) + pCan = string($(QuoteNode(pCanonical))) + @warn """ + `$ctor(sys, u0, tspan, p; kw...)` is deprecated. Use + `$ctor(sys, merge($uCan, $pCan); tspan=tspan)` instead. + """ + return $T{iip, spec}(sys, merge($uCanonical, $pCanonical); tspan=tspan, kw...) + end + end + + for pType in [SciMLBase.NullParameters, Nothing], uType in [Any, Nothing] + + @eval function SciMLBase.$T(sys::System, u0::$uType, tspan, p::$pType; kw...) + ctor = string($T) + pT = string($(QuoteNode(pType))) + @warn """ + `$ctor(sys, u0, tspan, p::$pT; kw...)` is deprecated. Use + `$ctor(sys, u0; tspan=tspan)` instead. + """ + $T(sys, u0; tspan=tspan, kw...) + end + @eval function SciMLBase.$T{iip}( + sys::System, u0::$uType, tspan, p::$pType; kw...) where {iip} + ctor = string($T{iip}) + pT = string($(QuoteNode(pType))) + @warn """ + `$ctor(sys, u0, tspan, p::$pT; kw...)` is deprecated. Use + `$ctor(sys, u0; tspan=tspan)` instead. + """ + return $T{iip}(sys, u0; tspan=tspan, kw...) + end + @eval function SciMLBase.$T{iip, spec}( + sys::System, u0::$uType, tspan, p::$pType; kw...) where {iip, spec} + ctor = string($T{iip, spec}) + pT = string($(QuoteNode(pType))) + @warn """ + `$ctor(sys, u0, tspan, p::$pT; kw...)` is deprecated. Use + `$ctor(sys, u0; tspan=tspan)` instead. + """ + return $T{iip, spec}(sys, u0; tspan=tspan, kw...) + end + end +end + +# Problems with positional tspan (unchanged) +for T in [:DAEProblem, :DiscreteProblem, :ImplicitDiscreteProblem] for (pType, pCanonical) in [ (AbstractDict, :p), (AbstractArray{<:Pair}, :(Dict(p))), diff --git a/src/problems/bvproblem.jl b/src/problems/bvproblem.jl index 1c193bca51..5c5419ca3e 100644 --- a/src/problems/bvproblem.jl +++ b/src/problems/bvproblem.jl @@ -1,5 +1,5 @@ @fallback_iip_specialize function SciMLBase.BVProblem{iip, spec}( - sys::System, op, tspan; + sys::System, op; tspan = nothing, check_compatibility = true, cse = true, checkbounds = false, eval_expression = false, eval_module = @__MODULE__, expression = Val{false}, guesses = Dict(), callback = nothing, @@ -7,6 +7,14 @@ check_complete(sys, BVProblem) check_compatibility && check_compatible_system(BVProblem, sys) isnothing(callback) || error("BVP solvers do not support callbacks.") + + # Use system's tspan as default if not provided + if tspan === nothing + tspan = get_tspan(sys) + if tspan === nothing + throw(ArgumentError("tspan must be provided either as an argument or defined in the system")) + end + end dvs = unknowns(sys) op = to_varmap(op, dvs) diff --git a/src/problems/ddeproblem.jl b/src/problems/ddeproblem.jl index fb4634f418..40a85a2afe 100644 --- a/src/problems/ddeproblem.jl +++ b/src/problems/ddeproblem.jl @@ -39,12 +39,20 @@ end @fallback_iip_specialize function SciMLBase.DDEProblem{iip, spec}( - sys::System, op, tspan; + sys::System, op; tspan = nothing, callback = nothing, check_length = true, cse = true, checkbounds = false, eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, u0_constructor = identity, expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, DDEProblem) check_compatibility && check_compatible_system(DDEProblem, sys) + + # Use system's tspan as default if not provided + if tspan === nothing + tspan = get_tspan(sys) + if tspan === nothing + throw(ArgumentError("tspan must be provided either as an argument or defined in the system")) + end + end f, u0, p = process_SciMLProblem(DDEFunction{iip, spec}, sys, op; diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index 68a38c95cf..461d8db6ab 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -70,12 +70,20 @@ end @fallback_iip_specialize function SciMLBase.ODEProblem{iip, spec}( - sys::System, op, tspan; + sys::System, op; tspan = nothing, callback = nothing, check_length = true, eval_expression = false, expression = Val{false}, eval_module = @__MODULE__, check_compatibility = true, kwargs...) where {iip, spec} check_complete(sys, ODEProblem) check_compatibility && check_compatible_system(ODEProblem, sys) + + # Use system's tspan as default if not provided + if tspan === nothing + tspan = get_tspan(sys) + if tspan === nothing + throw(ArgumentError("tspan must be provided either as an argument or defined in the system")) + end + end f, u0, p = process_SciMLProblem(ODEFunction{iip, spec}, sys, op; diff --git a/src/problems/sddeproblem.jl b/src/problems/sddeproblem.jl index f0e8e354aa..f82d5ad4a6 100644 --- a/src/problems/sddeproblem.jl +++ b/src/problems/sddeproblem.jl @@ -40,13 +40,21 @@ end @fallback_iip_specialize function SciMLBase.SDDEProblem{iip, spec}( - sys::System, op, tspan; + sys::System, op; tspan = nothing, callback = nothing, check_length = true, cse = true, checkbounds = false, eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, u0_constructor = identity, sparse = false, sparsenoise = sparse, expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, SDDEProblem) check_compatibility && check_compatible_system(SDDEProblem, sys) + + # Use system's tspan as default if not provided + if tspan === nothing + tspan = get_tspan(sys) + if tspan === nothing + throw(ArgumentError("tspan must be provided either as an argument or defined in the system")) + end + end f, u0, p = process_SciMLProblem(SDDEFunction{iip, spec}, sys, op; diff --git a/src/problems/sdeproblem.jl b/src/problems/sdeproblem.jl index e322775d2b..4b4227faba 100644 --- a/src/problems/sdeproblem.jl +++ b/src/problems/sdeproblem.jl @@ -65,12 +65,20 @@ end @fallback_iip_specialize function SciMLBase.SDEProblem{iip, spec}( - sys::System, op, tspan; + sys::System, op; tspan = nothing, callback = nothing, check_length = true, eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, sparse = false, sparsenoise = sparse, expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, SDEProblem) check_compatibility && check_compatible_system(SDEProblem, sys) + + # Use system's tspan as default if not provided + if tspan === nothing + tspan = get_tspan(sys) + if tspan === nothing + throw(ArgumentError("tspan must be provided either as an argument or defined in the system")) + end + end f, u0, p = process_SciMLProblem(SDEFunction{iip, spec}, sys, op; diff --git a/src/systems/system.jl b/src/systems/system.jl index 08421e04cc..a6d9c26552 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -190,6 +190,11 @@ struct System <: IntermediateDeprecationSystem """ tstops::Vector{Any} """ + The time span for time-dependent systems. This is a tuple of the start and end + time for the system, which can be symbolic expressions involving parameters. + """ + tspan::Union{Nothing, Tuple{Any, Any}} + """ The `TearingState` of the system post-simplification with `mtkcompile`. """ tearing_state::Any @@ -256,7 +261,7 @@ struct System <: IntermediateDeprecationSystem defaults, guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions = Dict{BasicSymbolic, String}(), metadata = MetadataT(), gui_metadata = nothing, - is_dde = false, tstops = [], tearing_state = nothing, namespacing = true, + is_dde = false, tstops = [], tspan = nothing, tearing_state = nothing, namespacing = true, complete = false, index_cache = nothing, ignored_connections = nothing, preface = nothing, parent = nothing, initializesystem = nothing, is_initializesystem = false, is_discrete = false, isscheduled = false, @@ -296,7 +301,7 @@ struct System <: IntermediateDeprecationSystem observed, parameter_dependencies, var_to_name, name, description, defaults, guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, - tstops, tearing_state, namespacing, complete, index_cache, ignored_connections, + tstops, tspan, tearing_state, namespacing, complete, index_cache, ignored_connections, preface, parent, initializesystem, is_initializesystem, is_discrete, isscheduled, schedule) end @@ -332,7 +337,7 @@ function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; continuous_events = SymbolicContinuousCallback[], discrete_events = SymbolicDiscreteCallback[], connector_type = nothing, assertions = Dict{BasicSymbolic, String}(), metadata = MetadataT(), gui_metadata = nothing, - is_dde = nothing, tstops = [], tearing_state = nothing, + is_dde = nothing, tstops = [], tspan = nothing, tearing_state = nothing, ignored_connections = nothing, parent = nothing, description = "", name = nothing, discover_from_metadata = true, initializesystem = nothing, is_initializesystem = false, is_discrete = false, @@ -417,7 +422,7 @@ function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; costs, consolidate, dvs, ps, brownians, iv, observed, Equation[], var_to_name, name, description, defaults, guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, - tstops, tearing_state, true, false, nothing, ignored_connections, preface, parent, + tstops, tspan, tearing_state, true, false, nothing, ignored_connections, preface, parent, initializesystem, is_initializesystem, is_discrete; checks) end diff --git a/test/odesystem.jl b/test/odesystem.jl index 9d1b2fc1e8..6c3b5a1f15 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1614,3 +1614,68 @@ end @test_nowarn push!(arr, sys) @test_nowarn TestWrapper(sys) end + +@testset "Symbolic tspan" begin + @variables x(t) y(t) + @parameters σ ρ β + + # Test creating a system with tspan + eqs = [D(x) ~ σ * (y - x), + D(y) ~ x * (ρ - x) - y] + + # Test with numeric tspan + @named sys1 = System(eqs, t, [x, y], [σ, ρ, β]; tspan = (0.0, 10.0)) + @test ModelingToolkit.get_tspan(sys1) == (0.0, 10.0) + @test ModelingToolkit.has_tspan(sys1) == true + + # Test with symbolic tspan + @parameters t0 tf + @named sys2 = System(eqs, t, [x, y], [σ, ρ, β]; tspan = (t0, tf)) + @test isequal(ModelingToolkit.get_tspan(sys2), (t0, tf)) + + # Test without tspan + @named sys3 = System(eqs, t, [x, y], [σ, ρ, β]) + @test ModelingToolkit.get_tspan(sys3) === nothing + @test ModelingToolkit.has_tspan(sys3) == true # Field exists but is nothing + + # Test that tspan is preserved through system completion + sys1_complete = complete(sys1) + @test ModelingToolkit.get_tspan(sys1_complete) == (0.0, 10.0) +end + +@testset "Default tspan usage in problem constructors" begin + @variables x(t) y(t) + @parameters σ ρ β + + eqs = [D(x) ~ σ * (y - x), + D(y) ~ x * (ρ - x) - y] + + # Test system with tspan + @named sys_with_tspan = System(eqs, t, [x, y], [σ, ρ, β]; tspan = (0.0, 10.0)) + sys_with_tspan = complete(sys_with_tspan) + + # Test system without tspan + @named sys_without_tspan = System(eqs, t, [x, y], [σ, ρ, β]) + sys_without_tspan = complete(sys_without_tspan) + + # Test ODEProblem uses system's tspan as default + u0 = [x => 1.0, y => 2.0] + p = [σ => 10.0, ρ => 28.0, β => 8/3] + + @test_nowarn prob1 = ODEProblem(sys_with_tspan, u0; p = p) + prob1 = ODEProblem(sys_with_tspan, u0; p = p) + @test prob1.tspan == (0.0, 10.0) + + # Test that explicitly provided tspan overrides system's tspan + @test_nowarn prob2 = ODEProblem(sys_with_tspan, u0; tspan = (0.0, 5.0), p = p) + prob2 = ODEProblem(sys_with_tspan, u0; tspan = (0.0, 5.0), p = p) + @test prob2.tspan == (0.0, 5.0) + + # Test that error is thrown when neither system nor argument provides tspan + @test_throws ArgumentError ODEProblem(sys_without_tspan, u0; p = p) + + # Test that providing tspan explicitly works for system without tspan + @test_nowarn prob3 = ODEProblem(sys_without_tspan, u0; tspan = (0.0, 8.0), p = p) + prob3 = ODEProblem(sys_without_tspan, u0; tspan = (0.0, 8.0), p = p) + @test prob3.tspan == (0.0, 8.0) +end