From 9733460668a662b41fd4cf87102d08521c1e4f92 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 22 Nov 2024 13:25:30 -0500 Subject: [PATCH 01/50] init --- src/systems/diffeqs/abstractodesystem.jl | 110 +++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 8d9e0b5381..2579ebafcf 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -466,6 +466,116 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, initializeprobpmap = initializeprobpmap) end +""" +```julia +SciMLBase.BVProblem{iip}(sys::AbstractODESystem, u0map, tspan, + parammap = DiffEqBase.NullParameters(); + version = nothing, tgrad = false, + jac = true, sparse = true, + simplify = false, + kwargs...) where {iip} +``` + +Create a `BVProblem` from the [`ODESystem`](@ref). The arguments `dvs` and +`ps` are used to set the order of the dependent variable and parameter vectors, +respectively. +""" +function SciMLBase.BVProblem(sys::AbstractODESystem, args...; kwargs...) + BVProblem{true}(sys, args...; kwargs...) +end + +function SciMLBase.BVProblem(sys::AbstractODESystem, + u0map::StaticArray, + args...; + kwargs...) + BVProblem{false, SciMLBase.FullSpecialize}(sys, u0map, args...; kwargs...) +end + +function SciMLBase.BVProblem{true}(sys::AbstractODESystem, args...; kwargs...) + BVProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) +end + +function SciMLBase.BVProblem{false}(sys::AbstractODESystem, args...; kwargs...) + BVProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) +end + +function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], + tspan = get_tspan(sys), + parammap = DiffEqBase.NullParameters(); + version = nothing, tgrad = false, + jac = true, sparse = true, + sparsity = true, + callback = nothing, + check_length = true, + warn_initialize_determined = true, + eval_expression = false, + eval_module = @__MODULE__, + kwargs...) where {iip, specialize} + + if !iscomplete(sys) + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `BVProblem`") + end + + f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, + check_length, warn_initialize_determined, eval_expression, eval_module, jac, sparse, sparsity, kwargs...) + + # if jac + # jac_gen = generate_jacobian(sys, dvs, ps; + # simplify = simplify, sparse = sparse, + # expression = Val{true}, + # expression_module = eval_module, + # checkbounds = checkbounds, kwargs...) + # jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) + + # _jac(u, p, t) = jac_oop(u, p, t) + # _jac(J, u, p, t) = jac_iip(J, u, p, t) + # _jac(u, p::Tuple{Vararg{Number}}, t) = jac_oop(u, p, t) + # _jac(J, u, p::Tuple{Vararg{Number}}, t) = jac_iip(J, u, p, t) + # _jac(u, p::Tuple, t) = jac_oop(u, p..., t) + # _jac(J, u, p::Tuple, t) = jac_iip(J, u, p..., t) + # _jac(u, p::MTKParameters, t) = jac_oop(u, p..., t) + # _jac(J, u, p::MTKParameters, t) = jac_iip(J, u, p..., t) + # else + # _jac = nothing + # end + + # jac_prototype = if sparse + # uElType = u0 === nothing ? Float64 : eltype(u0) + # if jac + # similar(calculate_jacobian(sys, sparse = sparse), uElType) + # else + # similar(jacobian_sparsity(sys), uElType) + # end + # else + # nothing + # end + + # f.jac = _jac + # f.jac_prototype = jac_prototype + # f.sparsity = jac ? jacobian_sparsity(sys) : nothing + + cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) + + kwargs = filter_kwargs(kwargs) + + kwargs1 = (;) + if cbs !== nothing + kwargs1 = merge(kwargs1, (callback = cbs,)) + end + + # Define the boundary conditions + bc = if iip + (residual, u, p, t) -> (residual = u[1] - u0) + else + (u, p, t) -> (u[1] - u0) + end + + return BVProblem{iip}(f, bc, u0, tspan, p; kwargs1..., kwargs...) +end + +get_callback(prob::BVProblem) = prob.kwargs[:callback] + """ ```julia DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), From b3da8137a9288b9c3eeff209a541dd41a5f97524 Mon Sep 17 00:00:00 2001 From: vyudu Date: Sun, 1 Dec 2024 16:18:18 -0500 Subject: [PATCH 02/50] up --- src/systems/diffeqs/abstractodesystem.jl | 64 +++++++++--------------- 1 file changed, 23 insertions(+), 41 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 7a8bf56562..bb4cbbb6c3 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -469,6 +469,17 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, initializeprobpmap = initializeprobpmap) end +""" +```julia +SciMLBase.BVPFunction{iip}(sys::AbstractODESystem, u0map, tspan, + parammap = DiffEqBase.NullParameters(); + version = nothing, tgrad = false, + jac = true, sparse = true, + simplify = false, + kwargs...) where {iip} +``` +""" + """ ```julia SciMLBase.BVProblem{iip}(sys::AbstractODESystem, u0map, tspan, @@ -481,7 +492,7 @@ SciMLBase.BVProblem{iip}(sys::AbstractODESystem, u0map, tspan, Create a `BVProblem` from the [`ODESystem`](@ref). The arguments `dvs` and `ps` are used to set the order of the dependent variable and parameter vectors, -respectively. +respectively. `u0` should be either the initial condition, a vector of values `u(t_i)` for collocation methods, or a function returning one or the other. """ function SciMLBase.BVProblem(sys::AbstractODESystem, args...; kwargs...) BVProblem{true}(sys, args...; kwargs...) @@ -502,12 +513,13 @@ function SciMLBase.BVProblem{false}(sys::AbstractODESystem, args...; kwargs...) BVProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) end +# figure out what's going on when we try to set `sparse`? + function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], tspan = get_tspan(sys), parammap = DiffEqBase.NullParameters(); version = nothing, tgrad = false, jac = true, sparse = true, - sparsity = true, callback = nothing, check_length = true, warn_initialize_determined = true, @@ -521,57 +533,27 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, - check_length, warn_initialize_determined, eval_expression, eval_module, jac, sparse, sparsity, kwargs...) - - # if jac - # jac_gen = generate_jacobian(sys, dvs, ps; - # simplify = simplify, sparse = sparse, - # expression = Val{true}, - # expression_module = eval_module, - # checkbounds = checkbounds, kwargs...) - # jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) - - # _jac(u, p, t) = jac_oop(u, p, t) - # _jac(J, u, p, t) = jac_iip(J, u, p, t) - # _jac(u, p::Tuple{Vararg{Number}}, t) = jac_oop(u, p, t) - # _jac(J, u, p::Tuple{Vararg{Number}}, t) = jac_iip(J, u, p, t) - # _jac(u, p::Tuple, t) = jac_oop(u, p..., t) - # _jac(J, u, p::Tuple, t) = jac_iip(J, u, p..., t) - # _jac(u, p::MTKParameters, t) = jac_oop(u, p..., t) - # _jac(J, u, p::MTKParameters, t) = jac_iip(J, u, p..., t) - # else - # _jac = nothing - # end - - # jac_prototype = if sparse - # uElType = u0 === nothing ? Float64 : eltype(u0) - # if jac - # similar(calculate_jacobian(sys, sparse = sparse), uElType) - # else - # similar(jacobian_sparsity(sys), uElType) - # end - # else - # nothing - # end - - # f.jac = _jac - # f.jac_prototype = jac_prototype - # f.sparsity = jac ? jacobian_sparsity(sys) : nothing + check_length, warn_initialize_determined, eval_expression, eval_module, jac, kwargs...) cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - kwargs = filter_kwargs(kwargs) kwargs1 = (;) if cbs !== nothing kwargs1 = merge(kwargs1, (callback = cbs,)) end + + # Construct initial conditions + _u0 = prepare_initial_state(u0) + __u0 = if _u0 isa Function + _u0(t_i) + end # Define the boundary conditions bc = if iip - (residual, u, p, t) -> (residual = u[1] - u0) + (residual, u, p, t) -> (residual = u[1] - __u0) else - (u, p, t) -> (u[1] - u0) + (u, p, t) -> (u[1] - __u0) end return BVProblem{iip}(f, bc, u0, tspan, p; kwargs1..., kwargs...) From 4affeac4b340a06c770373b498ec6b0e94c25a06 Mon Sep 17 00:00:00 2001 From: vyudu Date: Sun, 1 Dec 2024 17:35:05 -0500 Subject: [PATCH 03/50] up --- src/systems/diffeqs/abstractodesystem.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 263a75d153..33bddece30 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -545,9 +545,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] # Construct initial conditions _u0 = prepare_initial_state(u0) - __u0 = if _u0 isa Function - _u0(t_i) - end + __u0 = _u0 isa Function ? _u0(tspan[1]) : _u0 # Define the boundary conditions bc = if iip From a3429ea2b7d9e67898023eac1599e71c3d1b7bec Mon Sep 17 00:00:00 2001 From: vyudu Date: Sun, 1 Dec 2024 17:42:39 -0500 Subject: [PATCH 04/50] up --- src/systems/diffeqs/abstractodesystem.jl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 33bddece30..84f40a01f7 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -513,8 +513,6 @@ function SciMLBase.BVProblem{false}(sys::AbstractODESystem, args...; kwargs...) BVProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) end -# figure out what's going on when we try to set `sparse`? - function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], tspan = get_tspan(sys), parammap = DiffEqBase.NullParameters(); @@ -544,14 +542,13 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] end # Construct initial conditions - _u0 = prepare_initial_state(u0) - __u0 = _u0 isa Function ? _u0(tspan[1]) : _u0 + _u0 = u0 isa Function ? u0(tspan[1]) : u0 # Define the boundary conditions bc = if iip - (residual, u, p, t) -> (residual = u[1] - __u0) + (residual, u, p, t) -> (residual = u[1] - _u0) else - (u, p, t) -> (u[1] - __u0) + (u, p, t) -> (u[1] - _u0) end return BVProblem{iip}(f, bc, u0, tspan, p; kwargs1..., kwargs...) From f751fbb51c49e0b35ee84a12c54ec2de919660a5 Mon Sep 17 00:00:00 2001 From: vyudu Date: Sun, 1 Dec 2024 21:22:08 -0500 Subject: [PATCH 05/50] up --- src/systems/diffeqs/abstractodesystem.jl | 18 ++------ test/bvproblem.jl | 56 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 test/bvproblem.jl diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 84f40a01f7..112419dea8 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -469,17 +469,6 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, initializeprobpmap = initializeprobpmap) end -""" -```julia -SciMLBase.BVPFunction{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = true, sparse = true, - simplify = false, - kwargs...) where {iip} -``` -""" - """ ```julia SciMLBase.BVProblem{iip}(sys::AbstractODESystem, u0map, tspan, @@ -492,7 +481,7 @@ SciMLBase.BVProblem{iip}(sys::AbstractODESystem, u0map, tspan, Create a `BVProblem` from the [`ODESystem`](@ref). The arguments `dvs` and `ps` are used to set the order of the dependent variable and parameter vectors, -respectively. `u0` should be either the initial condition, a vector of values `u(t_i)` for collocation methods, or a function returning one or the other. +respectively. `u0map` should be used to specify the initial condition, or be a function returning an initial condition. """ function SciMLBase.BVProblem(sys::AbstractODESystem, args...; kwargs...) BVProblem{true}(sys, args...; kwargs...) @@ -517,7 +506,6 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] tspan = get_tspan(sys), parammap = DiffEqBase.NullParameters(); version = nothing, tgrad = false, - jac = true, sparse = true, callback = nothing, check_length = true, warn_initialize_determined = true, @@ -531,7 +519,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, - check_length, warn_initialize_determined, eval_expression, eval_module, jac, kwargs...) + check_length, warn_initialize_determined, eval_expression, eval_module, kwargs...) cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) kwargs = filter_kwargs(kwargs) @@ -546,7 +534,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] # Define the boundary conditions bc = if iip - (residual, u, p, t) -> (residual = u[1] - _u0) + (residual, u, p, t) -> residual .= u[1] - _u0 else (u, p, t) -> (u[1] - _u0) end diff --git a/test/bvproblem.jl b/test/bvproblem.jl new file mode 100644 index 0000000000..90d41a96ad --- /dev/null +++ b/test/bvproblem.jl @@ -0,0 +1,56 @@ +using BoundaryValueDiffEq, OrdinaryDiffEq +using ModelingToolkit +using ModelingToolkit: t_nounits as t, D_nounits as D + +@parameters σ = 10. ρ = 28 β = 8/3 +@variables x(t) = 1 y(t) = 0 z(t) = 0 + +eqs = [D(x) ~ σ*(y-x), + D(y) ~ x*(ρ-z)-y, + D(z) ~ x*y - β*z] + +u0map = [:x => 1., :y => 0., :z => 0.] +parammap = [:ρ => 28., :β => 8/3, :σ => 10.] +tspan = (0., 10.) + +@mtkbuild lorenz = ODESystem(eqs, t) + +bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lorenz, u0map, tspan, parammap) +sol = solve(bvp, MIRK4(), dt = 0.1); + +bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}(lorenz, u0map, tspan, parammap) +sol2 = solve(bvp, MIRK4(), dt = 0.1); + +op = ODEProblem(lorenz, u0map, tspan, parammap) +osol = solve(op) + +@test sol.u[end] ≈ osol.u[end] +@test sol2.u[end] ≈ osol.u[end] +@test sol.u[1] == [1., 0., 0.] +@test sol2.u[1] == [1., 0., 0.] + +### Testing on pendulum + +@parameters g = 9.81 L = 1. +@variables θ(t) = π/2 + +eqs = [D(D(θ)) ~ -(g / L) * sin(θ)] + +@mtkbuild pend = ODESystem(eqs, t) + +u0map = [θ => π/2, D(θ) => π/2] +parammap = [:L => 2.] +tspan = (0., 10.) + +bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) +sol = solve(bvp, MIRK4(), dt = 0.05); + +bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) +sol2 = solve(bvp2, MIRK4(), dt = 0.05); + +osol = solve(pend) + +@test sol.u[end] ≈ osol.u[end] +@test sol.u[1] == [π/2, π/2] +@test sol2.u[end] ≈ osol.u[end] +@test sol2.u[1] == [π/2, π/2] From a9fdfd6a3a114c4df28e4781c6b667990c01bafc Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 3 Dec 2024 05:04:45 -0500 Subject: [PATCH 06/50] up --- src/systems/diffeqs/abstractodesystem.jl | 6 ++-- test/bvproblem.jl | 44 ++++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 112419dea8..b795bb981d 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -529,12 +529,12 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] kwargs1 = merge(kwargs1, (callback = cbs,)) end - # Construct initial conditions + # Construct initial conditions. _u0 = u0 isa Function ? u0(tspan[1]) : u0 - # Define the boundary conditions + # Define the boundary conditions. bc = if iip - (residual, u, p, t) -> residual .= u[1] - _u0 + (residual, u, p, t) -> (residual .= u[1] - _u0) else (u, p, t) -> (u[1] - _u0) end diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 90d41a96ad..4865c867b3 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -2,32 +2,31 @@ using BoundaryValueDiffEq, OrdinaryDiffEq using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D -@parameters σ = 10. ρ = 28 β = 8/3 -@variables x(t) = 1 y(t) = 0 z(t) = 0 +@parameters α = 7.5 β = 4. γ = 8. δ = 5. +@variables x(t) = 1. y(t) = 2. -eqs = [D(x) ~ σ*(y-x), - D(y) ~ x*(ρ-z)-y, - D(z) ~ x*y - β*z] +eqs = [D(x) ~ α*x - β*x*y, + D(y) ~ -γ*y + δ*x*y] -u0map = [:x => 1., :y => 0., :z => 0.] -parammap = [:ρ => 28., :β => 8/3, :σ => 10.] +u0map = [:x => 1., :y => 2.] +parammap = [:α => 7.5, :β => 4, :γ => 8., :δ => 5.] tspan = (0., 10.) -@mtkbuild lorenz = ODESystem(eqs, t) +@mtkbuild lotkavolterra = ODESystem(eqs, t) -bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lorenz, u0map, tspan, parammap) -sol = solve(bvp, MIRK4(), dt = 0.1); +bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap) +sol = solve(bvp, MIRK4(), dt = 0.01); -bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}(lorenz, u0map, tspan, parammap) -sol2 = solve(bvp, MIRK4(), dt = 0.1); +bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap) +sol2 = solve(bvp, MIRK4(), dt = 0.01); -op = ODEProblem(lorenz, u0map, tspan, parammap) -osol = solve(op) +op = ODEProblem(lotkavolterra, u0map, tspan, parammap) +osol = solve(op, Vern9()) -@test sol.u[end] ≈ osol.u[end] -@test sol2.u[end] ≈ osol.u[end] -@test sol.u[1] == [1., 0., 0.] -@test sol2.u[1] == [1., 0., 0.] +@test isapprox(sol.u[end],osol.u[end]; atol = 0.001) +@test isapprox(sol2.u[end],osol.u[end]; atol = 0.001) +@test sol.u[1] == [1., 2.] +@test sol2.u[1] == [1., 2.] ### Testing on pendulum @@ -39,16 +38,17 @@ eqs = [D(D(θ)) ~ -(g / L) * sin(θ)] @mtkbuild pend = ODESystem(eqs, t) u0map = [θ => π/2, D(θ) => π/2] -parammap = [:L => 2.] +parammap = [:L => 2., :g => 9.81] tspan = (0., 10.) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) -sol = solve(bvp, MIRK4(), dt = 0.05); +sol = solve(bvp, MIRK4(), dt = 0.01); bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) -sol2 = solve(bvp2, MIRK4(), dt = 0.05); +sol2 = solve(bvp2, MIRK4(), dt = 0.01); -osol = solve(pend) +op = ODEProblem(pend, u0map, tspan, parammap) +osol = solve(op, Vern9()) @test sol.u[end] ≈ osol.u[end] @test sol.u[1] == [π/2, π/2] From a9f210691c1f38ef0f575eb89a939763b81c9c1b Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 3 Dec 2024 19:11:35 -0500 Subject: [PATCH 07/50] up --- src/systems/diffeqs/abstractodesystem.jl | 4 ++-- test/bvproblem.jl | 8 ++++---- test/runtests.jl | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index b795bb981d..8dac19f296 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -534,7 +534,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] # Define the boundary conditions. bc = if iip - (residual, u, p, t) -> (residual .= u[1] - _u0) + (residual, u, p, t) -> (residual .= u[1] .- _u0) else (u, p, t) -> (u[1] - _u0) end @@ -542,7 +542,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] return BVProblem{iip}(f, bc, u0, tspan, p; kwargs1..., kwargs...) end -get_callback(prob::BVProblem) = prob.kwargs[:callback] +get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") """ ```julia diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 4865c867b3..7235638cf0 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -23,8 +23,8 @@ sol2 = solve(bvp, MIRK4(), dt = 0.01); op = ODEProblem(lotkavolterra, u0map, tspan, parammap) osol = solve(op, Vern9()) -@test isapprox(sol.u[end],osol.u[end]; atol = 0.001) -@test isapprox(sol2.u[end],osol.u[end]; atol = 0.001) +@test isapprox(sol.u[end],osol.u[end]; atol = 0.01) +@test isapprox(sol2.u[end],osol.u[end]; atol = 0.01) @test sol.u[1] == [1., 2.] @test sol2.u[1] == [1., 2.] @@ -50,7 +50,7 @@ sol2 = solve(bvp2, MIRK4(), dt = 0.01); op = ODEProblem(pend, u0map, tspan, parammap) osol = solve(op, Vern9()) -@test sol.u[end] ≈ osol.u[end] +@test isapprox(sol.u[end], osol.u[end]; atol = 0.01) @test sol.u[1] == [π/2, π/2] -@test sol2.u[end] ≈ osol.u[end] +@test isapprox(sol2.u[end], osol.u[end]; atol = 0.01) @test sol2.u[1] == [π/2, π/2] diff --git a/test/runtests.jl b/test/runtests.jl index 44846eed57..eaa87e6407 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -80,6 +80,7 @@ end @safetestset "NonlinearSystem Test" include("nonlinearsystem.jl") @safetestset "PDE Construction Test" include("pde.jl") @safetestset "JumpSystem Test" include("jumpsystem.jl") + @safetestset "BVProblem Test" include("bvproblem.jl") @safetestset "print_tree" include("print_tree.jl") @safetestset "Constraints Test" include("constraints.jl") end From 18fdd5f79702627e30ce0acc971677a5cd1d303f Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 13 Dec 2024 10:04:59 +0900 Subject: [PATCH 08/50] up --- Project.toml | 6 ++++-- test/bvproblem.jl | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 093c632c42..19063a0342 100644 --- a/Project.toml +++ b/Project.toml @@ -72,14 +72,15 @@ MTKBifurcationKitExt = "BifurcationKit" MTKChainRulesCoreExt = "ChainRulesCore" MTKDeepDiffsExt = "DeepDiffs" MTKHomotopyContinuationExt = "HomotopyContinuation" -MTKLabelledArraysExt = "LabelledArrays" MTKInfiniteOptExt = "InfiniteOpt" +MTKLabelledArraysExt = "LabelledArrays" [compat] AbstractTrees = "0.3, 0.4" ArrayInterface = "6, 7" BifurcationKit = "0.4" BlockArrays = "1.1" +BoundaryValueDiffEq = "5.12.0" ChainRulesCore = "1" Combinatorics = "1" CommonSolve = "0.2.4" @@ -145,6 +146,7 @@ julia = "1.9" [extras] AmplNLWriter = "7c4d4715-977e-5154-bfe0-e096adeac482" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +BoundaryValueDiffEq = "764a87c0-6b3e-53db-9096-fe964310641d" ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e" DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0" DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6" @@ -174,4 +176,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET"] +test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEq", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET"] diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 7235638cf0..7787bd9c3e 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -44,7 +44,7 @@ tspan = (0., 10.) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) sol = solve(bvp, MIRK4(), dt = 0.01); -bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) +bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) sol2 = solve(bvp2, MIRK4(), dt = 0.01); op = ODEProblem(pend, u0map, tspan, parammap) From 9d65a3345ade530654a64ace2069ff24e8c67875 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 16 Dec 2024 23:43:35 +0800 Subject: [PATCH 09/50] fixing create_array --- Project.toml | 3 ++ src/systems/diffeqs/abstractodesystem.jl | 10 ++++- test/bvproblem.jl | 53 +++++++++++++++--------- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/Project.toml b/Project.toml index 19063a0342..ab854b80bd 100644 --- a/Project.toml +++ b/Project.toml @@ -7,6 +7,9 @@ version = "9.54.0" AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" +BoundaryValueDiffEq = "764a87c0-6b3e-53db-9096-fe964310641d" +BoundaryValueDiffEqCore = "56b672f2-a5fe-4263-ab2d-da677488eb3a" +BoundaryValueDiffEqMIRK = "1a22d4ce-7765-49ea-b6f2-13c8438986a6" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 8dac19f296..3a502d3183 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -539,11 +539,19 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] (u, p, t) -> (u[1] - _u0) end - return BVProblem{iip}(f, bc, u0, tspan, p; kwargs1..., kwargs...) + return BVProblem{iip}(f, bc, _u0, tspan, p; kwargs1..., kwargs...) end get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") +@inline function create_array(::Type{Base.ReinterpretArray}, ::Nothing, ::Val{1}, ::Val{dims}, elems...) where dims + [elems...] +end + +@inline function create_array(::Type{Base.ReinterpretArray}, T, ::Val{1}, ::Val{dims}, elems...) where dims + T[elems...] +end + """ ```julia DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 7787bd9c3e..7cdb7e1837 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -2,6 +2,8 @@ using BoundaryValueDiffEq, OrdinaryDiffEq using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D +solvers = [MIRK4, RadauIIa5, LobattoIIIa3] + @parameters α = 7.5 β = 4. γ = 8. δ = 5. @variables x(t) = 1. y(t) = 2. @@ -13,20 +15,26 @@ parammap = [:α => 7.5, :β => 4, :γ => 8., :δ => 5.] tspan = (0., 10.) @mtkbuild lotkavolterra = ODESystem(eqs, t) +op = ODEProblem(lotkavolterra, u0map, tspan, parammap) +osol = solve(op, Vern9()) -bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap) -sol = solve(bvp, MIRK4(), dt = 0.01); +bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; eval_expression = true) -bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap) -sol2 = solve(bvp, MIRK4(), dt = 0.01); +for solver in solvers + println("$solver") + sol = solve(bvp, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + @test sol.u[1] == [1., 2.] +end -op = ODEProblem(lotkavolterra, u0map, tspan, parammap) -osol = solve(op, Vern9()) +# Test out of place +bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; eval_expression = true) -@test isapprox(sol.u[end],osol.u[end]; atol = 0.01) -@test isapprox(sol2.u[end],osol.u[end]; atol = 0.01) -@test sol.u[1] == [1., 2.] -@test sol2.u[1] == [1., 2.] +for solver in solvers + sol = solve(bvp2, solver(), dt = 0.01) + @test isapprox(sol.u[end],osol.u[end]; atol = 0.01) + @test sol.u[1] == [1., 2.] +end ### Testing on pendulum @@ -38,19 +46,24 @@ eqs = [D(D(θ)) ~ -(g / L) * sin(θ)] @mtkbuild pend = ODESystem(eqs, t) u0map = [θ => π/2, D(θ) => π/2] -parammap = [:L => 2., :g => 9.81] +parammap = [:L => 1., :g => 9.81] tspan = (0., 10.) +op = ODEProblem(pend, u0map, tspan, parammap) +osol = solve(op, Vern9()) + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) -sol = solve(bvp, MIRK4(), dt = 0.01); +for solver in solvers + sol = solve(bvp2, solver(), dt = 0.01) + @test isapprox(sol.u[end],osol.u[end]; atol = 0.01) + @test sol.u[1] == [π/2, π/2] +end +# Test out-of-place bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) -sol2 = solve(bvp2, MIRK4(), dt = 0.01); - -op = ODEProblem(pend, u0map, tspan, parammap) -osol = solve(op, Vern9()) -@test isapprox(sol.u[end], osol.u[end]; atol = 0.01) -@test sol.u[1] == [π/2, π/2] -@test isapprox(sol2.u[end], osol.u[end]; atol = 0.01) -@test sol2.u[1] == [π/2, π/2] +for solver in solvers + sol = solve(bvp2, solver(), dt = 0.01) + @test isapprox(sol.u[end],osol.u[end]; atol = 0.01) + @test sol.u[1] == [π/2, π/2] +end From 999ec308d58e32c51941743a6ba5a8f096c56ac2 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 16 Dec 2024 23:44:24 +0800 Subject: [PATCH 10/50] revert Project.toml --- Project.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Project.toml b/Project.toml index ab854b80bd..19063a0342 100644 --- a/Project.toml +++ b/Project.toml @@ -7,9 +7,6 @@ version = "9.54.0" AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" -BoundaryValueDiffEq = "764a87c0-6b3e-53db-9096-fe964310641d" -BoundaryValueDiffEqCore = "56b672f2-a5fe-4263-ab2d-da677488eb3a" -BoundaryValueDiffEqMIRK = "1a22d4ce-7765-49ea-b6f2-13c8438986a6" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" From 9226ad687ff06cbc1dbbdcaa16ba3df85ef32d4e Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 16 Dec 2024 23:56:04 +0800 Subject: [PATCH 11/50] Up --- test/bvproblem.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 7cdb7e1837..86e3722eec 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -21,7 +21,6 @@ osol = solve(op, Vern9()) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; eval_expression = true) for solver in solvers - println("$solver") sol = solve(bvp, solver(), dt = 0.01) @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) @test sol.u[1] == [1., 2.] @@ -47,15 +46,15 @@ eqs = [D(D(θ)) ~ -(g / L) * sin(θ)] u0map = [θ => π/2, D(θ) => π/2] parammap = [:L => 1., :g => 9.81] -tspan = (0., 10.) +tspan = (0., 6.) op = ODEProblem(pend, u0map, tspan, parammap) osol = solve(op, Vern9()) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) for solver in solvers - sol = solve(bvp2, solver(), dt = 0.01) - @test isapprox(sol.u[end],osol.u[end]; atol = 0.01) + sol = solve(bvp, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) @test sol.u[1] == [π/2, π/2] end From 67d8164c591b74a9b985ffbcaf1ef248fb0efaaa Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 17 Dec 2024 00:09:52 +0800 Subject: [PATCH 12/50] formatting --- src/systems/diffeqs/abstractodesystem.jl | 11 ++++--- test/bvproblem.jl | 42 +++++++++++++----------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 87ee6e823d..06c83073bf 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -512,7 +512,6 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] eval_expression = false, eval_module = @__MODULE__, kwargs...) where {iip, specialize} - if !iscomplete(sys) error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `BVProblem`") end @@ -528,12 +527,12 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] if cbs !== nothing kwargs1 = merge(kwargs1, (callback = cbs,)) end - + # Construct initial conditions. _u0 = u0 isa Function ? u0(tspan[1]) : u0 # Define the boundary conditions. - bc = if iip + bc = if iip (residual, u, p, t) -> (residual .= u[1] .- _u0) else (u, p, t) -> (u[1] - _u0) @@ -544,11 +543,13 @@ end get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") -@inline function create_array(::Type{Base.ReinterpretArray}, ::Nothing, ::Val{1}, ::Val{dims}, elems...) where dims +@inline function create_array(::Type{Base.ReinterpretArray}, ::Nothing, + ::Val{1}, ::Val{dims}, elems...) where {dims} [elems...] end -@inline function create_array(::Type{Base.ReinterpretArray}, T, ::Val{1}, ::Val{dims}, elems...) where dims +@inline function create_array( + ::Type{Base.ReinterpretArray}, T, ::Val{1}, ::Val{dims}, elems...) where {dims} T[elems...] end diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 86e3722eec..1072874917 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -4,49 +4,51 @@ using ModelingToolkit: t_nounits as t, D_nounits as D solvers = [MIRK4, RadauIIa5, LobattoIIIa3] -@parameters α = 7.5 β = 4. γ = 8. δ = 5. -@variables x(t) = 1. y(t) = 2. +@parameters α=7.5 β=4.0 γ=8.0 δ=5.0 +@variables x(t)=1.0 y(t)=2.0 -eqs = [D(x) ~ α*x - β*x*y, - D(y) ~ -γ*y + δ*x*y] +eqs = [D(x) ~ α * x - β * x * y, + D(y) ~ -γ * y + δ * x * y] -u0map = [:x => 1., :y => 2.] -parammap = [:α => 7.5, :β => 4, :γ => 8., :δ => 5.] -tspan = (0., 10.) +u0map = [:x => 1.0, :y => 2.0] +parammap = [:α => 7.5, :β => 4, :γ => 8.0, :δ => 5.0] +tspan = (0.0, 10.0) @mtkbuild lotkavolterra = ODESystem(eqs, t) op = ODEProblem(lotkavolterra, u0map, tspan, parammap) osol = solve(op, Vern9()) -bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; eval_expression = true) +bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( + lotkavolterra, u0map, tspan, parammap; eval_expression = true) for solver in solvers sol = solve(bvp, solver(), dt = 0.01) @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - @test sol.u[1] == [1., 2.] + @test sol.u[1] == [1.0, 2.0] end # Test out of place -bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; eval_expression = true) +bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}( + lotkavolterra, u0map, tspan, parammap; eval_expression = true) for solver in solvers sol = solve(bvp2, solver(), dt = 0.01) - @test isapprox(sol.u[end],osol.u[end]; atol = 0.01) - @test sol.u[1] == [1., 2.] + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + @test sol.u[1] == [1.0, 2.0] end ### Testing on pendulum -@parameters g = 9.81 L = 1. -@variables θ(t) = π/2 +@parameters g=9.81 L=1.0 +@variables θ(t) = π / 2 eqs = [D(D(θ)) ~ -(g / L) * sin(θ)] @mtkbuild pend = ODESystem(eqs, t) -u0map = [θ => π/2, D(θ) => π/2] -parammap = [:L => 1., :g => 9.81] -tspan = (0., 6.) +u0map = [θ => π / 2, D(θ) => π / 2] +parammap = [:L => 1.0, :g => 9.81] +tspan = (0.0, 6.0) op = ODEProblem(pend, u0map, tspan, parammap) osol = solve(op, Vern9()) @@ -55,7 +57,7 @@ bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, pa for solver in solvers sol = solve(bvp, solver(), dt = 0.01) @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - @test sol.u[1] == [π/2, π/2] + @test sol.u[1] == [π / 2, π / 2] end # Test out-of-place @@ -63,6 +65,6 @@ bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, for solver in solvers sol = solve(bvp2, solver(), dt = 0.01) - @test isapprox(sol.u[end],osol.u[end]; atol = 0.01) - @test sol.u[1] == [π/2, π/2] + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + @test sol.u[1] == [π / 2, π / 2] end From 25988f3bc6b6d66b008b6a40341f75e4547e574a Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 17 Dec 2024 10:52:37 +0800 Subject: [PATCH 13/50] up --- src/systems/diffeqs/abstractodesystem.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 06c83073bf..c84f5ff5be 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -543,13 +543,13 @@ end get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") -@inline function create_array(::Type{Base.ReinterpretArray}, ::Nothing, +@inline function SciML.Code.create_array(::Type{<:Base.ReinterpretArray}, ::Nothing, ::Val{1}, ::Val{dims}, elems...) where {dims} [elems...] end -@inline function create_array( - ::Type{Base.ReinterpretArray}, T, ::Val{1}, ::Val{dims}, elems...) where {dims} +@inline function SciML.Code.create_array( + ::Type{<:Base.ReinterpretArray}, T, ::Val{1}, ::Val{dims}, elems...) where {dims} T[elems...] end From bb28d4fe2a0d753221104186fe42808c7657f5d6 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 17 Dec 2024 10:53:22 +0800 Subject: [PATCH 14/50] up --- src/systems/diffeqs/abstractodesystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index c84f5ff5be..eac2df16aa 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -543,12 +543,12 @@ end get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") -@inline function SciML.Code.create_array(::Type{<:Base.ReinterpretArray}, ::Nothing, +@inline function SciMLBase.Code.create_array(::Type{<:Base.ReinterpretArray}, ::Nothing, ::Val{1}, ::Val{dims}, elems...) where {dims} [elems...] end -@inline function SciML.Code.create_array( +@inline function SciMLBase.Code.create_array( ::Type{<:Base.ReinterpretArray}, T, ::Val{1}, ::Val{dims}, elems...) where {dims} T[elems...] end From b2bf7c05532bdf68d93e566d07c5e7444473dd7a Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 17 Dec 2024 11:00:30 +0800 Subject: [PATCH 15/50] fix --- src/systems/diffeqs/abstractodesystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index eac2df16aa..3d143d49fb 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -543,12 +543,12 @@ end get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") -@inline function SciMLBase.Code.create_array(::Type{<:Base.ReinterpretArray}, ::Nothing, +@inline function SymbolicUtils.Code.create_array(::Type{<:Base.ReinterpretArray}, ::Nothing, ::Val{1}, ::Val{dims}, elems...) where {dims} [elems...] end -@inline function SciMLBase.Code.create_array( +@inline function SymbolicUtils.Code.create_array( ::Type{<:Base.ReinterpretArray}, T, ::Val{1}, ::Val{dims}, elems...) where {dims} T[elems...] end From 3751c2a92c282593ef52b67b727c7db635ea677e Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 20 Dec 2024 23:58:28 +0900 Subject: [PATCH 16/50] up --- src/systems/diffeqs/abstractodesystem.jl | 165 +++++++++++------------ test/bvproblem.jl | 4 +- 2 files changed, 83 insertions(+), 86 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 3d143d49fb..d8ff71c324 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -469,90 +469,6 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, initializeprobpmap = initializeprobpmap) end -""" -```julia -SciMLBase.BVProblem{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = true, sparse = true, - simplify = false, - kwargs...) where {iip} -``` - -Create a `BVProblem` from the [`ODESystem`](@ref). The arguments `dvs` and -`ps` are used to set the order of the dependent variable and parameter vectors, -respectively. `u0map` should be used to specify the initial condition, or be a function returning an initial condition. -""" -function SciMLBase.BVProblem(sys::AbstractODESystem, args...; kwargs...) - BVProblem{true}(sys, args...; kwargs...) -end - -function SciMLBase.BVProblem(sys::AbstractODESystem, - u0map::StaticArray, - args...; - kwargs...) - BVProblem{false, SciMLBase.FullSpecialize}(sys, u0map, args...; kwargs...) -end - -function SciMLBase.BVProblem{true}(sys::AbstractODESystem, args...; kwargs...) - BVProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function SciMLBase.BVProblem{false}(sys::AbstractODESystem, args...; kwargs...) - BVProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], - tspan = get_tspan(sys), - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - callback = nothing, - check_length = true, - warn_initialize_determined = true, - eval_expression = false, - eval_module = @__MODULE__, - kwargs...) where {iip, specialize} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `BVProblem`") - end - - f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, - check_length, warn_initialize_determined, eval_expression, eval_module, kwargs...) - - cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - kwargs = filter_kwargs(kwargs) - - kwargs1 = (;) - if cbs !== nothing - kwargs1 = merge(kwargs1, (callback = cbs,)) - end - - # Construct initial conditions. - _u0 = u0 isa Function ? u0(tspan[1]) : u0 - - # Define the boundary conditions. - bc = if iip - (residual, u, p, t) -> (residual .= u[1] .- _u0) - else - (u, p, t) -> (u[1] - _u0) - end - - return BVProblem{iip}(f, bc, _u0, tspan, p; kwargs1..., kwargs...) -end - -get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") - -@inline function SymbolicUtils.Code.create_array(::Type{<:Base.ReinterpretArray}, ::Nothing, - ::Val{1}, ::Val{dims}, elems...) where {dims} - [elems...] -end - -@inline function SymbolicUtils.Code.create_array( - ::Type{<:Base.ReinterpretArray}, T, ::Val{1}, ::Val{dims}, elems...) where {dims} - T[elems...] -end - """ ```julia DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), @@ -943,6 +859,87 @@ function DiffEqBase.ODEProblem{iip, specialize}(sys::AbstractODESystem, u0map = end get_callback(prob::ODEProblem) = prob.kwargs[:callback] +""" +```julia +SciMLBase.BVProblem{iip}(sys::AbstractODESystem, u0map, tspan, + parammap = DiffEqBase.NullParameters(); + version = nothing, tgrad = false, + jac = true, sparse = true, + simplify = false, + kwargs...) where {iip} +``` + +Create a `BVProblem` from the [`ODESystem`](@ref). The arguments `dvs` and +`ps` are used to set the order of the dependent variable and parameter vectors, +respectively. `u0map` should be used to specify the initial condition. +""" +function SciMLBase.BVProblem(sys::AbstractODESystem, args...; kwargs...) + BVProblem{true}(sys, args...; kwargs...) +end + +function SciMLBase.BVProblem(sys::AbstractODESystem, + u0map::StaticArray, + args...; + kwargs...) + BVProblem{false, SciMLBase.FullSpecialize}(sys, u0map, args...; kwargs...) +end + +function SciMLBase.BVProblem{true}(sys::AbstractODESystem, args...; kwargs...) + BVProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) +end + +function SciMLBase.BVProblem{false}(sys::AbstractODESystem, args...; kwargs...) + BVProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) +end + +function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], + tspan = get_tspan(sys), + parammap = DiffEqBase.NullParameters(); + version = nothing, tgrad = false, + callback = nothing, + check_length = true, + warn_initialize_determined = true, + eval_expression = false, + eval_module = @__MODULE__, + kwargs...) where {iip, specialize} + if !iscomplete(sys) + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `BVProblem`") + end + + f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, + check_length, warn_initialize_determined, eval_expression, eval_module, kwargs...) + + cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) + kwargs = filter_kwargs(kwargs) + + kwargs1 = (;) + if cbs !== nothing + kwargs1 = merge(kwargs1, (callback = cbs,)) + end + + # Define the boundary conditions. + bc = if iip + (residual, u, p, t) -> (residual .= u[1] .- u0) + else + (u, p, t) -> (u[1] - u0) + end + + return BVProblem{iip}(f, bc, u0, tspan, p; kwargs1..., kwargs...) +end + +get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") + +@inline function SymbolicUtils.Code.create_array(::Type{<:Base.ReinterpretArray}, ::Nothing, + ::Val{1}, ::Val{dims}, elems...) where {dims} + [elems...] +end + +@inline function SymbolicUtils.Code.create_array( + ::Type{<:Base.ReinterpretArray}, T, ::Val{1}, ::Val{dims}, elems...) where {dims} + T[elems...] +end + """ ```julia DiffEqBase.DAEProblem{iip}(sys::AbstractODESystem, du0map, u0map, tspan, diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 1072874917..c5a302147d 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -10,8 +10,8 @@ solvers = [MIRK4, RadauIIa5, LobattoIIIa3] eqs = [D(x) ~ α * x - β * x * y, D(y) ~ -γ * y + δ * x * y] -u0map = [:x => 1.0, :y => 2.0] -parammap = [:α => 7.5, :β => 4, :γ => 8.0, :δ => 5.0] +u0map = [x => 1.0, y => 2.0] +parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] tspan = (0.0, 10.0) @mtkbuild lotkavolterra = ODESystem(eqs, t) From ef1f089cbd493272e2b9297c2189aa2a6029d72f Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 8 Jan 2025 15:12:12 -0500 Subject: [PATCH 17/50] up --- src/systems/diffeqs/abstractodesystem.jl | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index d8ff71c324..87ab83f10b 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -930,16 +930,6 @@ end get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") -@inline function SymbolicUtils.Code.create_array(::Type{<:Base.ReinterpretArray}, ::Nothing, - ::Val{1}, ::Val{dims}, elems...) where {dims} - [elems...] -end - -@inline function SymbolicUtils.Code.create_array( - ::Type{<:Base.ReinterpretArray}, T, ::Val{1}, ::Val{dims}, elems...) where {dims} - T[elems...] -end - """ ```julia DiffEqBase.DAEProblem{iip}(sys::AbstractODESystem, du0map, u0map, tspan, From 2a25200bcb6a1ad57c4329b6383ba844eb73c2f7 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 9 Jan 2025 17:36:38 -0500 Subject: [PATCH 18/50] extend BVProblem for constraint equations --- src/systems/diffeqs/abstractodesystem.jl | 74 +++++++++++++++++-- test/bvproblem.jl | 90 ++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 4 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index f5c1c288d7..74b1bf7596 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -873,7 +873,7 @@ function SciMLBase.BVProblem(sys::AbstractODESystem, kwargs...) BVProblem{false, SciMLBase.FullSpecialize}(sys, u0map, args...; kwargs...) end - +o function SciMLBase.BVProblem{true}(sys::AbstractODESystem, args...; kwargs...) BVProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) end @@ -908,11 +908,32 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] kwargs1 = merge(kwargs1, (callback = cbs,)) end + # Handle algebraic equations + stidxmap = Dict([v => i for (i, v) in enumerate(unknowns(sys))]) + pidxmap = Dict([v => i for (i, v) in enumerate(parameters(sys))]) + ns = length(stmap) + ne = length(get_alg_eqs(sys)) + # Define the boundary conditions. - bc = if iip - (residual, u, p, t) -> (residual .= u[1] .- u0) + bc = if has_alg_eqs(sys) + if iip + (residual,u,p,t) -> begin + residual[1:ns] .= u[1] .- u0 + residual[ns+1:ns+ne] .= sub_u_p_into_symeq.(get_alg_eqs(sys)) + end + else + (u,p,t) -> begin + resid = vcat(u[1] - u0, sub_u_p_into_symeq.(get_alg_eqs(sys))) + end + end else - (u, p, t) -> (u[1] - u0) + if iip + (residual,u,p,t) -> begin + residual .= u[1] .- u0 + end + else + (u,p,t) -> (u[1] - u0) + end end return BVProblem{iip}(f, bc, u0, tspan, p; kwargs1..., kwargs...) @@ -920,6 +941,51 @@ end get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") +# Helper to create the dictionary that will substitute numeric values for u, p into the algebraic equations in the ODESystem. Used to construct the boundary condition function. +# Take a system with variables x,y, parameters g +# +# 1 + x + y → 1 + u[1][1] + u[1][2] +# x(0.5) → u(0.5)[1] +# x(0.5)*g(0.5) → u(0.5)[1]*p[1] + +function sub_u_p_into_symeq(eq, u, p, stidxmap, pidxmap) + iv = ModelingToolkit.get_iv(sys) + eq = Symbolics.unwrap(eq) + + stmap = Dict([st => u[1][i] for st => i in stidxmap]) + pmap = Dict([pa => p[i] for pa => i in pidxmap]) + eq = Symbolics.substitute(eq, merge(stmap, pmap)) + + csyms = [] + # Find most nested calls, substitute those first. + while !isempty(find_callable_syms!(csyms, eq)) + for sym in csyms + t = arguments(sym)[1] + x = operation(sym) + + if isparameter(x) + eq = Symbolics.substitute(eq, Dict(x(t) => p[pidxmap[x(iv)]])) + elseif isvariable(x) + eq = Symbolics.substitute(eq, Dict(x(t) => u(val)[stidxmap[x(iv)]])) + end + end + empty!(csyms) + end + eq +end + +function find_callable_syms!(csyms, ex) + ex = Symbolics.unwrap(ex) + + if iscall(ex) + operation(ex) isa Symbolic && (arguments(ex)[1] isa Symbolic) && push!(csyms, ex) # only add leaf nodes + for arg in arguments(ex) + find_callable_syms!(csyms, arg) + end + end + csyms +end + """ ```julia DiffEqBase.DAEProblem{iip}(sys::AbstractODESystem, du0map, u0map, tspan, diff --git a/test/bvproblem.jl b/test/bvproblem.jl index c5a302147d..2d5535325a 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -2,6 +2,7 @@ using BoundaryValueDiffEq, OrdinaryDiffEq using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D +### Test Collocation solvers on simple problems solvers = [MIRK4, RadauIIa5, LobattoIIIa3] @parameters α=7.5 β=4.0 γ=8.0 δ=5.0 @@ -68,3 +69,92 @@ for solver in solvers @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) @test sol.u[1] == [π / 2, π / 2] end + +################################################### +### TESTING ODESystem with Constraint Equations ### +################################################### + +# Cartesian pendulum from the docs. Testing that initialization is satisfied. +let + @parameters g + @variables x(t) y(t) [state_priority = 10] λ(t) + eqs = [D(D(x)) ~ λ * x + D(D(y)) ~ λ * y - g + x^2 + y^2 ~ 1] + @mtkbuild pend = ODESystem(eqs, t) + + tspan = (0.0, 1.5) + u0map = [x => 1, y => 0] + parammap = [g => 1] + guesses = [λ => 1] + + prob = ODEProblem(pend, u0map, tspan, pmap; guesses) + sol = solve(prob, Rodas5P()) + + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses) + + for solver in solvers + sol = solve(bvp, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + conditions = getfield.(equations(pend)[3:end], :rhs) + @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 + end + + bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) + for solver in solvers + sol = solve(bvp, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + conditions = getfield.(equations(pend)[3:end], :rhs) + @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 + end +end + +# Adding a midpoint boundary condition. +let + @parameters g + @variables x(..) y(t) [state_priority = 10] λ(t) + eqs = [D(D(x(t))) ~ λ * x(t) + D(D(y)) ~ λ * y - g + x(t)^2 + y^2 ~ 1 + x(0.5) ~ 1] + @mtkbuild pend = ODESystem(eqs, t) + + tspan = (0.0, 1.5) + u0map = [x(t) => 0.6, y => 0.8] + parammap = [g => 1] + guesses = [λ => 1] + + prob = ODEProblem(pend, u0map, tspan, pmap; guesses, check_length = false) + sol = solve(prob, Rodas5P()) + + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses = guesses, check_length = false) + + for solver in solvers + sol = solve(bvp, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + conditions = getfield.(equations(pend)[3:end], :rhs) + @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 + @test sol.u[1] == [π / 2, π / 2] + end + + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses) + + for solver in solvers + sol = solve(bvp, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + conditions = getfield.(equations(pend)[3:end], :rhs) + @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 + end + + bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) + for solver in solvers + sol = solve(bvp, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + conditions = getfield.(equations(pend)[3:end], :rhs) + @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 + end +end + +# Testing a more complicated case with multiple constraints. +let +end From 50504abbcf0149d50bd6e858ae1c5f368f8d2835 Mon Sep 17 00:00:00 2001 From: vyudu Date: Sat, 11 Jan 2025 13:57:59 -0500 Subject: [PATCH 19/50] adding tests --- Project.toml | 6 +- src/systems/diffeqs/abstractodesystem.jl | 140 ++++++++++--- test/bvproblem.jl | 242 ++++++++++++++--------- 3 files changed, 261 insertions(+), 127 deletions(-) diff --git a/Project.toml b/Project.toml index 98fd119f0c..b0bd4381b6 100644 --- a/Project.toml +++ b/Project.toml @@ -82,6 +82,7 @@ ArrayInterface = "6, 7" BifurcationKit = "0.4" BlockArrays = "1.1" BoundaryValueDiffEq = "5.12.0" +BoundaryValueDiffEqAscher = "1.1.0" ChainRulesCore = "1" Combinatorics = "1" CommonSolve = "0.2.4" @@ -140,8 +141,8 @@ SimpleNonlinearSolve = "0.1.0, 1, 2" SparseArrays = "1" SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "0.10, 0.11, 0.12, 1.0" -StochasticDiffEq = "6.72.1" StochasticDelayDiffEq = "1.8.1" +StochasticDiffEq = "6.72.1" SymbolicIndexingInterface = "0.3.36" SymbolicUtils = "3.7" Symbolics = "6.19" @@ -154,6 +155,7 @@ julia = "1.9" AmplNLWriter = "7c4d4715-977e-5154-bfe0-e096adeac482" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" BoundaryValueDiffEq = "764a87c0-6b3e-53db-9096-fe964310641d" +BoundaryValueDiffEqAscher = "7227322d-7511-4e07-9247-ad6ff830280e" ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e" DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0" DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6" @@ -185,4 +187,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve"] +test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEq", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve"] diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 74b1bf7596..3c45b1b2f8 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -853,15 +853,47 @@ get_callback(prob::ODEProblem) = prob.kwargs[:callback] ```julia SciMLBase.BVProblem{iip}(sys::AbstractODESystem, u0map, tspan, parammap = DiffEqBase.NullParameters(); + constraints = nothing, guesses = nothing, version = nothing, tgrad = false, jac = true, sparse = true, simplify = false, kwargs...) where {iip} ``` -Create a `BVProblem` from the [`ODESystem`](@ref). The arguments `dvs` and +Create a boundary value problem from the [`ODESystem`](@ref). The arguments `dvs` and `ps` are used to set the order of the dependent variable and parameter vectors, -respectively. `u0map` should be used to specify the initial condition. +respectively. `u0map` is used to specify fixed initial values for the states. + +Every variable must have either an initial guess supplied using `guesses` or +a fixed initial value specified using `u0map`. + +`constraints` are used to specify boundary conditions to the ODESystem in the +form of equations. These values should specify values that state variables should +take at specific points, as in `x(0.5) ~ 1`). More general constraints that +should hold over the entire solution, such as `x(t)^2 + y(t)^2`, should be +specified as one of the equations used to build the `ODESystem`. Below is an example. + +```julia + @parameters g + @variables x(..) y(t) [state_priority = 10] λ(t) + eqs = [D(D(x(t))) ~ λ * x(t) + D(D(y)) ~ λ * y - g + x(t)^2 + y^2 ~ 1] + @mtkbuild pend = ODESystem(eqs, t) + + tspan = (0.0, 1.5) + u0map = [x(t) => 0.6, y => 0.8] + parammap = [g => 1] + guesses = [λ => 1] + constraints = [x(0.5) ~ 1] + + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) +``` + +If no `constraints` are specified, the problem will be treated as an initial value problem. + +If the `ODESystem` has algebraic equations like `x(t)^2 + y(t)^2`, the resulting +`BVProblem` must be solved using BVDAE solvers, such as Ascher. """ function SciMLBase.BVProblem(sys::AbstractODESystem, args...; kwargs...) BVProblem{true}(sys, args...; kwargs...) @@ -873,7 +905,7 @@ function SciMLBase.BVProblem(sys::AbstractODESystem, kwargs...) BVProblem{false, SciMLBase.FullSpecialize}(sys, u0map, args...; kwargs...) end -o + function SciMLBase.BVProblem{true}(sys::AbstractODESystem, args...; kwargs...) BVProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) end @@ -885,6 +917,7 @@ end function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], tspan = get_tspan(sys), parammap = DiffEqBase.NullParameters(); + constraints = nothing, guesses = nothing, version = nothing, tgrad = false, callback = nothing, check_length = true, @@ -892,38 +925,63 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] eval_expression = false, eval_module = @__MODULE__, kwargs...) where {iip, specialize} + if !iscomplete(sys) error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `BVProblem`") end + !isnothing(callbacks) && error("BVP solvers do not support callbacks.") - f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, - check_length, warn_initialize_determined, eval_expression, eval_module, kwargs...) + iv = get_iv(sys) + constraintsts = nothing + constraintps = nothing + sts = unknowns(sys) + ps = parameters(sys) - cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - kwargs = filter_kwargs(kwargs) + if !isnothing(constraints) + constraints isa Equation || + constraints isa Vector{Equation} || + error("Constraints must be specified as an equation or a vector of equations.") - kwargs1 = (;) - if cbs !== nothing - kwargs1 = merge(kwargs1, (callback = cbs,)) + (length(constraints) + length(u0map) > length(sts)) && + error("The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) cannot exceed the total number of states.") + + constraintsts = OrderedSet() + constraintps = OrderedSet() + + for eq in constraints + collect_vars!(constraintsts, constraintps, eq, iv) + validate_constraint_syms(eq, constraintsts, constraintps, Set(sts), Set(ps), iv) + empty!(constraintsts) + empty!(constraintps) + end end - # Handle algebraic equations - stidxmap = Dict([v => i for (i, v) in enumerate(unknowns(sys))]) - pidxmap = Dict([v => i for (i, v) in enumerate(parameters(sys))]) - ns = length(stmap) - ne = length(get_alg_eqs(sys)) + f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, guesses, + check_length, warn_initialize_determined, eval_expression, eval_module, kwargs...) + + stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) + pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) + + # Indices of states that have initial constraints. + u0i = has_alg_eqs(sys) ? collect(1:length(sts)) : [stidxmap[k] for k in keys(u0map)] + ni = length(u0i) - # Define the boundary conditions. - bc = if has_alg_eqs(sys) + bc = if !isnothing(constraints) + ne = length(constraints) if iip (residual,u,p,t) -> begin - residual[1:ns] .= u[1] .- u0 - residual[ns+1:ns+ne] .= sub_u_p_into_symeq.(get_alg_eqs(sys)) + residual[1:ni] .= u[1][u0i] .- u0[u0i] + residual[ni+1:ni+ne] .= map(constraints) do cons + sub_u_p_into_symeq(cons.rhs - cons.lhs, u, p, stidxmap, pidxmap, iv, tspan) + end end else (u,p,t) -> begin - resid = vcat(u[1] - u0, sub_u_p_into_symeq.(get_alg_eqs(sys))) + consresid = map(constraints) do cons + sub_u_p_into_symeq(cons.rhs-cons.lhs, u, p, stidxmap, pidxmap, iv, tspan) + end + resid = vcat(u[1][u0i] - u0[u0i], consresid) end end else @@ -941,32 +999,54 @@ end get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") -# Helper to create the dictionary that will substitute numeric values for u, p into the algebraic equations in the ODESystem. Used to construct the boundary condition function. +# Validate that all the variables in the BVP constraints are well-formed states or parameters. +function validate_constraint_syms(eq, constraintsts, constraintps, sts, ps, iv) + ModelingToolkit.check_variables(constraintsts) + ModelingToolkit.check_parameters(constraintps) + + for var in constraintsts + if arguments(var) == iv + var ∈ sts || error("Constraint equation $eq contains a variable $var that is not a variable of the ODESystem.") + error("Constraint equation $eq contains a variable $var that does not have a specified argument. Such equations should be specified as algebraic equations to the ODESystem rather than a boundary constraints.") + else + operation(var)(iv) ∈ sts || error("Constraint equation $eq contains a variable $(operation(var)) that is not a variable of the ODESystem.") + end + end + + for var in constraintps + if !iscall(var) + var ∈ ps || error("Constraint equation $eq contains a parameter $var that is not a parameter of the ODESystem.") + else + operation(var) ∈ ps || error("Constraint equations contain a parameter $var that is not a parameter of the ODESystem.") + end + end +end + +# Helper to substitute numeric values for u, p into the algebraic equations in the ODESystem. Used to construct the boundary condition function. # Take a system with variables x,y, parameters g # -# 1 + x + y → 1 + u[1][1] + u[1][2] +# 1 + x(0) + y(0) → 1 + u[1][1] + u[1][2] # x(0.5) → u(0.5)[1] # x(0.5)*g(0.5) → u(0.5)[1]*p[1] - -function sub_u_p_into_symeq(eq, u, p, stidxmap, pidxmap) - iv = ModelingToolkit.get_iv(sys) +function sub_u_p_into_symeq(eq, u, p, stidxmap, pidxmap, iv, tspan) eq = Symbolics.unwrap(eq) - stmap = Dict([st => u[1][i] for st => i in stidxmap]) - pmap = Dict([pa => p[i] for pa => i in pidxmap]) + stmap = Dict([st => u[1][i] for (st, i) in stidxmap]) + pmap = Dict([pa => p[i] for (pa, i) in pidxmap]) eq = Symbolics.substitute(eq, merge(stmap, pmap)) csyms = [] # Find most nested calls, substitute those first. while !isempty(find_callable_syms!(csyms, eq)) for sym in csyms - t = arguments(sym)[1] x = operation(sym) + t = arguments(sym)[1] + prog = (tspan[2] - tspan[1])/(t - tspan[1]) # 1 / the % of the timespan elapsed if isparameter(x) eq = Symbolics.substitute(eq, Dict(x(t) => p[pidxmap[x(iv)]])) elseif isvariable(x) - eq = Symbolics.substitute(eq, Dict(x(t) => u(val)[stidxmap[x(iv)]])) + eq = Symbolics.substitute(eq, Dict(x(t) => u[Int(end ÷ prog)][stidxmap[x(iv)]])) end end empty!(csyms) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 2d5535325a..6432c5ae02 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -1,80 +1,85 @@ -using BoundaryValueDiffEq, OrdinaryDiffEq +using BoundaryValueDiffEq, OrdinaryDiffEq, BoundaryValueDiffEqAscher using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D ### Test Collocation solvers on simple problems solvers = [MIRK4, RadauIIa5, LobattoIIIa3] +daesolvers = [Ascher2, Ascher4, Ascher6] -@parameters α=7.5 β=4.0 γ=8.0 δ=5.0 -@variables x(t)=1.0 y(t)=2.0 - -eqs = [D(x) ~ α * x - β * x * y, - D(y) ~ -γ * y + δ * x * y] - -u0map = [x => 1.0, y => 2.0] -parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] -tspan = (0.0, 10.0) - -@mtkbuild lotkavolterra = ODESystem(eqs, t) -op = ODEProblem(lotkavolterra, u0map, tspan, parammap) -osol = solve(op, Vern9()) - -bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( - lotkavolterra, u0map, tspan, parammap; eval_expression = true) - -for solver in solvers - sol = solve(bvp, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - @test sol.u[1] == [1.0, 2.0] -end - -# Test out of place -bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}( - lotkavolterra, u0map, tspan, parammap; eval_expression = true) - -for solver in solvers - sol = solve(bvp2, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - @test sol.u[1] == [1.0, 2.0] +let + @parameters α=7.5 β=4.0 γ=8.0 δ=5.0 + @variables x(t)=1.0 y(t)=2.0 + + eqs = [D(x) ~ α * x - β * x * y, + D(y) ~ -γ * y + δ * x * y] + + u0map = [x => 1.0, y => 2.0] + parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] + tspan = (0.0, 10.0) + + @mtkbuild lotkavolterra = ODESystem(eqs, t) + op = ODEProblem(lotkavolterra, u0map, tspan, parammap) + osol = solve(op, Vern9()) + + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( + lotkavolterra, u0map, tspan, parammap; eval_expression = true) + + for solver in solvers + sol = solve(bvp, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + @test sol.u[1] == [1.0, 2.0] + end + + # Test out of place + bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}( + lotkavolterra, u0map, tspan, parammap; eval_expression = true) + + for solver in solvers + sol = solve(bvp2, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + @test sol.u[1] == [1.0, 2.0] + end end ### Testing on pendulum - -@parameters g=9.81 L=1.0 -@variables θ(t) = π / 2 - -eqs = [D(D(θ)) ~ -(g / L) * sin(θ)] - -@mtkbuild pend = ODESystem(eqs, t) - -u0map = [θ => π / 2, D(θ) => π / 2] -parammap = [:L => 1.0, :g => 9.81] -tspan = (0.0, 6.0) - -op = ODEProblem(pend, u0map, tspan, parammap) -osol = solve(op, Vern9()) - -bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) -for solver in solvers - sol = solve(bvp, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - @test sol.u[1] == [π / 2, π / 2] -end - -# Test out-of-place -bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) - -for solver in solvers - sol = solve(bvp2, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - @test sol.u[1] == [π / 2, π / 2] +let + @parameters g=9.81 L=1.0 + @variables θ(t) = π / 2 + + eqs = [D(D(θ)) ~ -(g / L) * sin(θ)] + + @mtkbuild pend = ODESystem(eqs, t) + + u0map = [θ => π / 2, D(θ) => π / 2] + parammap = [:L => 1.0, :g => 9.81] + tspan = (0.0, 6.0) + + op = ODEProblem(pend, u0map, tspan, parammap) + osol = solve(op, Vern9()) + + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) + for solver in solvers + sol = solve(bvp, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + @test sol.u[1] == [π / 2, π / 2] + end + + # Test out-of-place + bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) + + for solver in solvers + sol = solve(bvp2, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + @test sol.u[1] == [π / 2, π / 2] + end end ################################################### -### TESTING ODESystem with Constraint Equations ### +### ODESystem with Constraint Equations, DAEs with constraints ### ################################################### -# Cartesian pendulum from the docs. Testing that initialization is satisfied. +# Cartesian pendulum from the docs. +# DAE IVP solved using BoundaryValueDiffEq solvers. let @parameters g @variables x(t) y(t) [state_priority = 10] λ(t) @@ -109,14 +114,74 @@ let end end -# Adding a midpoint boundary condition. +function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.01) + for solver in solvers + sol = solve(bvp, solver(); dt) + + for (k, v) in u0map + @test sol[k][1] == v + end + + for cons in constraints + @test sol[cons.rhs - cons.lhs] ≈ 0 + end + + for eq in equations + @test sol[eq] ≈ 0 + end + end +end + +# Simple ODESystem with BVP constraints. +let + @parameters α=7.5 β=4.0 γ=8.0 δ=5.0 + @variables x(..) y(t) + + eqs = [D(x) ~ α * x - β * x * y, + D(y) ~ -γ * y + δ * x * y] + + u0map = [y => 2.0] + parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] + tspan = (0.0, 10.0) + guesses = [x => 1.0] + + @mtkbuild lotkavolterra = ODESystem(eqs, t) + op = ODEProblem(lotkavolterra, u0map, tspan, parammap, guesses = guesses) + + constraints = [x(6.) ~ 3] + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) + test_solvers(solvers, bvp, u0map, constraints) + + # Testing that more complicated constraints give correct solutions. + constraints = [y(2.) + x(8.) ~ 12] + bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) + test_solvers(solvers, bvp, u0map, constraints) + + constraints = [α * β - x(6.) ~ 24] + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) + test_solvers(solvers, bvp, u0map, constraints) + + # Testing that errors are properly thrown when malformed constraints are given. + @variables bad(..) + constraints = [x(1.) + bad(3.) ~ 10] + @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) + + constraints = [x(t) + y(t) ~ 3] + @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) + + @parameters bad2 + constraints = [bad2 + x(0.) ~ 3] + @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) +end + +# Adding a midpoint boundary constraint. +# Solve using BVDAE solvers. let @parameters g @variables x(..) y(t) [state_priority = 10] λ(t) eqs = [D(D(x(t))) ~ λ * x(t) D(D(y)) ~ λ * y - g - x(t)^2 + y^2 ~ 1 - x(0.5) ~ 1] + x(t)^2 + y^2 ~ 1] @mtkbuild pend = ODESystem(eqs, t) tspan = (0.0, 1.5) @@ -124,37 +189,24 @@ let parammap = [g => 1] guesses = [λ => 1] - prob = ODEProblem(pend, u0map, tspan, pmap; guesses, check_length = false) - sol = solve(prob, Rodas5P()) + constraints = [x(0.5) ~ 1] + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) + test_solvers(daesolvers, bvp, u0map, constraints) - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses = guesses, check_length = false) - - for solver in solvers - sol = solve(bvp, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - conditions = getfield.(equations(pend)[3:end], :rhs) - @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 - @test sol.u[1] == [π / 2, π / 2] - end + bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) + test_solvers(daesolvers, bvp2, u0map, constraints, get_alg_eqs(pend)) - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses) - - for solver in solvers - sol = solve(bvp, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - conditions = getfield.(equations(pend)[3:end], :rhs) - @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 - end + # More complicated constraints. + u0map = [x(t) => 0.6] + guesses = [λ => 1, y(t) => 0.8] - bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) - for solver in solvers - sol = solve(bvp, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - conditions = getfield.(equations(pend)[3:end], :rhs) - @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 - end -end + constraints = [x(0.5) ~ 1, + x(0.3)^3 + y(0.6)^2 ~ 0.5] + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) + test_solvers(daesolvers, bvp, u0map, constraints, get_alg_eqs(pend)) -# Testing a more complicated case with multiple constraints. -let + constraints = [x(0.4) * g ~ y(0.2), + y(0.7) ~ 0.3] + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) + test_solvers(daesolvers, bvp, u0map, constraints, get_alg_eqs(pend)) end From 5d082ab05dc674b005ef1a724aa7273a712c2b66 Mon Sep 17 00:00:00 2001 From: vyudu Date: Sat, 11 Jan 2025 16:20:55 -0500 Subject: [PATCH 20/50] up --- src/systems/diffeqs/abstractodesystem.jl | 8 +++---- test/bvproblem.jl | 27 ++++++++++++------------ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 3c45b1b2f8..99b34ec1a8 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -917,7 +917,7 @@ end function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], tspan = get_tspan(sys), parammap = DiffEqBase.NullParameters(); - constraints = nothing, guesses = nothing, + constraints = nothing, guesses = Dict(), version = nothing, tgrad = false, callback = nothing, check_length = true, @@ -929,7 +929,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] if !iscomplete(sys) error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `BVProblem`") end - !isnothing(callbacks) && error("BVP solvers do not support callbacks.") + !isnothing(callback) && error("BVP solvers do not support callbacks.") iv = get_iv(sys) constraintsts = nothing @@ -964,7 +964,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) # Indices of states that have initial constraints. - u0i = has_alg_eqs(sys) ? collect(1:length(sts)) : [stidxmap[k] for k in keys(u0map)] + u0i = has_alg_eqs(sys) ? collect(1:length(sts)) : [stidxmap[k] for (k,v) in u0map] ni = length(u0i) bc = if !isnothing(constraints) @@ -994,7 +994,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] end end - return BVProblem{iip}(f, bc, u0, tspan, p; kwargs1..., kwargs...) + return BVProblem{iip}(f, bc, u0, tspan, p; kwargs...) end get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 6432c5ae02..e864433f3c 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -44,13 +44,14 @@ end ### Testing on pendulum let @parameters g=9.81 L=1.0 - @variables θ(t) = π / 2 + @variables θ(t) = π / 2 θ_t(t) - eqs = [D(D(θ)) ~ -(g / L) * sin(θ)] + eqs = [D(θ) ~ θ_t + D(θ_t) ~ -(g / L) * sin(θ)] @mtkbuild pend = ODESystem(eqs, t) - u0map = [θ => π / 2, D(θ) => π / 2] + u0map = [θ => π / 2, θ_t => π / 2] parammap = [:L => 1.0, :g => 9.81] tspan = (0.0, 6.0) @@ -74,9 +75,9 @@ let end end -################################################### -### ODESystem with Constraint Equations, DAEs with constraints ### -################################################### +################################################################## +### ODESystem with constraint equations, DAEs with constraints ### +################################################################## # Cartesian pendulum from the docs. # DAE IVP solved using BoundaryValueDiffEq solvers. @@ -90,19 +91,19 @@ let tspan = (0.0, 1.5) u0map = [x => 1, y => 0] - parammap = [g => 1] - guesses = [λ => 1] + pmap = [g => 1] + guess = [λ => 1] - prob = ODEProblem(pend, u0map, tspan, pmap; guesses) - sol = solve(prob, Rodas5P()) + prob = ODEProblem(pend, u0map, tspan, pmap; guesses = guess) + osol = solve(prob, Rodas5P()) - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses) + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses = guess) for solver in solvers - sol = solve(bvp, solver(), dt = 0.01) + sol = solve(bvp, solver(), dt = 0.001) @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) conditions = getfield.(equations(pend)[3:end], :rhs) - @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 + @test isapprox([sol[conditions][1]; sol[x][1] - 1; sol[y][1]], zeros(5), atol = 0.001) end bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) From b83e003babe1347439d05ea87a8b90995f1dcc82 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 14 Jan 2025 00:59:31 -0500 Subject: [PATCH 21/50] refactor the bc creation function --- src/systems/diffeqs/abstractodesystem.jl | 189 ++++++++++--------- test/bvproblem.jl | 230 ++++++++++++++++------- 2 files changed, 264 insertions(+), 155 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 99b34ec1a8..25347a17cd 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -931,68 +931,60 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] end !isnothing(callback) && error("BVP solvers do not support callbacks.") - iv = get_iv(sys) + has_alg_eqs(sys) && error("The BVProblem currently does not support ODESystems with algebraic equations.") # Remove this when the BVDAE solvers get updated, the codegen should work when it does. + constraintsts = nothing constraintps = nothing sts = unknowns(sys) ps = parameters(sys) - if !isnothing(constraints) + # Constraint validation + f_cons = if !isnothing(constraints) constraints isa Equation || constraints isa Vector{Equation} || error("Constraints must be specified as an equation or a vector of equations.") (length(constraints) + length(u0map) > length(sts)) && - error("The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) cannot exceed the total number of states.") - - constraintsts = OrderedSet() - constraintps = OrderedSet() - - for eq in constraints - collect_vars!(constraintsts, constraintps, eq, iv) - validate_constraint_syms(eq, constraintsts, constraintps, Set(sts), Set(ps), iv) - empty!(constraintsts) - empty!(constraintps) - end + error("The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) cannot exceed the total number of states.") end - f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; + # ODESystems without algebraic equations should use both fixed values + guesses + # for initialization. + _u0map = has_alg_eqs(sys) ? u0map : merge(Dict(u0map), Dict(guesses)) + f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, _u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, guesses, check_length, warn_initialize_determined, eval_expression, eval_module, kwargs...) stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) - pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) - - # Indices of states that have initial constraints. - u0i = has_alg_eqs(sys) ? collect(1:length(sts)) : [stidxmap[k] for (k,v) in u0map] - ni = length(u0i) - - bc = if !isnothing(constraints) - ne = length(constraints) - if iip - (residual,u,p,t) -> begin - residual[1:ni] .= u[1][u0i] .- u0[u0i] - residual[ni+1:ni+ne] .= map(constraints) do cons - sub_u_p_into_symeq(cons.rhs - cons.lhs, u, p, stidxmap, pidxmap, iv, tspan) - end - end - else - (u,p,t) -> begin - consresid = map(constraints) do cons - sub_u_p_into_symeq(cons.rhs-cons.lhs, u, p, stidxmap, pidxmap, iv, tspan) - end - resid = vcat(u[1][u0i] - u0[u0i], consresid) - end - end - else - if iip - (residual,u,p,t) -> begin - residual .= u[1] .- u0 - end - else - (u,p,t) -> (u[1] - u0) - end - end + u0_idxs = has_alg_eqs(sys) ? collect(1:length(sts)) : [stidxmap[k] for (k,v) in u0map] + + # bc = if !isnothing(constraints) && iip + # (residual,u,p,t) -> begin + # println(u(0.5)) + # residual[1:ni] .= u[1][u0i] .- u0[u0i] + # for (i, cons) in enumerate(constraints) + # residual[ni+i] = eval_symbolic_residual(cons, u, p, stidxmap, pidxmap, iv, tspan) + # end + # end + + # elseif !isnothing(constraints) && !iip + # (u,p,t) -> begin + # consresid = map(constraints) do cons + # eval_symbolic_residual(cons, u, p, stidxmap, pidxmap, iv, tspan) + # end + # resid = vcat(u[1][u0i] - u0[u0i], consresid) + # end + + # elseif iip + # (residual,u,p,t) -> begin + # println(u(0.5)) + # residual .= u[1] .- u0 + # end + + # else + # (u,p,t) -> (u[1] - u0) + # end + bc = process_constraints(sys, constraints, u0, u0_idxs, tspan, iip) return BVProblem{iip}(f, bc, u0, tspan, p; kwargs...) end @@ -1001,11 +993,10 @@ get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") # Validate that all the variables in the BVP constraints are well-formed states or parameters. function validate_constraint_syms(eq, constraintsts, constraintps, sts, ps, iv) - ModelingToolkit.check_variables(constraintsts) - ModelingToolkit.check_parameters(constraintps) - for var in constraintsts - if arguments(var) == iv + if length(arguments(var)) > 1 + error("Too many arguments for variable $var.") + elseif arguments(var) == iv var ∈ sts || error("Constraint equation $eq contains a variable $var that is not a variable of the ODESystem.") error("Constraint equation $eq contains a variable $var that does not have a specified argument. Such equations should be specified as algebraic equations to the ODESystem rather than a boundary constraints.") else @@ -1017,53 +1008,81 @@ function validate_constraint_syms(eq, constraintsts, constraintps, sts, ps, iv) if !iscall(var) var ∈ ps || error("Constraint equation $eq contains a parameter $var that is not a parameter of the ODESystem.") else + length(arguments(var)) > 1 && error("Too many arguments for parameter $var.") operation(var) ∈ ps || error("Constraint equations contain a parameter $var that is not a parameter of the ODESystem.") end end end -# Helper to substitute numeric values for u, p into the algebraic equations in the ODESystem. Used to construct the boundary condition function. -# Take a system with variables x,y, parameters g -# -# 1 + x(0) + y(0) → 1 + u[1][1] + u[1][2] -# x(0.5) → u(0.5)[1] -# x(0.5)*g(0.5) → u(0.5)[1]*p[1] -function sub_u_p_into_symeq(eq, u, p, stidxmap, pidxmap, iv, tspan) - eq = Symbolics.unwrap(eq) - - stmap = Dict([st => u[1][i] for (st, i) in stidxmap]) - pmap = Dict([pa => p[i] for (pa, i) in pidxmap]) - eq = Symbolics.substitute(eq, merge(stmap, pmap)) - - csyms = [] - # Find most nested calls, substitute those first. - while !isempty(find_callable_syms!(csyms, eq)) - for sym in csyms - x = operation(sym) - t = arguments(sym)[1] - prog = (tspan[2] - tspan[1])/(t - tspan[1]) # 1 / the % of the timespan elapsed - - if isparameter(x) - eq = Symbolics.substitute(eq, Dict(x(t) => p[pidxmap[x(iv)]])) - elseif isvariable(x) - eq = Symbolics.substitute(eq, Dict(x(t) => u[Int(end ÷ prog)][stidxmap[x(iv)]])) +""" + process_constraints(sys, constraints, u0, tspan, iip) + + Given an ODESystem with some constraints, generate the boundary condition function. +""" +function process_constraints(sys::ODESystem, constraints, u0, u0_idxs, tspan, iip) + + iv = get_iv(sys) + sts = get_unknowns(sys) + ps = get_ps(sys) + np = length(ps) + ns = length(sts) + + stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) + pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) + + @variables sol(..)[1:ns] p[1:np] + exprs = Any[] + + constraintsts = OrderedSet() + constraintps = OrderedSet() + + !isnothing(constraints) && for cons in constraints + collect_vars!(constraintsts, constraintps, cons, iv) + validate_constraint_syms(cons, constraintsts, constraintps, Set(sts), Set(ps), iv) + expr = cons.rhs - cons.lhs + + for st in constraintsts + x = operation(st) + t = arguments(st)[1] + idx = stidxmap[x(iv)] + + expr = Symbolics.substitute(expr, Dict(x(t) => sol(t)[idx])) + end + + for var in constraintps + if iscall(var) + x = operation(var) + t = arguments(var)[1] + idx = pidxmap[x] + + expr = Symbolics.substitute(expr, Dict(x(t) => p[idx])) + else + idx = pidxmap[var] + expr = Symbolics.substitute(expr, Dict(var => p[idx])) end end - empty!(csyms) + + empty!(constraintsts) + empty!(constraintps) + push!(exprs, expr) end - eq -end -function find_callable_syms!(csyms, ex) - ex = Symbolics.unwrap(ex) + init_cond_exprs = Any[] - if iscall(ex) - operation(ex) isa Symbolic && (arguments(ex)[1] isa Symbolic) && push!(csyms, ex) # only add leaf nodes - for arg in arguments(ex) - find_callable_syms!(csyms, arg) + for i in u0_idxs + expr = sol(tspan[1])[i] - u0[i] + push!(init_cond_exprs, expr) + end + + exprs = vcat(init_cond_exprs, exprs) + bcs = Symbolics.build_function(exprs, sol, p, expression = Val{false}) + if iip + return (resid, u, p, t) -> begin + bcs[2](resid, u, p) end + else + return (u, p, t) -> bcs[1](u, p) end - csyms end """ diff --git a/test/bvproblem.jl b/test/bvproblem.jl index e864433f3c..7e0d45b128 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -1,3 +1,5 @@ +### TODO: update when BoundaryValueDiffEqAscher is updated to use the normal boundary condition conventions + using BoundaryValueDiffEq, OrdinaryDiffEq, BoundaryValueDiffEqAscher using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D @@ -81,51 +83,140 @@ end # Cartesian pendulum from the docs. # DAE IVP solved using BoundaryValueDiffEq solvers. +# let +# @parameters g +# @variables x(t) y(t) [state_priority = 10] λ(t) +# eqs = [D(D(x)) ~ λ * x +# D(D(y)) ~ λ * y - g +# x^2 + y^2 ~ 1] +# @mtkbuild pend = ODESystem(eqs, t) +# +# tspan = (0.0, 1.5) +# u0map = [x => 1, y => 0] +# pmap = [g => 1] +# guess = [λ => 1] +# +# prob = ODEProblem(pend, u0map, tspan, pmap; guesses = guess) +# osol = solve(prob, Rodas5P()) +# +# zeta = [0., 0., 0., 0., 0.] +# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses = guess) +# +# for solver in solvers +# sol = solve(bvp, solver(zeta), dt = 0.001) +# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) +# conditions = getfield.(equations(pend)[3:end], :rhs) +# @test isapprox([sol[conditions][1]; sol[x][1] - 1; sol[y][1]], zeros(5), atol = 0.001) +# end +# +# bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) +# for solver in solvers +# sol = solve(bvp, solver(zeta), dt = 0.01) +# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) +# conditions = getfield.(equations(pend)[3:end], :rhs) +# @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 +# end +# end + +# Test generation of boundary condition function. let - @parameters g - @variables x(t) y(t) [state_priority = 10] λ(t) - eqs = [D(D(x)) ~ λ * x - D(D(y)) ~ λ * y - g - x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) - - tspan = (0.0, 1.5) - u0map = [x => 1, y => 0] - pmap = [g => 1] - guess = [λ => 1] - - prob = ODEProblem(pend, u0map, tspan, pmap; guesses = guess) - osol = solve(prob, Rodas5P()) - - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses = guess) + @parameters α=7.5 β=4.0 γ=8.0 δ=5.0 + @variables x(..) y(t) + eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y, + D(y) ~ -γ * y + δ * x(t) * y] - for solver in solvers - sol = solve(bvp, solver(), dt = 0.001) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - conditions = getfield.(equations(pend)[3:end], :rhs) - @test isapprox([sol[conditions][1]; sol[x][1] - 1; sol[y][1]], zeros(5), atol = 0.001) + tspan = (0., 10.) + @mtkbuild lksys = ODESystem(eqs, t) + + function lotkavolterra!(du, u, p, t) + du[1] = p[1]*u[1] - p[2]*u[1]*u[2] + du[2] = -p[3]*u[2] + p[4]*u[1]*u[2] end - bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) - for solver in solvers - sol = solve(bvp, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - conditions = getfield.(equations(pend)[3:end], :rhs) - @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 + function lotkavolterra(u, p, t) + [p[1]*u[1] - p[2]*u[1]*u[2], -p[3]*u[2] + p[4]*u[1]*u[2]] + end + # Compare the built bc function to the actual constructed one. + function bc!(resid, u, p, t) + resid[1] = u[1][1] - 1. + resid[2] = u[1][2] - 2. + nothing + end + function bc(u, p, t) + [u[1][1] - 1., u[1][2] - 2.] + end + + constraints = nothing + u0 = [1., 2.]; p = [7.5, 4., 8., 5.] + genbc_iip = ModelingToolkit.process_constraints(lksys, constraints, u0, [1, 2], tspan, true) + genbc_oop = ModelingToolkit.process_constraints(lksys, constraints, u0, [1, 2], tspan, false) + + bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1,2], tspan, p) + bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1,2], tspan, p) + + sol1 = solve(bvpi1, MIRK4(), dt = 0.01) + sol2 = solve(bvpi2, MIRK4(), dt = 0.01) + @test sol1 ≈ sol2 + + bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) + bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) + + sol1 = solve(bvpo1, MIRK4(), dt = 0.01) + sol2 = solve(bvpo2, MIRK4(), dt = 0.01) + @test sol1 ≈ sol2 + + # Test with a constraint. + constraints = [x(0.5) ~ 1.] + + function bc!(resid, u, p, t) + resid[1] = u[1][2] - 2. + resid[2] = u(0.5)[1] - 1. + end + function bc(u, p, t) + [u[1][2] - 2., u(0.5)[1] - 1.] end + + u0 = [1., 2.] + genbc_iip = ModelingToolkit.process_constraints(lksys, constraints, u0, [2], tspan, true) + genbc_oop = ModelingToolkit.process_constraints(lksys, constraints, u0, [2], tspan, false) + + bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1,2], tspan, p) + bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1,2], tspan, p) + + sol1 = solve(bvpi1, MIRK4(), dt = 0.01) + sol2 = solve(bvpi2, MIRK4(), dt = 0.01) + @test sol1 ≈ sol2 # don't get true equality here, not sure why + + bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) + bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) + + sol1 = solve(bvpo1, MIRK4(), dt = 0.01) + sol2 = solve(bvpo2, MIRK4(), dt = 0.01) + @test sol1 ≈ sol2 end function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.01) for solver in solvers - sol = solve(bvp, solver(); dt) + sol = solve(prob, solver(); dt) + @test successful_retcode(sol.retcode) + p = prob.p; t = sol.t; bc = prob.f.bc + ns = length(prob.u0) + + if isinplace(bvp.f) + resid = zeros(ns) + bc!(resid, sol, p, t) + @test isapprox(zeros(ns), resid) + else + @test isapprox(zeros(ns), bc(sol, p, t)) + end for (k, v) in u0map @test sol[k][1] == v end - for cons in constraints - @test sol[cons.rhs - cons.lhs] ≈ 0 - end + # for cons in constraints + # @test sol[cons.rhs - cons.lhs] ≈ 0 + # end for eq in equations @test sol[eq] ≈ 0 @@ -138,19 +229,18 @@ let @parameters α=7.5 β=4.0 γ=8.0 δ=5.0 @variables x(..) y(t) - eqs = [D(x) ~ α * x - β * x * y, - D(y) ~ -γ * y + δ * x * y] + eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y, + D(y) ~ -γ * y + δ * x(t) * y] u0map = [y => 2.0] parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] tspan = (0.0, 10.0) - guesses = [x => 1.0] + guesses = [x(t) => 1.0] @mtkbuild lotkavolterra = ODESystem(eqs, t) - op = ODEProblem(lotkavolterra, u0map, tspan, parammap, guesses = guesses) constraints = [x(6.) ~ 3] - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) test_solvers(solvers, bvp, u0map, constraints) # Testing that more complicated constraints give correct solutions. @@ -177,37 +267,37 @@ end # Adding a midpoint boundary constraint. # Solve using BVDAE solvers. -let - @parameters g - @variables x(..) y(t) [state_priority = 10] λ(t) - eqs = [D(D(x(t))) ~ λ * x(t) - D(D(y)) ~ λ * y - g - x(t)^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) - - tspan = (0.0, 1.5) - u0map = [x(t) => 0.6, y => 0.8] - parammap = [g => 1] - guesses = [λ => 1] - - constraints = [x(0.5) ~ 1] - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) - test_solvers(daesolvers, bvp, u0map, constraints) - - bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) - test_solvers(daesolvers, bvp2, u0map, constraints, get_alg_eqs(pend)) - - # More complicated constraints. - u0map = [x(t) => 0.6] - guesses = [λ => 1, y(t) => 0.8] - - constraints = [x(0.5) ~ 1, - x(0.3)^3 + y(0.6)^2 ~ 0.5] - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) - test_solvers(daesolvers, bvp, u0map, constraints, get_alg_eqs(pend)) - - constraints = [x(0.4) * g ~ y(0.2), - y(0.7) ~ 0.3] - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) - test_solvers(daesolvers, bvp, u0map, constraints, get_alg_eqs(pend)) -end +# let +# @parameters g +# @variables x(..) y(t) [state_priority = 10] λ(t) +# eqs = [D(D(x(t))) ~ λ * x(t) +# D(D(y)) ~ λ * y - g +# x(t)^2 + y^2 ~ 1] +# @mtkbuild pend = ODESystem(eqs, t) +# +# tspan = (0.0, 1.5) +# u0map = [x(t) => 0.6, y => 0.8] +# parammap = [g => 1] +# guesses = [λ => 1] +# +# constraints = [x(0.5) ~ 1] +# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) +# test_solvers(daesolvers, bvp, u0map, constraints) +# +# bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) +# test_solvers(daesolvers, bvp2, u0map, constraints, get_alg_eqs(pend)) +# +# # More complicated constraints. +# u0map = [x(t) => 0.6] +# guesses = [λ => 1, y(t) => 0.8] +# +# constraints = [x(0.5) ~ 1, +# x(0.3)^3 + y(0.6)^2 ~ 0.5] +# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) +# test_solvers(daesolvers, bvp, u0map, constraints, get_alg_eqs(pend)) +# +# constraints = [x(0.4) * g ~ y(0.2), +# y(0.7) ~ 0.3] +# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) +# test_solvers(daesolvers, bvp, u0map, constraints, get_alg_eqs(pend)) +# end From db5eb66ea29533e51aefd6ea49ea04e0257f3201 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 14 Jan 2025 13:46:31 -0500 Subject: [PATCH 22/50] up --- src/systems/diffeqs/abstractodesystem.jl | 33 +----- test/bvproblem.jl | 144 ++++++++++++----------- 2 files changed, 76 insertions(+), 101 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 25347a17cd..0e8435942c 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -939,7 +939,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] ps = parameters(sys) # Constraint validation - f_cons = if !isnothing(constraints) + if !isnothing(constraints) constraints isa Equation || constraints isa Vector{Equation} || error("Constraints must be specified as an equation or a vector of equations.") @@ -958,32 +958,6 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) u0_idxs = has_alg_eqs(sys) ? collect(1:length(sts)) : [stidxmap[k] for (k,v) in u0map] - # bc = if !isnothing(constraints) && iip - # (residual,u,p,t) -> begin - # println(u(0.5)) - # residual[1:ni] .= u[1][u0i] .- u0[u0i] - # for (i, cons) in enumerate(constraints) - # residual[ni+i] = eval_symbolic_residual(cons, u, p, stidxmap, pidxmap, iv, tspan) - # end - # end - - # elseif !isnothing(constraints) && !iip - # (u,p,t) -> begin - # consresid = map(constraints) do cons - # eval_symbolic_residual(cons, u, p, stidxmap, pidxmap, iv, tspan) - # end - # resid = vcat(u[1][u0i] - u0[u0i], consresid) - # end - - # elseif iip - # (residual,u,p,t) -> begin - # println(u(0.5)) - # residual .= u[1] .- u0 - # end - - # else - # (u,p,t) -> (u[1] - u0) - # end bc = process_constraints(sys, constraints, u0, u0_idxs, tspan, iip) return BVProblem{iip}(f, bc, u0, tspan, p; kwargs...) @@ -1075,11 +1049,10 @@ function process_constraints(sys::ODESystem, constraints, u0, u0_idxs, tspan, ii end exprs = vcat(init_cond_exprs, exprs) + @show exprs bcs = Symbolics.build_function(exprs, sol, p, expression = Val{false}) if iip - return (resid, u, p, t) -> begin - bcs[2](resid, u, p) - end + return (resid, u, p, t) -> bcs[2](resid, u, p) else return (u, p, t) -> bcs[1](u, p) end diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 7e0d45b128..42762b8a3f 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -2,7 +2,9 @@ using BoundaryValueDiffEq, OrdinaryDiffEq, BoundaryValueDiffEqAscher using ModelingToolkit +using SciMLBase using ModelingToolkit: t_nounits as t, D_nounits as D +import ModelingToolkit: process_constraints ### Test Collocation solvers on simple problems solvers = [MIRK4, RadauIIa5, LobattoIIIa3] @@ -81,46 +83,9 @@ end ### ODESystem with constraint equations, DAEs with constraints ### ################################################################## -# Cartesian pendulum from the docs. -# DAE IVP solved using BoundaryValueDiffEq solvers. -# let -# @parameters g -# @variables x(t) y(t) [state_priority = 10] λ(t) -# eqs = [D(D(x)) ~ λ * x -# D(D(y)) ~ λ * y - g -# x^2 + y^2 ~ 1] -# @mtkbuild pend = ODESystem(eqs, t) -# -# tspan = (0.0, 1.5) -# u0map = [x => 1, y => 0] -# pmap = [g => 1] -# guess = [λ => 1] -# -# prob = ODEProblem(pend, u0map, tspan, pmap; guesses = guess) -# osol = solve(prob, Rodas5P()) -# -# zeta = [0., 0., 0., 0., 0.] -# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses = guess) -# -# for solver in solvers -# sol = solve(bvp, solver(zeta), dt = 0.001) -# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) -# conditions = getfield.(equations(pend)[3:end], :rhs) -# @test isapprox([sol[conditions][1]; sol[x][1] - 1; sol[y][1]], zeros(5), atol = 0.001) -# end -# -# bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) -# for solver in solvers -# sol = solve(bvp, solver(zeta), dt = 0.01) -# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) -# conditions = getfield.(equations(pend)[3:end], :rhs) -# @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 -# end -# end - # Test generation of boundary condition function. let - @parameters α=7.5 β=4.0 γ=8.0 δ=5.0 + @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 @variables x(..) y(t) eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y, D(y) ~ -γ * y + δ * x(t) * y] @@ -130,11 +95,11 @@ let function lotkavolterra!(du, u, p, t) du[1] = p[1]*u[1] - p[2]*u[1]*u[2] - du[2] = -p[3]*u[2] + p[4]*u[1]*u[2] + du[2] = -p[4]*u[2] + p[3]*u[1]*u[2] end function lotkavolterra(u, p, t) - [p[1]*u[1] - p[2]*u[1]*u[2], -p[3]*u[2] + p[4]*u[1]*u[2]] + [p[1]*u[1] - p[2]*u[1]*u[2], -p[4]*u[2] + p[3]*u[1]*u[2]] end # Compare the built bc function to the actual constructed one. function bc!(resid, u, p, t) @@ -146,23 +111,22 @@ let [u[1][1] - 1., u[1][2] - 2.] end - constraints = nothing - u0 = [1., 2.]; p = [7.5, 4., 8., 5.] - genbc_iip = ModelingToolkit.process_constraints(lksys, constraints, u0, [1, 2], tspan, true) - genbc_oop = ModelingToolkit.process_constraints(lksys, constraints, u0, [1, 2], tspan, false) + u0 = [1., 2.]; p = [1.5, 1., 3., 1.] + genbc_iip = ModelingToolkit.process_constraints(lksys, nothing, u0, [1, 2], tspan, true) + genbc_oop = ModelingToolkit.process_constraints(lksys, nothing, u0, [1, 2], tspan, false) - bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1,2], tspan, p) - bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1,2], tspan, p) + bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1.,2.], tspan, p) + bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1.,2.], tspan, p) - sol1 = solve(bvpi1, MIRK4(), dt = 0.01) - sol2 = solve(bvpi2, MIRK4(), dt = 0.01) + sol1 = solve(bvpi1, MIRK4(), dt = 0.05) + sol2 = solve(bvpi2, MIRK4(), dt = 0.05) @test sol1 ≈ sol2 bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) - sol1 = solve(bvpo1, MIRK4(), dt = 0.01) - sol2 = solve(bvpo2, MIRK4(), dt = 0.01) + sol1 = solve(bvpo1, MIRK4(), dt = 0.05) + sol2 = solve(bvpo2, MIRK4(), dt = 0.05) @test sol1 ≈ sol2 # Test with a constraint. @@ -180,28 +144,28 @@ let genbc_iip = ModelingToolkit.process_constraints(lksys, constraints, u0, [2], tspan, true) genbc_oop = ModelingToolkit.process_constraints(lksys, constraints, u0, [2], tspan, false) - bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1,2], tspan, p) - bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1,2], tspan, p) - - sol1 = solve(bvpi1, MIRK4(), dt = 0.01) - sol2 = solve(bvpi2, MIRK4(), dt = 0.01) + bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1.,2.], tspan, p) + bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1.,2.], tspan, p) + bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan, parammap; guesses, constraints) + + sol1 = solve(bvpi1, MIRK4(), dt = 0.05) + sol2 = solve(bvpi2, MIRK4(), dt = 0.05) @test sol1 ≈ sol2 # don't get true equality here, not sure why bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) - sol1 = solve(bvpo1, MIRK4(), dt = 0.01) - sol2 = solve(bvpo2, MIRK4(), dt = 0.01) + sol1 = solve(bvpo1, MIRK4(), dt = 0.05) + sol2 = solve(bvpo2, MIRK4(), dt = 0.05) @test sol1 ≈ sol2 end -function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.01) +function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.05) for solver in solvers sol = solve(prob, solver(); dt) - @test successful_retcode(sol.retcode) + @test SciMLBase.successful_retcode(sol.retcode) p = prob.p; t = sol.t; bc = prob.f.bc ns = length(prob.u0) - if isinplace(bvp.f) resid = zeros(ns) bc!(resid, sol, p, t) @@ -226,45 +190,83 @@ end # Simple ODESystem with BVP constraints. let - @parameters α=7.5 β=4.0 γ=8.0 δ=5.0 + t = ModelingToolkit.t_nounits; D = ModelingToolkit.D_nounits + @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 @variables x(..) y(t) eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y, D(y) ~ -γ * y + δ * x(t) * y] - u0map = [y => 2.0] + u0map = [] parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] tspan = (0.0, 10.0) - guesses = [x(t) => 1.0] + guesses = [x(t) => 1.0, y => 2.] @mtkbuild lotkavolterra = ODESystem(eqs, t) - constraints = [x(6.) ~ 3] + constraints = [x(6.) ~ 1.5] bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) test_solvers(solvers, bvp, u0map, constraints) # Testing that more complicated constraints give correct solutions. - constraints = [y(2.) + x(8.) ~ 12] - bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) + constraints = [y(2.) + x(8.) ~ 2.] + bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) test_solvers(solvers, bvp, u0map, constraints) - constraints = [α * β - x(6.) ~ 24] - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) + constraints = [α * β - x(6.) ~ 0.5] + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) test_solvers(solvers, bvp, u0map, constraints) # Testing that errors are properly thrown when malformed constraints are given. @variables bad(..) constraints = [x(1.) + bad(3.) ~ 10] - @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) + @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) constraints = [x(t) + y(t) ~ 3] - @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) + @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) @parameters bad2 constraints = [bad2 + x(0.) ~ 3] - @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, constraints) + @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) end +# Cartesian pendulum from the docs. +# DAE IVP solved using BoundaryValueDiffEq solvers. +# let +# @parameters g +# @variables x(t) y(t) [state_priority = 10] λ(t) +# eqs = [D(D(x)) ~ λ * x +# D(D(y)) ~ λ * y - g +# x^2 + y^2 ~ 1] +# @mtkbuild pend = ODESystem(eqs, t) +# +# tspan = (0.0, 1.5) +# u0map = [x => 1, y => 0] +# pmap = [g => 1] +# guess = [λ => 1] +# +# prob = ODEProblem(pend, u0map, tspan, pmap; guesses = guess) +# osol = solve(prob, Rodas5P()) +# +# zeta = [0., 0., 0., 0., 0.] +# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses = guess) +# +# for solver in solvers +# sol = solve(bvp, solver(zeta), dt = 0.001) +# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) +# conditions = getfield.(equations(pend)[3:end], :rhs) +# @test isapprox([sol[conditions][1]; sol[x][1] - 1; sol[y][1]], zeros(5), atol = 0.001) +# end +# +# bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) +# for solver in solvers +# sol = solve(bvp, solver(zeta), dt = 0.01) +# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) +# conditions = getfield.(equations(pend)[3:end], :rhs) +# @test [sol[conditions][1]; sol[x][1] - 1; sol[y][1]] ≈ 0 +# end +# end + # Adding a midpoint boundary constraint. # Solve using BVDAE solvers. # let From e802946e597d8f7fa7932ed328cb3acb1599e2c3 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 15 Jan 2025 16:10:55 -0500 Subject: [PATCH 23/50] test update --- src/systems/diffeqs/abstractodesystem.jl | 10 ++-- test/bvproblem.jl | 76 +++++++++++++----------- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 0e8435942c..d935a94eb8 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -860,12 +860,11 @@ SciMLBase.BVProblem{iip}(sys::AbstractODESystem, u0map, tspan, kwargs...) where {iip} ``` -Create a boundary value problem from the [`ODESystem`](@ref). The arguments `dvs` and -`ps` are used to set the order of the dependent variable and parameter vectors, -respectively. `u0map` is used to specify fixed initial values for the states. +Create a boundary value problem from the [`ODESystem`](@ref). -Every variable must have either an initial guess supplied using `guesses` or -a fixed initial value specified using `u0map`. +`u0map` is used to specify fixed initial values for the states. Every variable +must have either an initial guess supplied using `guesses` or a fixed initial +value specified using `u0map`. `constraints` are used to specify boundary conditions to the ODESystem in the form of equations. These values should specify values that state variables should @@ -1049,7 +1048,6 @@ function process_constraints(sys::ODESystem, constraints, u0, u0_idxs, tspan, ii end exprs = vcat(init_cond_exprs, exprs) - @show exprs bcs = Symbolics.build_function(exprs, sol, p, expression = Val{false}) if iip return (resid, u, p, t) -> bcs[2](resid, u, p) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 42762b8a3f..e6645c060d 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -83,14 +83,14 @@ end ### ODESystem with constraint equations, DAEs with constraints ### ################################################################## -# Test generation of boundary condition function. +# Test generation of boundary condition function using `process_constraints`. Compare solutions to manually written boundary conditions let @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 - @variables x(..) y(t) - eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y, - D(y) ~ -γ * y + δ * x(t) * y] + @variables x(..) y(..) + eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y(t), + D(y(t)) ~ -γ * y(t) + δ * x(t) * y(t)] - tspan = (0., 10.) + tspan = (0., 1.) @mtkbuild lksys = ODESystem(eqs, t) function lotkavolterra!(du, u, p, t) @@ -111,7 +111,7 @@ let [u[1][1] - 1., u[1][2] - 2.] end - u0 = [1., 2.]; p = [1.5, 1., 3., 1.] + u0 = [1., 2.]; p = [1.5, 1., 1., 3.] genbc_iip = ModelingToolkit.process_constraints(lksys, nothing, u0, [1, 2], tspan, true) genbc_oop = ModelingToolkit.process_constraints(lksys, nothing, u0, [1, 2], tspan, false) @@ -130,48 +130,54 @@ let @test sol1 ≈ sol2 # Test with a constraint. - constraints = [x(0.5) ~ 1.] + constraints = [y(0.5) ~ 2.] function bc!(resid, u, p, t) - resid[1] = u[1][2] - 2. - resid[2] = u(0.5)[1] - 1. + resid[1] = u(0.0)[1] - 1. + resid[2] = u(0.5)[2] - 2. end function bc(u, p, t) - [u[1][2] - 2., u(0.5)[1] - 1.] + [u(0.0)[1] - 1., u(0.5)[2] - 2.] end - u0 = [1., 2.] - genbc_iip = ModelingToolkit.process_constraints(lksys, constraints, u0, [2], tspan, true) - genbc_oop = ModelingToolkit.process_constraints(lksys, constraints, u0, [2], tspan, false) + u0 = [1, 1.] + genbc_iip = ModelingToolkit.process_constraints(lksys, constraints, u0, [1], tspan, true) + genbc_oop = ModelingToolkit.process_constraints(lksys, constraints, u0, [1], tspan, false) - bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1.,2.], tspan, p) - bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1.,2.], tspan, p) - bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan, parammap; guesses, constraints) + bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, u0, tspan, p) + bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, u0, tspan, p) + bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) + bvpi4 = SciMLBase.BVProblem{true, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) - sol1 = solve(bvpi1, MIRK4(), dt = 0.05) - sol2 = solve(bvpi2, MIRK4(), dt = 0.05) - @test sol1 ≈ sol2 # don't get true equality here, not sure why + @btime sol1 = solve(bvpi1, MIRK4(), dt = 0.01) + @btime sol2 = solve(bvpi2, MIRK4(), dt = 0.01) + @btime sol3 = solve(bvpi3, MIRK4(), dt = 0.01) + @btime sol4 = solve(bvpi4, MIRK4(), dt = 0.01) + @test sol1 ≈ sol2 ≈ sol3 ≈ sol4 # don't get true equality here, not sure why bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) + bvpo3 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan, parammap; guesses = [y(t) => 1.], constraints) - sol1 = solve(bvpo1, MIRK4(), dt = 0.05) - sol2 = solve(bvpo2, MIRK4(), dt = 0.05) - @test sol1 ≈ sol2 + @btime sol1 = solve(bvpo1, MIRK4(), dt = 0.05) + @btime sol2 = solve(bvpo2, MIRK4(), dt = 0.05) + @btime sol3 = solve(bvpo3, MIRK4(), dt = 0.05) + @test sol1 ≈ sol2 ≈ sol3 end -function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.05) +function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.05, atol = 1e-4) for solver in solvers - sol = solve(prob, solver(); dt) + println("Solver: $solver") + @btime sol = solve(prob, solver(), dt = dt, abstol = atol) @test SciMLBase.successful_retcode(sol.retcode) p = prob.p; t = sol.t; bc = prob.f.bc ns = length(prob.u0) if isinplace(bvp.f) resid = zeros(ns) bc!(resid, sol, p, t) - @test isapprox(zeros(ns), resid) + @test isapprox(zeros(ns), resid; atol) else - @test isapprox(zeros(ns), bc(sol, p, t)) + @test isapprox(zeros(ns), bc(sol, p, t); atol) end for (k, v) in u0map @@ -190,23 +196,21 @@ end # Simple ODESystem with BVP constraints. let - t = ModelingToolkit.t_nounits; D = ModelingToolkit.D_nounits @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 - @variables x(..) y(t) + @variables x(..) y(..) - eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y, - D(y) ~ -γ * y + δ * x(t) * y] + eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y(t), + D(y(t)) ~ -γ * y(t) + δ * x(t) * y(t)] u0map = [] - parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] tspan = (0.0, 10.0) - guesses = [x(t) => 1.0, y => 2.] + guesses = [x(t) => 4.0, y(t) => 2.] - @mtkbuild lotkavolterra = ODESystem(eqs, t) + @mtkbuild lksys = ODESystem(eqs, t) - constraints = [x(6.) ~ 1.5] - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) - test_solvers(solvers, bvp, u0map, constraints) + constraints = [x(6.) ~ 3.5, x(3.) ~ 7.] + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) + test_solvers(solvers, bvp, u0map, constraints; dt = 0.1) # Testing that more complicated constraints give correct solutions. constraints = [y(2.) + x(8.) ~ 2.] From e74e047bf333fc197ecd0b5390f22aa2651a4431 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 15 Jan 2025 17:20:43 -0500 Subject: [PATCH 24/50] fix --- test/bvproblem.jl | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index e6645c060d..a8de6af44d 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -7,7 +7,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D import ModelingToolkit: process_constraints ### Test Collocation solvers on simple problems -solvers = [MIRK4, RadauIIa5, LobattoIIIa3] +solvers = [MIRK4, RadauIIa5] daesolvers = [Ascher2, Ascher4, Ascher6] let @@ -149,32 +149,32 @@ let bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) bvpi4 = SciMLBase.BVProblem{true, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) - @btime sol1 = solve(bvpi1, MIRK4(), dt = 0.01) - @btime sol2 = solve(bvpi2, MIRK4(), dt = 0.01) - @btime sol3 = solve(bvpi3, MIRK4(), dt = 0.01) - @btime sol4 = solve(bvpi4, MIRK4(), dt = 0.01) + sol1 = @btime solve($bvpi1, MIRK4(), dt = 0.01) + sol2 = @btime solve($bvpi2, MIRK4(), dt = 0.01) + sol3 = @btime solve($bvpi3, MIRK4(), dt = 0.01) + sol4 = @btime solve($bvpi4, MIRK4(), dt = 0.01) @test sol1 ≈ sol2 ≈ sol3 ≈ sol4 # don't get true equality here, not sure why bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) - bvpo3 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan, parammap; guesses = [y(t) => 1.], constraints) + bvpo3 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) - @btime sol1 = solve(bvpo1, MIRK4(), dt = 0.05) - @btime sol2 = solve(bvpo2, MIRK4(), dt = 0.05) - @btime sol3 = solve(bvpo3, MIRK4(), dt = 0.05) + sol1 = @btime solve($bvpo1, MIRK4(), dt = 0.05) + sol2 = @btime solve($bvpo2, MIRK4(), dt = 0.05) + sol3 = @btime solve($bvpo3, MIRK4(), dt = 0.05) @test sol1 ≈ sol2 ≈ sol3 end function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.05, atol = 1e-4) for solver in solvers println("Solver: $solver") - @btime sol = solve(prob, solver(), dt = dt, abstol = atol) + sol = @btime solve($prob, $solver(), dt = $dt, abstol = $atol) @test SciMLBase.successful_retcode(sol.retcode) p = prob.p; t = sol.t; bc = prob.f.bc ns = length(prob.u0) if isinplace(bvp.f) resid = zeros(ns) - bc!(resid, sol, p, t) + bc(resid, sol, p, t) @test isapprox(zeros(ns), resid; atol) else @test isapprox(zeros(ns), bc(sol, p, t); atol) @@ -203,35 +203,35 @@ let D(y(t)) ~ -γ * y(t) + δ * x(t) * y(t)] u0map = [] - tspan = (0.0, 10.0) + tspan = (0.0, 1.0) guesses = [x(t) => 4.0, y(t) => 2.] @mtkbuild lksys = ODESystem(eqs, t) - constraints = [x(6.) ~ 3.5, x(3.) ~ 7.] + constraints = [x(.6) ~ 3.5, x(.3) ~ 7.] bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) - test_solvers(solvers, bvp, u0map, constraints; dt = 0.1) + test_solvers(solvers, bvp, u0map, constraints; dt = 0.05) # Testing that more complicated constraints give correct solutions. - constraints = [y(2.) + x(8.) ~ 2.] - bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) - test_solvers(solvers, bvp, u0map, constraints) + constraints = [y(.2) + x(.8) ~ 3., y(.3) + x(.5) ~ 5.] + bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, u0map, tspan; guesses, constraints, jac = true) + test_solvers(solvers, bvp, u0map, constraints; dt = 0.05) - constraints = [α * β - x(6.) ~ 0.5] - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) + constraints = [α * β - x(.6) ~ 0.0, y(.2) ~ 3.] + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) test_solvers(solvers, bvp, u0map, constraints) # Testing that errors are properly thrown when malformed constraints are given. @variables bad(..) constraints = [x(1.) + bad(3.) ~ 10] - @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) + @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) constraints = [x(t) + y(t) ~ 3] - @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) + @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) @parameters bad2 constraints = [bad2 + x(0.) ~ 3] - @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap; guesses, constraints) + @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) end # Cartesian pendulum from the docs. From 86d4144d910c0ba56ec5057f7a380ccca5caf879 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 17 Jan 2025 16:49:58 -0500 Subject: [PATCH 25/50] test more solvers: --- src/systems/diffeqs/abstractodesystem.jl | 2 +- test/bvproblem.jl | 333 ++++++++++++----------- 2 files changed, 172 insertions(+), 163 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index d935a94eb8..cf6e0962fd 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -969,7 +969,7 @@ function validate_constraint_syms(eq, constraintsts, constraintps, sts, ps, iv) for var in constraintsts if length(arguments(var)) > 1 error("Too many arguments for variable $var.") - elseif arguments(var) == iv + elseif isequal(arguments(var)[1], iv) var ∈ sts || error("Constraint equation $eq contains a variable $var that is not a variable of the ODESystem.") error("Constraint equation $eq contains a variable $var that does not have a specified argument. Such equations should be specified as algebraic equations to the ODESystem rather than a boundary constraints.") else diff --git a/test/bvproblem.jl b/test/bvproblem.jl index a8de6af44d..2f278e6135 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -1,183 +1,186 @@ ### TODO: update when BoundaryValueDiffEqAscher is updated to use the normal boundary condition conventions using BoundaryValueDiffEq, OrdinaryDiffEq, BoundaryValueDiffEqAscher +using BenchmarkTools using ModelingToolkit using SciMLBase using ModelingToolkit: t_nounits as t, D_nounits as D import ModelingToolkit: process_constraints ### Test Collocation solvers on simple problems -solvers = [MIRK4, RadauIIa5] +solvers = [MIRK4, RadauIIa5, LobattoIIIa3] daesolvers = [Ascher2, Ascher4, Ascher6] -let - @parameters α=7.5 β=4.0 γ=8.0 δ=5.0 - @variables x(t)=1.0 y(t)=2.0 - - eqs = [D(x) ~ α * x - β * x * y, - D(y) ~ -γ * y + δ * x * y] - - u0map = [x => 1.0, y => 2.0] - parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] - tspan = (0.0, 10.0) - - @mtkbuild lotkavolterra = ODESystem(eqs, t) - op = ODEProblem(lotkavolterra, u0map, tspan, parammap) - osol = solve(op, Vern9()) - - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( - lotkavolterra, u0map, tspan, parammap; eval_expression = true) - - for solver in solvers - sol = solve(bvp, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - @test sol.u[1] == [1.0, 2.0] - end - - # Test out of place - bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}( - lotkavolterra, u0map, tspan, parammap; eval_expression = true) - - for solver in solvers - sol = solve(bvp2, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - @test sol.u[1] == [1.0, 2.0] - end -end - -### Testing on pendulum -let - @parameters g=9.81 L=1.0 - @variables θ(t) = π / 2 θ_t(t) - - eqs = [D(θ) ~ θ_t - D(θ_t) ~ -(g / L) * sin(θ)] - - @mtkbuild pend = ODESystem(eqs, t) - - u0map = [θ => π / 2, θ_t => π / 2] - parammap = [:L => 1.0, :g => 9.81] - tspan = (0.0, 6.0) - - op = ODEProblem(pend, u0map, tspan, parammap) - osol = solve(op, Vern9()) - - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) - for solver in solvers - sol = solve(bvp, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - @test sol.u[1] == [π / 2, π / 2] - end - - # Test out-of-place - bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) - - for solver in solvers - sol = solve(bvp2, solver(), dt = 0.01) - @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) - @test sol.u[1] == [π / 2, π / 2] - end -end - -################################################################## -### ODESystem with constraint equations, DAEs with constraints ### -################################################################## - -# Test generation of boundary condition function using `process_constraints`. Compare solutions to manually written boundary conditions -let - @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 - @variables x(..) y(..) - eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y(t), - D(y(t)) ~ -γ * y(t) + δ * x(t) * y(t)] - - tspan = (0., 1.) - @mtkbuild lksys = ODESystem(eqs, t) - - function lotkavolterra!(du, u, p, t) - du[1] = p[1]*u[1] - p[2]*u[1]*u[2] - du[2] = -p[4]*u[2] + p[3]*u[1]*u[2] - end - - function lotkavolterra(u, p, t) - [p[1]*u[1] - p[2]*u[1]*u[2], -p[4]*u[2] + p[3]*u[1]*u[2]] - end - # Compare the built bc function to the actual constructed one. - function bc!(resid, u, p, t) - resid[1] = u[1][1] - 1. - resid[2] = u[1][2] - 2. - nothing - end - function bc(u, p, t) - [u[1][1] - 1., u[1][2] - 2.] - end - - u0 = [1., 2.]; p = [1.5, 1., 1., 3.] - genbc_iip = ModelingToolkit.process_constraints(lksys, nothing, u0, [1, 2], tspan, true) - genbc_oop = ModelingToolkit.process_constraints(lksys, nothing, u0, [1, 2], tspan, false) - - bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1.,2.], tspan, p) - bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1.,2.], tspan, p) - - sol1 = solve(bvpi1, MIRK4(), dt = 0.05) - sol2 = solve(bvpi2, MIRK4(), dt = 0.05) - @test sol1 ≈ sol2 - - bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) - bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) - - sol1 = solve(bvpo1, MIRK4(), dt = 0.05) - sol2 = solve(bvpo2, MIRK4(), dt = 0.05) - @test sol1 ≈ sol2 - - # Test with a constraint. - constraints = [y(0.5) ~ 2.] - - function bc!(resid, u, p, t) - resid[1] = u(0.0)[1] - 1. - resid[2] = u(0.5)[2] - 2. - end - function bc(u, p, t) - [u(0.0)[1] - 1., u(0.5)[2] - 2.] - end - - u0 = [1, 1.] - genbc_iip = ModelingToolkit.process_constraints(lksys, constraints, u0, [1], tspan, true) - genbc_oop = ModelingToolkit.process_constraints(lksys, constraints, u0, [1], tspan, false) - - bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, u0, tspan, p) - bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, u0, tspan, p) - bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) - bvpi4 = SciMLBase.BVProblem{true, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) - - sol1 = @btime solve($bvpi1, MIRK4(), dt = 0.01) - sol2 = @btime solve($bvpi2, MIRK4(), dt = 0.01) - sol3 = @btime solve($bvpi3, MIRK4(), dt = 0.01) - sol4 = @btime solve($bvpi4, MIRK4(), dt = 0.01) - @test sol1 ≈ sol2 ≈ sol3 ≈ sol4 # don't get true equality here, not sure why - - bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) - bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) - bvpo3 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) - - sol1 = @btime solve($bvpo1, MIRK4(), dt = 0.05) - sol2 = @btime solve($bvpo2, MIRK4(), dt = 0.05) - sol3 = @btime solve($bvpo3, MIRK4(), dt = 0.05) - @test sol1 ≈ sol2 ≈ sol3 -end +# let +# @parameters α=7.5 β=4.0 γ=8.0 δ=5.0 +# @variables x(t)=1.0 y(t)=2.0 +# +# eqs = [D(x) ~ α * x - β * x * y, +# D(y) ~ -γ * y + δ * x * y] +# +# u0map = [x => 1.0, y => 2.0] +# parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] +# tspan = (0.0, 10.0) +# +# @mtkbuild lotkavolterra = ODESystem(eqs, t) +# op = ODEProblem(lotkavolterra, u0map, tspan, parammap) +# osol = solve(op, Vern9()) +# +# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( +# lotkavolterra, u0map, tspan, parammap; eval_expression = true) +# +# for solver in solvers +# sol = solve(bvp, solver(), dt = 0.01) +# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) +# @test sol.u[1] == [1.0, 2.0] +# end +# +# # Test out of place +# bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}( +# lotkavolterra, u0map, tspan, parammap; eval_expression = true) +# +# for solver in solvers +# sol = solve(bvp2, solver(), dt = 0.01) +# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) +# @test sol.u[1] == [1.0, 2.0] +# end +# end +# +# ### Testing on pendulum +# let +# @parameters g=9.81 L=1.0 +# @variables θ(t) = π / 2 θ_t(t) +# +# eqs = [D(θ) ~ θ_t +# D(θ_t) ~ -(g / L) * sin(θ)] +# +# @mtkbuild pend = ODESystem(eqs, t) +# +# u0map = [θ => π / 2, θ_t => π / 2] +# parammap = [:L => 1.0, :g => 9.81] +# tspan = (0.0, 6.0) +# +# op = ODEProblem(pend, u0map, tspan, parammap) +# osol = solve(op, Vern9()) +# +# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) +# for solver in solvers +# sol = solve(bvp, solver(), dt = 0.01) +# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) +# @test sol.u[1] == [π / 2, π / 2] +# end +# +# # Test out-of-place +# bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) +# +# for solver in solvers +# sol = solve(bvp2, solver(), dt = 0.01) +# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) +# @test sol.u[1] == [π / 2, π / 2] +# end +# end +# +# ################################################################## +# ### ODESystem with constraint equations, DAEs with constraints ### +# ################################################################## +# +# # Test generation of boundary condition function using `process_constraints`. Compare solutions to manually written boundary conditions +# let +# @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 +# @variables x(..) y(..) +# eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y(t), +# D(y(t)) ~ -γ * y(t) + δ * x(t) * y(t)] +# +# tspan = (0., 1.) +# @mtkbuild lksys = ODESystem(eqs, t) +# +# function lotkavolterra!(du, u, p, t) +# du[1] = p[1]*u[1] - p[2]*u[1]*u[2] +# du[2] = -p[4]*u[2] + p[3]*u[1]*u[2] +# end +# +# function lotkavolterra(u, p, t) +# [p[1]*u[1] - p[2]*u[1]*u[2], -p[4]*u[2] + p[3]*u[1]*u[2]] +# end +# # Compare the built bc function to the actual constructed one. +# function bc!(resid, u, p, t) +# resid[1] = u[1][1] - 1. +# resid[2] = u[1][2] - 2. +# nothing +# end +# function bc(u, p, t) +# [u[1][1] - 1., u[1][2] - 2.] +# end +# +# u0 = [1., 2.]; p = [1.5, 1., 1., 3.] +# genbc_iip = ModelingToolkit.process_constraints(lksys, nothing, u0, [1, 2], tspan, true) +# genbc_oop = ModelingToolkit.process_constraints(lksys, nothing, u0, [1, 2], tspan, false) +# +# bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1.,2.], tspan, p) +# bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1.,2.], tspan, p) +# +# sol1 = solve(bvpi1, MIRK4(), dt = 0.05) +# sol2 = solve(bvpi2, MIRK4(), dt = 0.05) +# @test sol1 ≈ sol2 +# +# bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) +# bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) +# +# sol1 = solve(bvpo1, MIRK4(), dt = 0.05) +# sol2 = solve(bvpo2, MIRK4(), dt = 0.05) +# @test sol1 ≈ sol2 +# +# # Test with a constraint. +# constraints = [y(0.5) ~ 2.] +# +# function bc!(resid, u, p, t) +# resid[1] = u(0.0)[1] - 1. +# resid[2] = u(0.5)[2] - 2. +# end +# function bc(u, p, t) +# [u(0.0)[1] - 1., u(0.5)[2] - 2.] +# end +# +# u0 = [1, 1.] +# genbc_iip = ModelingToolkit.process_constraints(lksys, constraints, u0, [1], tspan, true) +# genbc_oop = ModelingToolkit.process_constraints(lksys, constraints, u0, [1], tspan, false) +# +# bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, u0, tspan, p) +# bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, u0, tspan, p) +# bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) +# bvpi4 = SciMLBase.BVProblem{true, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) +# +# sol1 = @btime solve($bvpi1, MIRK4(), dt = 0.01) +# sol2 = @btime solve($bvpi2, MIRK4(), dt = 0.01) +# sol3 = @btime solve($bvpi3, MIRK4(), dt = 0.01) +# sol4 = @btime solve($bvpi4, MIRK4(), dt = 0.01) +# @test sol1 ≈ sol2 ≈ sol3 ≈ sol4 # don't get true equality here, not sure why +# +# bvpo1 = BVProblem(lotkavolterra, bc, u0, tspan, p) +# bvpo2 = BVProblem(lotkavolterra, genbc_oop, u0, tspan, p) +# bvpo3 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) +# +# sol1 = @btime solve($bvpo1, MIRK4(), dt = 0.05) +# sol2 = @btime solve($bvpo2, MIRK4(), dt = 0.05) +# sol3 = @btime solve($bvpo3, MIRK4(), dt = 0.05) +# @test sol1 ≈ sol2 ≈ sol3 +# end -function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.05, atol = 1e-4) +function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.05, atol = 1e-3) for solver in solvers println("Solver: $solver") sol = @btime solve($prob, $solver(), dt = $dt, abstol = $atol) @test SciMLBase.successful_retcode(sol.retcode) p = prob.p; t = sol.t; bc = prob.f.bc ns = length(prob.u0) - if isinplace(bvp.f) + if isinplace(prob.f) resid = zeros(ns) bc(resid, sol, p, t) @test isapprox(zeros(ns), resid; atol) + @show resid else @test isapprox(zeros(ns), bc(sol, p, t); atol) + @show bc(sol, p, t) end for (k, v) in u0map @@ -194,6 +197,12 @@ function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0. end end +solvers = [RadauIIa3, RadauIIa5, RadauIIa7, + LobattoIIIa2, LobattoIIIa4, LobattoIIIa5, + LobattoIIIb2, LobattoIIIb3, LobattoIIIb4, LobattoIIIb5, + LobattoIIIc2, LobattoIIIc3, LobattoIIIc4, LobattoIIIc5] +weird = [MIRK2, MIRK5, RadauIIa2] +daesolvers = [] # Simple ODESystem with BVP constraints. let @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 @@ -213,8 +222,8 @@ let test_solvers(solvers, bvp, u0map, constraints; dt = 0.05) # Testing that more complicated constraints give correct solutions. - constraints = [y(.2) + x(.8) ~ 3., y(.3) + x(.5) ~ 5.] - bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, u0map, tspan; guesses, constraints, jac = true) + constraints = [y(.2) + x(.8) ~ 3., y(.3) ~ 2.] + bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, u0map, tspan; guesses, constraints) test_solvers(solvers, bvp, u0map, constraints; dt = 0.05) constraints = [α * β - x(.6) ~ 0.0, y(.2) ~ 3.] @@ -224,14 +233,14 @@ let # Testing that errors are properly thrown when malformed constraints are given. @variables bad(..) constraints = [x(1.) + bad(3.) ~ 10] - @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) + @test_throws ErrorException bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) constraints = [x(t) + y(t) ~ 3] - @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) + @test_throws ErrorException bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) @parameters bad2 constraints = [bad2 + x(0.) ~ 3] - @test_throws Exception bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) + @test_throws ErrorException bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) end # Cartesian pendulum from the docs. From ec386fe041595284c9ab44a0f54fe9a6c232155e Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 27 Jan 2025 23:02:07 -0500 Subject: [PATCH 26/50] Refactor constraints --- src/ModelingToolkit.jl | 8 +- src/systems/abstractsystem.jl | 1 + src/systems/diffeqs/abstractodesystem.jl | 121 ++---- src/systems/diffeqs/bvpsystem.jl | 217 ++++++++++ src/systems/diffeqs/odesystem.jl | 86 +++- .../optimization/constraints_system.jl | 5 + test/bvproblem.jl | 373 +++++++++--------- test/odesystem.jl | 30 ++ 8 files changed, 568 insertions(+), 273 deletions(-) create mode 100644 src/systems/diffeqs/bvpsystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 10aba4d8a9..2d53c61401 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -150,6 +150,10 @@ include("systems/imperative_affect.jl") include("systems/callbacks.jl") include("systems/problem_utils.jl") +include("systems/optimization/constraints_system.jl") +include("systems/optimization/optimizationsystem.jl") +include("systems/optimization/modelingtoolkitize.jl") + include("systems/nonlinear/nonlinearsystem.jl") include("systems/nonlinear/homotopy_continuation.jl") include("systems/diffeqs/odesystem.jl") @@ -165,10 +169,6 @@ include("systems/discrete_system/discrete_system.jl") include("systems/jumps/jumpsystem.jl") -include("systems/optimization/constraints_system.jl") -include("systems/optimization/optimizationsystem.jl") -include("systems/optimization/modelingtoolkitize.jl") - include("systems/pde/pdesystem.jl") include("systems/sparsematrixclil.jl") diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 168260ae69..7c0fe0285d 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -983,6 +983,7 @@ for prop in [:eqs :structure :op :constraints + :constraintsystem :controls :loss :bcs diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index cf6e0962fd..d0d6ed7937 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -827,6 +827,12 @@ function DiffEqBase.ODEProblem{iip, specialize}(sys::AbstractODESystem, u0map = if !iscomplete(sys) error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEProblem`") end + + if !isnothing(get_constraintsystem(sys)) + error("An ODESystem with constraints cannot be used to construct a regular ODEProblem. + Consider a BVProblem instead.") + end + f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, check_length, warn_initialize_determined, eval_expression, eval_module, kwargs...) @@ -866,18 +872,23 @@ Create a boundary value problem from the [`ODESystem`](@ref). must have either an initial guess supplied using `guesses` or a fixed initial value specified using `u0map`. -`constraints` are used to specify boundary conditions to the ODESystem in the -form of equations. These values should specify values that state variables should +Boundary value conditions are supplied to ODESystems +in the form of a ConstraintsSystem. These equations +should specify values that state variables should take at specific points, as in `x(0.5) ~ 1`). More general constraints that should hold over the entire solution, such as `x(t)^2 + y(t)^2`, should be -specified as one of the equations used to build the `ODESystem`. Below is an example. +specified as one of the equations used to build the `ODESystem`. + +If an ODESystem without `constraints` is specified, it will be treated as an initial value problem. ```julia - @parameters g + @parameters g t_c = 0.5 @variables x(..) y(t) [state_priority = 10] λ(t) eqs = [D(D(x(t))) ~ λ * x(t) D(D(y)) ~ λ * y - g x(t)^2 + y^2 ~ 1] + cstr = [x(0.5) ~ 1] + @named cstrs = ConstraintsSystem(cstr, t) @mtkbuild pend = ODESystem(eqs, t) tspan = (0.0, 1.5) @@ -889,9 +900,7 @@ specified as one of the equations used to build the `ODESystem`. Below is an exa bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) ``` -If no `constraints` are specified, the problem will be treated as an initial value problem. - -If the `ODESystem` has algebraic equations like `x(t)^2 + y(t)^2`, the resulting +If the `ODESystem` has algebraic equations, like `x(t)^2 + y(t)^2`, the resulting `BVProblem` must be solved using BVDAE solvers, such as Ascher. """ function SciMLBase.BVProblem(sys::AbstractODESystem, args...; kwargs...) @@ -916,7 +925,7 @@ end function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], tspan = get_tspan(sys), parammap = DiffEqBase.NullParameters(); - constraints = nothing, guesses = Dict(), + guesses = Dict(), version = nothing, tgrad = false, callback = nothing, check_length = true, @@ -930,21 +939,14 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] end !isnothing(callback) && error("BVP solvers do not support callbacks.") - has_alg_eqs(sys) && error("The BVProblem currently does not support ODESystems with algebraic equations.") # Remove this when the BVDAE solvers get updated, the codegen should work when it does. + has_alg_eqs(sys) && error("The BVProblem constructor currently does not support ODESystems with algebraic equations.") # Remove this when the BVDAE solvers get updated, the codegen should work when it does. - constraintsts = nothing - constraintps = nothing sts = unknowns(sys) ps = parameters(sys) - # Constraint validation if !isnothing(constraints) - constraints isa Equation || - constraints isa Vector{Equation} || - error("Constraints must be specified as an equation or a vector of equations.") - (length(constraints) + length(u0map) > length(sts)) && - error("The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) cannot exceed the total number of states.") + @warn "The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) exceeds the total number of states. The BVP solvers will default to doing a nonlinear least-squares optimization." end # ODESystems without algebraic equations should use both fixed values + guesses @@ -957,48 +959,25 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) u0_idxs = has_alg_eqs(sys) ? collect(1:length(sts)) : [stidxmap[k] for (k,v) in u0map] - bc = process_constraints(sys, constraints, u0, u0_idxs, tspan, iip) - + bc = generate_function_bc(sys, u0, u0_idxs, tspan, iip) return BVProblem{iip}(f, bc, u0, tspan, p; kwargs...) end get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") -# Validate that all the variables in the BVP constraints are well-formed states or parameters. -function validate_constraint_syms(eq, constraintsts, constraintps, sts, ps, iv) - for var in constraintsts - if length(arguments(var)) > 1 - error("Too many arguments for variable $var.") - elseif isequal(arguments(var)[1], iv) - var ∈ sts || error("Constraint equation $eq contains a variable $var that is not a variable of the ODESystem.") - error("Constraint equation $eq contains a variable $var that does not have a specified argument. Such equations should be specified as algebraic equations to the ODESystem rather than a boundary constraints.") - else - operation(var)(iv) ∈ sts || error("Constraint equation $eq contains a variable $(operation(var)) that is not a variable of the ODESystem.") - end - end - - for var in constraintps - if !iscall(var) - var ∈ ps || error("Constraint equation $eq contains a parameter $var that is not a parameter of the ODESystem.") - else - length(arguments(var)) > 1 && error("Too many arguments for parameter $var.") - operation(var) ∈ ps || error("Constraint equations contain a parameter $var that is not a parameter of the ODESystem.") - end - end -end - """ - process_constraints(sys, constraints, u0, tspan, iip) + generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan, iip) - Given an ODESystem with some constraints, generate the boundary condition function. + Given an ODESystem with constraints, generate the boundary condition function to pass to boundary value problem solvers. """ -function process_constraints(sys::ODESystem, constraints, u0, u0_idxs, tspan, iip) - +function generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan, iip) iv = get_iv(sys) sts = get_unknowns(sys) ps = get_ps(sys) np = length(ps) ns = length(sts) + conssys = get_constraintsystem(sys) + cons = constraints(conssys) stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) @@ -1006,48 +985,34 @@ function process_constraints(sys::ODESystem, constraints, u0, u0_idxs, tspan, ii @variables sol(..)[1:ns] p[1:np] exprs = Any[] - constraintsts = OrderedSet() - constraintps = OrderedSet() + for st in get_unknowns(cons) + x = operation(st) + t = first(arguments(st)) + idx = stidxmap[x(iv)] - !isnothing(constraints) && for cons in constraints - collect_vars!(constraintsts, constraintps, cons, iv) - validate_constraint_syms(cons, constraintsts, constraintps, Set(sts), Set(ps), iv) - expr = cons.rhs - cons.lhs - - for st in constraintsts - x = operation(st) - t = arguments(st)[1] - idx = stidxmap[x(iv)] - - expr = Symbolics.substitute(expr, Dict(x(t) => sol(t)[idx])) - end + cons = Symbolics.substitute(cons, Dict(x(t) => sol(t)[idx])) + end - for var in constraintps - if iscall(var) - x = operation(var) - t = arguments(var)[1] - idx = pidxmap[x] + for var in get_parameters(cons) + if iscall(var) + x = operation(var) + t = arguments(var)[1] + idx = pidxmap[x] - expr = Symbolics.substitute(expr, Dict(x(t) => p[idx])) - else - idx = pidxmap[var] - expr = Symbolics.substitute(expr, Dict(var => p[idx])) - end + cons = Symbolics.substitute(cons, Dict(x(t) => p[idx])) + else + idx = pidxmap[var] + cons = Symbolics.substitute(cons, Dict(var => p[idx])) end - - empty!(constraintsts) - empty!(constraintps) - push!(exprs, expr) end - init_cond_exprs = Any[] - + init_conds = Any[] for i in u0_idxs expr = sol(tspan[1])[i] - u0[i] - push!(init_cond_exprs, expr) + push!(init_conds, expr) end - exprs = vcat(init_cond_exprs, exprs) + exprs = vcat(init_conds, cons) bcs = Symbolics.build_function(exprs, sol, p, expression = Val{false}) if iip return (resid, u, p, t) -> bcs[2](resid, u, p) diff --git a/src/systems/diffeqs/bvpsystem.jl b/src/systems/diffeqs/bvpsystem.jl new file mode 100644 index 0000000000..ae3029fa14 --- /dev/null +++ b/src/systems/diffeqs/bvpsystem.jl @@ -0,0 +1,217 @@ +""" +$(TYPEDEF) + +A system of ordinary differential equations. + +# Fields +$(FIELDS) + +# Example + +```julia +using ModelingToolkit +using ModelingToolkit: t_nounits as t, D_nounits as D + +@parameters σ ρ β +@variables x(t) y(t) z(t) + +eqs = [D(x) ~ σ*(y-x), + D(y) ~ x*(ρ-z)-y, + D(z) ~ x*y - β*z] + +@named de = ODESystem(eqs,t,[x,y,z],[σ,ρ,β],tspan=(0, 1000.0)) +``` +""" +struct ODESystem <: AbstractODESystem + """ + A tag for the system. If two systems have the same tag, then they are + structurally identical. + """ + tag::UInt + """The ODEs defining the system.""" + eqs::Vector{Equation} + """Independent variable.""" + iv::BasicSymbolic{Real} + """ + Dependent (unknown) variables. Must not contain the independent variable. + + N.B.: If `torn_matching !== nothing`, this includes all variables. Actual + ODE unknowns are determined by the `SelectedState()` entries in `torn_matching`. + """ + unknowns::Vector + """Parameter variables. Must not contain the independent variable.""" + ps::Vector + """Time span.""" + tspan::Union{NTuple{2, Any}, Nothing} + """Array variables.""" + var_to_name::Any + """Control parameters (some subset of `ps`).""" + ctrls::Vector + """Observed variables.""" + observed::Vector{Equation} + """ + Time-derivative matrix. Note: this field will not be defined until + [`calculate_tgrad`](@ref) is called on the system. + """ + tgrad::RefValue{Vector{Num}} + """ + Jacobian matrix. Note: this field will not be defined until + [`calculate_jacobian`](@ref) is called on the system. + """ + jac::RefValue{Any} + """ + Control Jacobian matrix. Note: this field will not be defined until + [`calculate_control_jacobian`](@ref) is called on the system. + """ + ctrl_jac::RefValue{Any} + """ + Note: this field will not be defined until + [`generate_factorized_W`](@ref) is called on the system. + """ + Wfact::RefValue{Matrix{Num}} + """ + Note: this field will not be defined until + [`generate_factorized_W`](@ref) is called on the system. + """ + Wfact_t::RefValue{Matrix{Num}} + """ + The name of the system. + """ + name::Symbol + """ + A description of the system. + """ + description::String + """ + The internal systems. These are required to have unique names. + """ + systems::Vector{ODESystem} + """ + The default values to use when initial conditions and/or + parameters are not supplied in `ODEProblem`. + """ + defaults::Dict + """ + The guesses to use as the initial conditions for the + initialization system. + """ + guesses::Dict + """ + Tearing result specifying how to solve the system. + """ + torn_matching::Union{Matching, Nothing} + """ + The system for performing the initialization. + """ + initializesystem::Union{Nothing, NonlinearSystem} + """ + Extra equations to be enforced during the initialization sequence. + """ + initialization_eqs::Vector{Equation} + """ + The schedule for the code generation process. + """ + schedule::Any + """ + Type of the system. + """ + connector_type::Any + """ + Inject assignment statements before the evaluation of the RHS function. + """ + preface::Any + """ + A `Vector{SymbolicContinuousCallback}` that model events. + The integrator will use root finding to guarantee that it steps at each zero crossing. + """ + continuous_events::Vector{SymbolicContinuousCallback} + """ + A `Vector{SymbolicDiscreteCallback}` that models events. Symbolic + analog to `SciMLBase.DiscreteCallback` that executes an affect when a given condition is + true at the end of an integration step. + """ + discrete_events::Vector{SymbolicDiscreteCallback} + """ + Topologically sorted parameter dependency equations, where all symbols are parameters and + the LHS is a single parameter. + """ + parameter_dependencies::Vector{Equation} + """ + Metadata for the system, to be used by downstream packages. + """ + metadata::Any + """ + Metadata for MTK GUI. + """ + gui_metadata::Union{Nothing, GUIMetadata} + """ + A boolean indicating if the given `ODESystem` represents a system of DDEs. + """ + is_dde::Bool + """ + A list of points to provide to the solver as tstops. Uses the same syntax as discrete + events. + """ + tstops::Vector{Any} + """ + Cache for intermediate tearing state. + """ + tearing_state::Any + """ + Substitutions generated by tearing. + """ + substitutions::Any + """ + If a model `sys` is complete, then `sys.x` no longer performs namespacing. + """ + complete::Bool + """ + Cached data for fast symbolic indexing. + """ + index_cache::Union{Nothing, IndexCache} + """ + A list of discrete subsystems. + """ + discrete_subsystems::Any + """ + A list of actual unknowns needed to be solved by solvers. + """ + solved_unknowns::Union{Nothing, Vector{Any}} + """ + A vector of vectors of indices for the split parameters. + """ + split_idxs::Union{Nothing, Vector{Vector{Int}}} + """ + The hierarchical parent system before simplification. + """ + parent::Any + + function ODESystem(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, + jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, + torn_matching, initializesystem, initialization_eqs, schedule, + connector_type, preface, cevents, + devents, parameter_dependencies, + metadata = nothing, gui_metadata = nothing, is_dde = false, + tstops = [], tearing_state = nothing, + substitutions = nothing, complete = false, index_cache = nothing, + discrete_subsystems = nothing, solved_unknowns = nothing, + split_idxs = nothing, parent = nothing; checks::Union{Bool, Int} = true) + if checks == true || (checks & CheckComponents) > 0 + check_independent_variables([iv]) + check_variables(dvs, iv) + check_parameters(ps, iv) + check_equations(deqs, iv) + check_equations(equations(cevents), iv) + end + if checks == true || (checks & CheckUnits) > 0 + u = __get_unit_type(dvs, ps, iv) + check_units(u, deqs) + end + new(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac, + ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, torn_matching, + initializesystem, initialization_eqs, schedule, connector_type, preface, + cevents, devents, parameter_dependencies, metadata, + gui_metadata, is_dde, tstops, tearing_state, substitutions, complete, index_cache, + discrete_subsystems, solved_unknowns, split_idxs, parent) + end +end diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 61f16fd926..6ca6cdf838 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -49,6 +49,8 @@ struct ODESystem <: AbstractODESystem ctrls::Vector """Observed variables.""" observed::Vector{Equation} + """System of constraints that must be satisfied by the solution to the system.""" + constraintsystem::Union{Nothing, ConstraintsSystem} """ Time-derivative matrix. Note: this field will not be defined until [`calculate_tgrad`](@ref) is called on the system. @@ -186,7 +188,7 @@ struct ODESystem <: AbstractODESystem """ parent::Any - function ODESystem(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, + function ODESystem(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, constraints, tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, torn_matching, initializesystem, initialization_eqs, schedule, connector_type, preface, cevents, @@ -207,7 +209,7 @@ struct ODESystem <: AbstractODESystem u = __get_unit_type(dvs, ps, iv) check_units(u, deqs) end - new(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac, + new(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, constraints, tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, torn_matching, initializesystem, initialization_eqs, schedule, connector_type, preface, cevents, devents, parameter_dependencies, metadata, @@ -219,6 +221,7 @@ end function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; controls = Num[], observed = Equation[], + constraints = Equation[], systems = ODESystem[], tspan = nothing, name = nothing, @@ -283,11 +286,26 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; cont_callbacks = SymbolicContinuousCallbacks(continuous_events) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events) + constraintsys = nothing + if !isempty(constraints) + constraintsys = process_constraint_system(constraints, dvs′, ps′, iv, systems) + dvset = Set(dvs′) + pset = Set(ps′) + for st in get_unknowns(constraintsys) + iscall(st) ? + !in(operation(st)(iv), dvset) && push!(dvs′, st) : + !in(st, dvset) && push!(dvs′, st) + end + for p in parameters(constraintsys) + !in(p, pset) && push!(ps′, p) + end + end + if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end ODESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - deqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, tgrad, jac, + deqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, constraintsys, tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, nothing, initializesystem, initialization_eqs, schedule, connector_type, preface, cont_callbacks, @@ -295,7 +313,7 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; metadata, gui_metadata, is_dde, tstops, checks = checks) end -function ODESystem(eqs, iv; kwargs...) +function ODESystem(eqs, iv; constraints = Equation[], kwargs...) eqs = collect(eqs) # NOTE: this assumes that the order of algebraic equations doesn't matter diffvars = OrderedSet() @@ -358,9 +376,10 @@ function ODESystem(eqs, iv; kwargs...) end end algevars = setdiff(allunknowns, diffvars) + # the orders here are very important! return ODESystem(Equation[diffeq; algeeq; compressed_eqs], iv, - collect(Iterators.flatten((diffvars, algevars))), collect(new_ps); kwargs...) + collect(Iterators.flatten((diffvars, algevars))), collect(new_ps); constraints, kwargs...) end # NOTE: equality does not check cached Jacobian @@ -770,3 +789,60 @@ function Base.show(io::IO, mime::MIME"text/plain", sys::ODESystem; hint = true, return nothing end + +# Validate that all the variables in the BVP constraints are well-formed states or parameters. +# - Any callable with multiple arguments will error. +# - Callable/delay variables (e.g. of the form x(0.6) should be unknowns of the system (and have one arg, etc.) +# - Callable/delay parameters should be parameters of the system (and have one arg, etc.) +function validate_constraint_syms(constraintsts, constraintps, sts, ps, iv) + for var in constraintsts + if !iscall(var) + occursin(iv, var) && var ∈ sts || throw(ArgumentError("Time-dependent variable $var is not an unknown of the system.")) + elseif length(arguments(var)) > 1 + throw(ArgumentError("Too many arguments for variable $var.")) + elseif length(arguments(var)) == 1 + arg = first(arguments(var)) + operation(var)(iv) ∈ sts || throw(ArgumentError("Variable $var is not a variable of the ODESystem. Called variables must be variables of the ODESystem.")) + + isequal(arg, iv) || + isparameter(arg) || + arg isa Integer || + arg isa AbstractFloat || + throw(ArgumentError("Invalid argument specified for variable $var. The argument of the variable should be either $iv, a parameter, or a value specifying the time that the constraint holds.")) + else + var ∈ sts && @warn "Variable $var has no argument. It will be interpreted as $var($iv), and the constraint will apply to the entire interval." + end + end + + for var in constraintps + !iscall(var) && continue + + if length(arguments(var)) > 1 + throw(ArgumentError("Too many arguments for parameter $var in equation $eq.")) + elseif length(arguments(var)) == 1 + arg = first(arguments(var)) + operation(var) ∈ ps || throw(ArgumentError("Parameter $var is not a parameter of the ODESystem. Called parameters must be parameters of the ODESystem.")) + + isequal(arg, iv) || + isparameter(arg) || + arg isa Integer || + arg isa AbstractFloat || + throw(ArgumentError("Invalid argument specified for callable parameter $var. The argument of the parameter should be either $iv, a parameter, or a value specifying the time that the constraint holds.")) + end + end +end + +function process_constraint_system(constraints::Vector{Equation}, sts, ps, iv, subsys::Vector{ODESystem}; name = :cons) + isempty(constraints) && return nothing + + constraintsts = OrderedSet() + constraintps = OrderedSet() + + for cons in constraints + syms = collect_vars!(constraintsts, constraintps, cons, iv) + end + validate_constraint_syms(constraintsts, constraintps, Set(sts), Set(ps), iv) + + constraint_subsys = get_constraintsystem.(subsys) + ConstraintsSystem(constraints, collect(constraintsts), collect(constraintps); systems = constraint_subsys, name) +end diff --git a/src/systems/optimization/constraints_system.jl b/src/systems/optimization/constraints_system.jl index 03225fc900..61e190e672 100644 --- a/src/systems/optimization/constraints_system.jl +++ b/src/systems/optimization/constraints_system.jl @@ -89,6 +89,10 @@ struct ConstraintsSystem <: AbstractTimeIndependentSystem tearing_state = nothing, substitutions = nothing, complete = false, index_cache = nothing; checks::Union{Bool, Int} = true) + + ##if checks == true || (checks & CheckComponents) > 0 + ## check_variables(unknowns, constraints) + ##end if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(unknowns, ps) check_units(u, constraints) @@ -253,3 +257,4 @@ function get_cmap(sys::ConstraintsSystem, exprs = nothing) cmap = map(x -> x ~ getdefault(x), cs) return cmap, cs end + diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 2f278e6135..16c12d1be6 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -8,163 +8,163 @@ using ModelingToolkit: t_nounits as t, D_nounits as D import ModelingToolkit: process_constraints ### Test Collocation solvers on simple problems -solvers = [MIRK4, RadauIIa5, LobattoIIIa3] +solvers = [MIRK4] daesolvers = [Ascher2, Ascher4, Ascher6] -# let -# @parameters α=7.5 β=4.0 γ=8.0 δ=5.0 -# @variables x(t)=1.0 y(t)=2.0 -# -# eqs = [D(x) ~ α * x - β * x * y, -# D(y) ~ -γ * y + δ * x * y] -# -# u0map = [x => 1.0, y => 2.0] -# parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] -# tspan = (0.0, 10.0) -# -# @mtkbuild lotkavolterra = ODESystem(eqs, t) -# op = ODEProblem(lotkavolterra, u0map, tspan, parammap) -# osol = solve(op, Vern9()) -# -# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( -# lotkavolterra, u0map, tspan, parammap; eval_expression = true) -# -# for solver in solvers -# sol = solve(bvp, solver(), dt = 0.01) -# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) -# @test sol.u[1] == [1.0, 2.0] -# end -# -# # Test out of place -# bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}( -# lotkavolterra, u0map, tspan, parammap; eval_expression = true) -# -# for solver in solvers -# sol = solve(bvp2, solver(), dt = 0.01) -# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) -# @test sol.u[1] == [1.0, 2.0] -# end -# end -# -# ### Testing on pendulum -# let -# @parameters g=9.81 L=1.0 -# @variables θ(t) = π / 2 θ_t(t) -# -# eqs = [D(θ) ~ θ_t -# D(θ_t) ~ -(g / L) * sin(θ)] -# -# @mtkbuild pend = ODESystem(eqs, t) -# -# u0map = [θ => π / 2, θ_t => π / 2] -# parammap = [:L => 1.0, :g => 9.81] -# tspan = (0.0, 6.0) -# -# op = ODEProblem(pend, u0map, tspan, parammap) -# osol = solve(op, Vern9()) -# -# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) -# for solver in solvers -# sol = solve(bvp, solver(), dt = 0.01) -# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) -# @test sol.u[1] == [π / 2, π / 2] -# end -# -# # Test out-of-place -# bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) -# -# for solver in solvers -# sol = solve(bvp2, solver(), dt = 0.01) -# @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) -# @test sol.u[1] == [π / 2, π / 2] -# end -# end -# -# ################################################################## -# ### ODESystem with constraint equations, DAEs with constraints ### -# ################################################################## -# -# # Test generation of boundary condition function using `process_constraints`. Compare solutions to manually written boundary conditions -# let -# @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 -# @variables x(..) y(..) -# eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y(t), -# D(y(t)) ~ -γ * y(t) + δ * x(t) * y(t)] -# -# tspan = (0., 1.) -# @mtkbuild lksys = ODESystem(eqs, t) -# -# function lotkavolterra!(du, u, p, t) -# du[1] = p[1]*u[1] - p[2]*u[1]*u[2] -# du[2] = -p[4]*u[2] + p[3]*u[1]*u[2] -# end -# -# function lotkavolterra(u, p, t) -# [p[1]*u[1] - p[2]*u[1]*u[2], -p[4]*u[2] + p[3]*u[1]*u[2]] -# end -# # Compare the built bc function to the actual constructed one. -# function bc!(resid, u, p, t) -# resid[1] = u[1][1] - 1. -# resid[2] = u[1][2] - 2. -# nothing -# end -# function bc(u, p, t) -# [u[1][1] - 1., u[1][2] - 2.] -# end -# -# u0 = [1., 2.]; p = [1.5, 1., 1., 3.] -# genbc_iip = ModelingToolkit.process_constraints(lksys, nothing, u0, [1, 2], tspan, true) -# genbc_oop = ModelingToolkit.process_constraints(lksys, nothing, u0, [1, 2], tspan, false) -# -# bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1.,2.], tspan, p) -# bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1.,2.], tspan, p) -# -# sol1 = solve(bvpi1, MIRK4(), dt = 0.05) -# sol2 = solve(bvpi2, MIRK4(), dt = 0.05) -# @test sol1 ≈ sol2 -# -# bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) -# bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) -# -# sol1 = solve(bvpo1, MIRK4(), dt = 0.05) -# sol2 = solve(bvpo2, MIRK4(), dt = 0.05) -# @test sol1 ≈ sol2 -# -# # Test with a constraint. -# constraints = [y(0.5) ~ 2.] -# -# function bc!(resid, u, p, t) -# resid[1] = u(0.0)[1] - 1. -# resid[2] = u(0.5)[2] - 2. -# end -# function bc(u, p, t) -# [u(0.0)[1] - 1., u(0.5)[2] - 2.] -# end -# -# u0 = [1, 1.] -# genbc_iip = ModelingToolkit.process_constraints(lksys, constraints, u0, [1], tspan, true) -# genbc_oop = ModelingToolkit.process_constraints(lksys, constraints, u0, [1], tspan, false) -# -# bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, u0, tspan, p) -# bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, u0, tspan, p) -# bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) -# bvpi4 = SciMLBase.BVProblem{true, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) -# -# sol1 = @btime solve($bvpi1, MIRK4(), dt = 0.01) -# sol2 = @btime solve($bvpi2, MIRK4(), dt = 0.01) -# sol3 = @btime solve($bvpi3, MIRK4(), dt = 0.01) -# sol4 = @btime solve($bvpi4, MIRK4(), dt = 0.01) -# @test sol1 ≈ sol2 ≈ sol3 ≈ sol4 # don't get true equality here, not sure why -# -# bvpo1 = BVProblem(lotkavolterra, bc, u0, tspan, p) -# bvpo2 = BVProblem(lotkavolterra, genbc_oop, u0, tspan, p) -# bvpo3 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) -# -# sol1 = @btime solve($bvpo1, MIRK4(), dt = 0.05) -# sol2 = @btime solve($bvpo2, MIRK4(), dt = 0.05) -# sol3 = @btime solve($bvpo3, MIRK4(), dt = 0.05) -# @test sol1 ≈ sol2 ≈ sol3 -# end +let + @parameters α=7.5 β=4.0 γ=8.0 δ=5.0 + @variables x(t)=1.0 y(t)=2.0 + + eqs = [D(x) ~ α * x - β * x * y, + D(y) ~ -γ * y + δ * x * y] + + u0map = [x => 1.0, y => 2.0] + parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] + tspan = (0.0, 10.0) + + @mtkbuild lotkavolterra = ODESystem(eqs, t) + op = ODEProblem(lotkavolterra, u0map, tspan, parammap) + osol = solve(op, Vern9()) + + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( + lotkavolterra, u0map, tspan, parammap; eval_expression = true) + + for solver in solvers + sol = solve(bvp, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + @test sol.u[1] == [1.0, 2.0] + end + + # Test out of place + bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}( + lotkavolterra, u0map, tspan, parammap; eval_expression = true) + + for solver in solvers + sol = solve(bvp2, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + @test sol.u[1] == [1.0, 2.0] + end +end + +### Testing on pendulum +let + @parameters g=9.81 L=1.0 + @variables θ(t) = π / 2 θ_t(t) + + eqs = [D(θ) ~ θ_t + D(θ_t) ~ -(g / L) * sin(θ)] + + @mtkbuild pend = ODESystem(eqs, t) + + u0map = [θ => π / 2, θ_t => π / 2] + parammap = [:L => 1.0, :g => 9.81] + tspan = (0.0, 6.0) + + op = ODEProblem(pend, u0map, tspan, parammap) + osol = solve(op, Vern9()) + + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) + for solver in solvers + sol = solve(bvp, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + @test sol.u[1] == [π / 2, π / 2] + end + + # Test out-of-place + bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) + + for solver in solvers + sol = solve(bvp2, solver(), dt = 0.01) + @test isapprox(sol.u[end], osol.u[end]; atol = 0.01) + @test sol.u[1] == [π / 2, π / 2] + end +end + +################################################################## +### ODESystem with constraint equations, DAEs with constraints ### +################################################################## + +# Test generation of boundary condition function using `generate_function_bc`. Compare solutions to manually written boundary conditions +let + @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 + @variables x(..) y(..) + eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y(t), + D(y(t)) ~ -γ * y(t) + δ * x(t) * y(t)] + + tspan = (0., 1.) + @mtkbuild lksys = ODESystem(eqs, t) + + function lotkavolterra!(du, u, p, t) + du[1] = p[1]*u[1] - p[2]*u[1]*u[2] + du[2] = -p[4]*u[2] + p[3]*u[1]*u[2] + end + + function lotkavolterra(u, p, t) + [p[1]*u[1] - p[2]*u[1]*u[2], -p[4]*u[2] + p[3]*u[1]*u[2]] + end + # Compare the built bc function to the actual constructed one. + function bc!(resid, u, p, t) + resid[1] = u[1][1] - 1. + resid[2] = u[1][2] - 2. + nothing + end + function bc(u, p, t) + [u[1][1] - 1., u[1][2] - 2.] + end + + u0 = [1., 2.]; p = [1.5, 1., 1., 3.] + genbc_iip = ModelingToolkit.generate_function_bc(lksys, nothing, u0, [1, 2], tspan, true) + genbc_oop = ModelingToolkit.generate_function_bc(lksys, nothing, u0, [1, 2], tspan, false) + + bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1.,2.], tspan, p) + bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1.,2.], tspan, p) + + sol1 = solve(bvpi1, MIRK4(), dt = 0.05) + sol2 = solve(bvpi2, MIRK4(), dt = 0.05) + @test sol1 ≈ sol2 + + bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) + bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) + + sol1 = solve(bvpo1, MIRK4(), dt = 0.05) + sol2 = solve(bvpo2, MIRK4(), dt = 0.05) + @test sol1 ≈ sol2 + + # Test with a constraint. + constraints = [y(0.5) ~ 2.] + + function bc!(resid, u, p, t) + resid[1] = u(0.0)[1] - 1. + resid[2] = u(0.5)[2] - 2. + end + function bc(u, p, t) + [u(0.0)[1] - 1., u(0.5)[2] - 2.] + end + + u0 = [1, 1.] + genbc_iip = ModelingToolkit.generate_function_bc(lksys, constraints, u0, [1], tspan, true) + genbc_oop = ModelingToolkit.generate_function_bc(lksys, constraints, u0, [1], tspan, false) + + bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, u0, tspan, p) + bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, u0, tspan, p) + bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) + bvpi4 = SciMLBase.BVProblem{true, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) + + sol1 = @btime solve($bvpi1, MIRK4(), dt = 0.01) + sol2 = @btime solve($bvpi2, MIRK4(), dt = 0.01) + sol3 = @btime solve($bvpi3, MIRK4(), dt = 0.01) + sol4 = @btime solve($bvpi4, MIRK4(), dt = 0.01) + @test sol1 ≈ sol2 ≈ sol3 ≈ sol4 # don't get true equality here, not sure why + + bvpo1 = BVProblem(lotkavolterra, bc, u0, tspan, p) + bvpo2 = BVProblem(lotkavolterra, genbc_oop, u0, tspan, p) + bvpo3 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) + + sol1 = @btime solve($bvpo1, MIRK4(), dt = 0.05) + sol2 = @btime solve($bvpo2, MIRK4(), dt = 0.05) + sol3 = @btime solve($bvpo3, MIRK4(), dt = 0.05) + @test sol1 ≈ sol2 ≈ sol3 +end function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.05, atol = 1e-3) for solver in solvers @@ -214,33 +214,32 @@ let u0map = [] tspan = (0.0, 1.0) guesses = [x(t) => 4.0, y(t) => 2.] + constr = [x(.6) ~ 3.5, x(.3) ~ 7.] + @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) - @mtkbuild lksys = ODESystem(eqs, t) - - constraints = [x(.6) ~ 3.5, x(.3) ~ 7.] - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) - test_solvers(solvers, bvp, u0map, constraints; dt = 0.05) + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses) + test_solvers(solvers, bvp, u0map, constr; dt = 0.05) - # Testing that more complicated constraints give correct solutions. - constraints = [y(.2) + x(.8) ~ 3., y(.3) ~ 2.] - bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, u0map, tspan; guesses, constraints) - test_solvers(solvers, bvp, u0map, constraints; dt = 0.05) + # Testing that more complicated constr give correct solutions. + constr = [y(.2) + x(.8) ~ 3., y(.3) ~ 2.] + bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, u0map, tspan; guesses) + test_solvers(solvers, bvp, u0map, constr; dt = 0.05) - constraints = [α * β - x(.6) ~ 0.0, y(.2) ~ 3.] - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) - test_solvers(solvers, bvp, u0map, constraints) + constr = [α * β - x(.6) ~ 0.0, y(.2) ~ 3.] + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses) + test_solvers(solvers, bvp, u0map, constr) - # Testing that errors are properly thrown when malformed constraints are given. + # Testing that errors are properly thrown when malformed constr are given. @variables bad(..) - constraints = [x(1.) + bad(3.) ~ 10] - @test_throws ErrorException bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) + constr = [x(1.) + bad(3.) ~ 10] + @test_throws ErrorException lksys = ODESystem(eqs, t; constraints = constr) - constraints = [x(t) + y(t) ~ 3] - @test_throws ErrorException bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) + constr = [x(t) + y(t) ~ 3] + @test_throws ErrorException lksys = ODESystem(eqs, t; constraints = constr) @parameters bad2 - constraints = [bad2 + x(0.) ~ 3] - @test_throws ErrorException bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses, constraints) + constr = [bad2 + x(0.) ~ 3] + @test_throws ErrorException lksys = ODESystem(eqs, t; constraints = constr) end # Cartesian pendulum from the docs. @@ -288,31 +287,33 @@ end # eqs = [D(D(x(t))) ~ λ * x(t) # D(D(y)) ~ λ * y - g # x(t)^2 + y^2 ~ 1] -# @mtkbuild pend = ODESystem(eqs, t) +# constr = [x(0.5) ~ 1] +# @mtkbuild pend = ODESystem(eqs, t; constr) # # tspan = (0.0, 1.5) # u0map = [x(t) => 0.6, y => 0.8] # parammap = [g => 1] # guesses = [λ => 1] # -# constraints = [x(0.5) ~ 1] -# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) -# test_solvers(daesolvers, bvp, u0map, constraints) +# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, check_length = false) +# test_solvers(daesolvers, bvp, u0map, constr) # # bvp2 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(pend, u0map, tspan, parammap) -# test_solvers(daesolvers, bvp2, u0map, constraints, get_alg_eqs(pend)) +# test_solvers(daesolvers, bvp2, u0map, constr, get_alg_eqs(pend)) # -# # More complicated constraints. +# # More complicated constr. # u0map = [x(t) => 0.6] # guesses = [λ => 1, y(t) => 0.8] # -# constraints = [x(0.5) ~ 1, +# constr = [x(0.5) ~ 1, # x(0.3)^3 + y(0.6)^2 ~ 0.5] -# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) -# test_solvers(daesolvers, bvp, u0map, constraints, get_alg_eqs(pend)) +# @mtkbuild pend = ODESystem(eqs, t; constr) +# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, check_length = false) +# test_solvers(daesolvers, bvp, u0map, constr, get_alg_eqs(pend)) # -# constraints = [x(0.4) * g ~ y(0.2), +# constr = [x(0.4) * g ~ y(0.2), # y(0.7) ~ 0.3] -# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; constraints, guesses, check_length = false) -# test_solvers(daesolvers, bvp, u0map, constraints, get_alg_eqs(pend)) +# @mtkbuild pend = ODESystem(eqs, t; constr) +# bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, check_length = false) +# test_solvers(daesolvers, bvp, u0map, constr, get_alg_eqs(pend)) # end diff --git a/test/odesystem.jl b/test/odesystem.jl index 85d135b338..516fa7d415 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1552,3 +1552,33 @@ end expected_tstops = unique!(sort!(vcat(0.0:0.075:10.0, 0.1, 0.2, 0.65, 0.35, 0.45))) @test all(x -> any(isapprox(x, atol = 1e-6), sol2.t), expected_tstops) end + +@testset "Constraint system construction" begin + @variables x(..) y(..) z(..) + @parameters a b c d e + eqs = [D(x(t)) ~ 3*a*y(t), D(y(t)) ~ x(t) - z(t), D(z(t)) ~ e*x(t)^2] + cons = [x(0.3) ~ c*d, y(0.7) ~ 3] + + # Test variables + parameters infer correctly. + @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @test issetequal(parameters(sys), [a, c, d, e]) + @test issetequal(unknowns(sys), [x(t), y(t)]) + + @parameters t_c + cons = [x(t_c) ~ 3] + @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @test_broken issetequal(parameters(sys), [a, e, t_c]) # TODO: unbreak this. + + # Test that bad constraints throw errors. + cons = [x(3, 4) ~ 3] + @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + + cons = [x(y(t)) ~ 2] + @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + + @variables u(t) v + cons = [x(t) * u ~ 3] + @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + cons = [x(t) * v ~ 3] + @test_nowarn @mtkbuild sys = ODESystem(eqs, t; constraints = cons) +end From 90ce80d658eabd599b99c9e5c9bbc5efc9d41470 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 28 Jan 2025 11:57:37 -0500 Subject: [PATCH 27/50] refactor tests --- src/systems/diffeqs/abstractodesystem.jl | 45 +++++++++++++----------- src/systems/diffeqs/odesystem.jl | 11 +++--- test/bvproblem.jl | 38 ++++++-------------- 3 files changed, 39 insertions(+), 55 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index d0d6ed7937..47e5c3b88c 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -943,9 +943,10 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] sts = unknowns(sys) ps = parameters(sys) + constraintsys = get_constraintsystem(sys) - if !isnothing(constraints) - (length(constraints) + length(u0map) > length(sts)) && + if !isnothing(constraintsys) + (length(constraints(constraintsys)) + length(u0map) > length(sts)) && @warn "The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) exceeds the total number of states. The BVP solvers will default to doing a nonlinear least-squares optimization." end @@ -976,33 +977,35 @@ function generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan, iip) ps = get_ps(sys) np = length(ps) ns = length(sts) - conssys = get_constraintsystem(sys) - cons = constraints(conssys) - stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) @variables sol(..)[1:ns] p[1:np] - exprs = Any[] - for st in get_unknowns(cons) - x = operation(st) - t = first(arguments(st)) - idx = stidxmap[x(iv)] + conssys = get_constraintsystem(sys) + cons = Any[] + if !isnothing(conssys) + cons = [con.lhs - con.rhs for con in constraints(conssys)] - cons = Symbolics.substitute(cons, Dict(x(t) => sol(t)[idx])) - end + for st in get_unknowns(conssys) + x = operation(st) + t = only(arguments(st)) + idx = stidxmap[x(iv)] - for var in get_parameters(cons) - if iscall(var) - x = operation(var) - t = arguments(var)[1] - idx = pidxmap[x] + cons = map(c -> Symbolics.substitute(c, Dict(x(t) => sol(t)[idx])), cons) + end - cons = Symbolics.substitute(cons, Dict(x(t) => p[idx])) - else - idx = pidxmap[var] - cons = Symbolics.substitute(cons, Dict(var => p[idx])) + for var in parameters(conssys) + if iscall(var) + x = operation(var) + t = only(arguments(var)) + idx = pidxmap[x] + + cons = map(c -> Symbolics.substitute(c, Dict(x(t) => p[idx])), cons) + else + idx = pidxmap[var] + cons = map(c -> Symbolics.substitute(c, Dict(var => p[idx])), cons) + end end end diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 6ca6cdf838..4ef9f89c6a 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -802,13 +802,11 @@ function validate_constraint_syms(constraintsts, constraintps, sts, ps, iv) throw(ArgumentError("Too many arguments for variable $var.")) elseif length(arguments(var)) == 1 arg = first(arguments(var)) - operation(var)(iv) ∈ sts || throw(ArgumentError("Variable $var is not a variable of the ODESystem. Called variables must be variables of the ODESystem.")) + operation(var)(iv) ∈ sts || + throw(ArgumentError("Variable $var is not a variable of the ODESystem. Called variables must be variables of the ODESystem.")) - isequal(arg, iv) || - isparameter(arg) || - arg isa Integer || - arg isa AbstractFloat || - throw(ArgumentError("Invalid argument specified for variable $var. The argument of the variable should be either $iv, a parameter, or a value specifying the time that the constraint holds.")) + isequal(arg, iv) || isparameter(arg) || arg isa Integer || arg isa AbstractFloat || + throw(ArgumentError("Invalid argument specified for variable $var. The argument of the variable should be either $iv, a parameter, or a value specifying the time that the constraint holds.")) else var ∈ sts && @warn "Variable $var has no argument. It will be interpreted as $var($iv), and the constraint will apply to the entire interval." end @@ -824,7 +822,6 @@ function validate_constraint_syms(constraintsts, constraintps, sts, ps, iv) operation(var) ∈ ps || throw(ArgumentError("Parameter $var is not a parameter of the ODESystem. Called parameters must be parameters of the ODESystem.")) isequal(arg, iv) || - isparameter(arg) || arg isa Integer || arg isa AbstractFloat || throw(ArgumentError("Invalid argument specified for callable parameter $var. The argument of the parameter should be either $iv, a parameter, or a value specifying the time that the constraint holds.")) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 16c12d1be6..c6c124d09e 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -5,7 +5,6 @@ using BenchmarkTools using ModelingToolkit using SciMLBase using ModelingToolkit: t_nounits as t, D_nounits as D -import ModelingToolkit: process_constraints ### Test Collocation solvers on simple problems solvers = [MIRK4] @@ -113,8 +112,8 @@ let end u0 = [1., 2.]; p = [1.5, 1., 1., 3.] - genbc_iip = ModelingToolkit.generate_function_bc(lksys, nothing, u0, [1, 2], tspan, true) - genbc_oop = ModelingToolkit.generate_function_bc(lksys, nothing, u0, [1, 2], tspan, false) + genbc_iip = ModelingToolkit.generate_function_bc(lksys, u0, [1, 2], tspan, true) + genbc_oop = ModelingToolkit.generate_function_bc(lksys, u0, [1, 2], tspan, false) bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1.,2.], tspan, p) bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1.,2.], tspan, p) @@ -131,7 +130,8 @@ let @test sol1 ≈ sol2 # Test with a constraint. - constraints = [y(0.5) ~ 2.] + constr = [y(0.5) ~ 2.] + @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) function bc!(resid, u, p, t) resid[1] = u(0.0)[1] - 1. @@ -142,13 +142,13 @@ let end u0 = [1, 1.] - genbc_iip = ModelingToolkit.generate_function_bc(lksys, constraints, u0, [1], tspan, true) - genbc_oop = ModelingToolkit.generate_function_bc(lksys, constraints, u0, [1], tspan, false) + genbc_iip = ModelingToolkit.generate_function_bc(lksys, u0, [1], tspan, true) + genbc_oop = ModelingToolkit.generate_function_bc(lksys, u0, [1], tspan, false) bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, u0, tspan, p) bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, u0, tspan, p) - bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) - bvpi4 = SciMLBase.BVProblem{true, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) + bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.]) + bvpi4 = SciMLBase.BVProblem{true, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.]) sol1 = @btime solve($bvpi1, MIRK4(), dt = 0.01) sol2 = @btime solve($bvpi2, MIRK4(), dt = 0.01) @@ -158,7 +158,7 @@ let bvpo1 = BVProblem(lotkavolterra, bc, u0, tspan, p) bvpo2 = BVProblem(lotkavolterra, genbc_oop, u0, tspan, p) - bvpo3 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.], constraints) + bvpo3 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.]) sol1 = @btime solve($bvpo1, MIRK4(), dt = 0.05) sol2 = @btime solve($bvpo2, MIRK4(), dt = 0.05) @@ -197,12 +197,6 @@ function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0. end end -solvers = [RadauIIa3, RadauIIa5, RadauIIa7, - LobattoIIIa2, LobattoIIIa4, LobattoIIIa5, - LobattoIIIb2, LobattoIIIb3, LobattoIIIb4, LobattoIIIb5, - LobattoIIIc2, LobattoIIIc3, LobattoIIIc4, LobattoIIIc5] -weird = [MIRK2, MIRK5, RadauIIa2] -daesolvers = [] # Simple ODESystem with BVP constraints. let @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 @@ -222,24 +216,14 @@ let # Testing that more complicated constr give correct solutions. constr = [y(.2) + x(.8) ~ 3., y(.3) ~ 2.] + @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, u0map, tspan; guesses) test_solvers(solvers, bvp, u0map, constr; dt = 0.05) constr = [α * β - x(.6) ~ 0.0, y(.2) ~ 3.] + @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses) test_solvers(solvers, bvp, u0map, constr) - - # Testing that errors are properly thrown when malformed constr are given. - @variables bad(..) - constr = [x(1.) + bad(3.) ~ 10] - @test_throws ErrorException lksys = ODESystem(eqs, t; constraints = constr) - - constr = [x(t) + y(t) ~ 3] - @test_throws ErrorException lksys = ODESystem(eqs, t; constraints = constr) - - @parameters bad2 - constr = [bad2 + x(0.) ~ 3] - @test_throws ErrorException lksys = ODESystem(eqs, t; constraints = constr) end # Cartesian pendulum from the docs. From a15c67024d4630193afe0f662d7c4c4183d07037 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 28 Jan 2025 14:23:08 -0500 Subject: [PATCH 28/50] fix sym validation --- src/systems/diffeqs/odesystem.jl | 91 ++++++++++++++------------------ test/odesystem.jl | 15 ++++-- 2 files changed, 49 insertions(+), 57 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 4ef9f89c6a..0c8304e428 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -221,7 +221,7 @@ end function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; controls = Num[], observed = Equation[], - constraints = Equation[], + constraintsystem = nothing, systems = ODESystem[], tspan = nothing, name = nothing, @@ -286,26 +286,17 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; cont_callbacks = SymbolicContinuousCallbacks(continuous_events) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events) - constraintsys = nothing - if !isempty(constraints) - constraintsys = process_constraint_system(constraints, dvs′, ps′, iv, systems) - dvset = Set(dvs′) - pset = Set(ps′) - for st in get_unknowns(constraintsys) - iscall(st) ? - !in(operation(st)(iv), dvset) && push!(dvs′, st) : - !in(st, dvset) && push!(dvs′, st) - end - for p in parameters(constraintsys) - !in(p, pset) && push!(ps′, p) - end - end - if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end + + if !isempty(systems) + cons = get_constraintsystems.(systems) + @set! constraintsystem.systems = cons + end + ODESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - deqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, constraintsys, tgrad, jac, + deqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, constraintsystem, tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, nothing, initializesystem, initialization_eqs, schedule, connector_type, preface, cont_callbacks, @@ -377,9 +368,22 @@ function ODESystem(eqs, iv; constraints = Equation[], kwargs...) end algevars = setdiff(allunknowns, diffvars) + if !isempty(constraints) + consvars = OrderedSet() + constraintsystem = process_constraint_system(constraints, allunknowns, new_ps, iv) + for st in get_unknowns(constraintsystem) + iscall(st) ? + !in(operation(st)(iv), allunknowns) && push!(consvars, st) : + !in(st, allunknowns) && push!(consvars, st) + end + for p in parameters(constraintsystem) + !in(p, new_ps) && push!(new_ps, p) + end + end + # the orders here are very important! return ODESystem(Equation[diffeq; algeeq; compressed_eqs], iv, - collect(Iterators.flatten((diffvars, algevars))), collect(new_ps); constraints, kwargs...) + collect(Iterators.flatten((diffvars, algevars, consvars))), collect(new_ps); constraintsystem, kwargs...) end # NOTE: equality does not check cached Jacobian @@ -791,55 +795,38 @@ function Base.show(io::IO, mime::MIME"text/plain", sys::ODESystem; hint = true, end # Validate that all the variables in the BVP constraints are well-formed states or parameters. -# - Any callable with multiple arguments will error. # - Callable/delay variables (e.g. of the form x(0.6) should be unknowns of the system (and have one arg, etc.) # - Callable/delay parameters should be parameters of the system (and have one arg, etc.) -function validate_constraint_syms(constraintsts, constraintps, sts, ps, iv) +function process_constraint_system(constraints::Vector{Equation}, sts, ps, iv; consname = :cons) + isempty(constraints) && return nothing + + constraintsts = OrderedSet() + constraintps = OrderedSet() + + # Hack? to extract parameters from callable variables in constraints. + for cons in constraints + collect_vars!(constraintsts, constraintps, cons, iv) + end + + # Validate the states. for var in constraintsts if !iscall(var) - occursin(iv, var) && var ∈ sts || throw(ArgumentError("Time-dependent variable $var is not an unknown of the system.")) + occursin(iv, var) && (var ∈ sts || throw(ArgumentError("Time-dependent variable $var is not an unknown of the system."))) elseif length(arguments(var)) > 1 throw(ArgumentError("Too many arguments for variable $var.")) elseif length(arguments(var)) == 1 - arg = first(arguments(var)) + arg = only(arguments(var)) operation(var)(iv) ∈ sts || throw(ArgumentError("Variable $var is not a variable of the ODESystem. Called variables must be variables of the ODESystem.")) isequal(arg, iv) || isparameter(arg) || arg isa Integer || arg isa AbstractFloat || throw(ArgumentError("Invalid argument specified for variable $var. The argument of the variable should be either $iv, a parameter, or a value specifying the time that the constraint holds.")) + + isparameter(arg) && push!(constraintps, arg) else var ∈ sts && @warn "Variable $var has no argument. It will be interpreted as $var($iv), and the constraint will apply to the entire interval." end end - for var in constraintps - !iscall(var) && continue - - if length(arguments(var)) > 1 - throw(ArgumentError("Too many arguments for parameter $var in equation $eq.")) - elseif length(arguments(var)) == 1 - arg = first(arguments(var)) - operation(var) ∈ ps || throw(ArgumentError("Parameter $var is not a parameter of the ODESystem. Called parameters must be parameters of the ODESystem.")) - - isequal(arg, iv) || - arg isa Integer || - arg isa AbstractFloat || - throw(ArgumentError("Invalid argument specified for callable parameter $var. The argument of the parameter should be either $iv, a parameter, or a value specifying the time that the constraint holds.")) - end - end -end - -function process_constraint_system(constraints::Vector{Equation}, sts, ps, iv, subsys::Vector{ODESystem}; name = :cons) - isempty(constraints) && return nothing - - constraintsts = OrderedSet() - constraintps = OrderedSet() - - for cons in constraints - syms = collect_vars!(constraintsts, constraintps, cons, iv) - end - validate_constraint_syms(constraintsts, constraintps, Set(sts), Set(ps), iv) - - constraint_subsys = get_constraintsystem.(subsys) - ConstraintsSystem(constraints, collect(constraintsts), collect(constraintps); systems = constraint_subsys, name) + ConstraintsSystem(constraints, collect(constraintsts), collect(constraintps); name = consname) end diff --git a/test/odesystem.jl b/test/odesystem.jl index 516fa7d415..0276bb4b4f 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1562,23 +1562,28 @@ end # Test variables + parameters infer correctly. @mtkbuild sys = ODESystem(eqs, t; constraints = cons) @test issetequal(parameters(sys), [a, c, d, e]) - @test issetequal(unknowns(sys), [x(t), y(t)]) + @test issetequal(unknowns(sys), [x(t), y(t), z(t)]) @parameters t_c cons = [x(t_c) ~ 3] @mtkbuild sys = ODESystem(eqs, t; constraints = cons) - @test_broken issetequal(parameters(sys), [a, e, t_c]) # TODO: unbreak this. + @test issetequal(parameters(sys), [a, e, t_c]) + + @parameters g(..) h i + cons = [g(h, i) * x(3) ~ c] + @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @test issetequal(parameters(sys), [g, h, i, a, e, c]) # Test that bad constraints throw errors. - cons = [x(3, 4) ~ 3] + cons = [x(3, 4) ~ 3] # unknowns cannot have multiple args. @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) - cons = [x(y(t)) ~ 2] + cons = [x(y(t)) ~ 2] # unknown arg must be parameter, value, or t @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) @variables u(t) v cons = [x(t) * u ~ 3] @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) cons = [x(t) * v ~ 3] - @test_nowarn @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) # Need time argument. end From c6ef04adac961b386f0f37c4750eee28273cff3f Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 28 Jan 2025 14:24:38 -0500 Subject: [PATCH 29/50] remove file --- src/systems/diffeqs/bvpsystem.jl | 217 ------------------------------- 1 file changed, 217 deletions(-) delete mode 100644 src/systems/diffeqs/bvpsystem.jl diff --git a/src/systems/diffeqs/bvpsystem.jl b/src/systems/diffeqs/bvpsystem.jl deleted file mode 100644 index ae3029fa14..0000000000 --- a/src/systems/diffeqs/bvpsystem.jl +++ /dev/null @@ -1,217 +0,0 @@ -""" -$(TYPEDEF) - -A system of ordinary differential equations. - -# Fields -$(FIELDS) - -# Example - -```julia -using ModelingToolkit -using ModelingToolkit: t_nounits as t, D_nounits as D - -@parameters σ ρ β -@variables x(t) y(t) z(t) - -eqs = [D(x) ~ σ*(y-x), - D(y) ~ x*(ρ-z)-y, - D(z) ~ x*y - β*z] - -@named de = ODESystem(eqs,t,[x,y,z],[σ,ρ,β],tspan=(0, 1000.0)) -``` -""" -struct ODESystem <: AbstractODESystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """The ODEs defining the system.""" - eqs::Vector{Equation} - """Independent variable.""" - iv::BasicSymbolic{Real} - """ - Dependent (unknown) variables. Must not contain the independent variable. - - N.B.: If `torn_matching !== nothing`, this includes all variables. Actual - ODE unknowns are determined by the `SelectedState()` entries in `torn_matching`. - """ - unknowns::Vector - """Parameter variables. Must not contain the independent variable.""" - ps::Vector - """Time span.""" - tspan::Union{NTuple{2, Any}, Nothing} - """Array variables.""" - var_to_name::Any - """Control parameters (some subset of `ps`).""" - ctrls::Vector - """Observed variables.""" - observed::Vector{Equation} - """ - Time-derivative matrix. Note: this field will not be defined until - [`calculate_tgrad`](@ref) is called on the system. - """ - tgrad::RefValue{Vector{Num}} - """ - Jacobian matrix. Note: this field will not be defined until - [`calculate_jacobian`](@ref) is called on the system. - """ - jac::RefValue{Any} - """ - Control Jacobian matrix. Note: this field will not be defined until - [`calculate_control_jacobian`](@ref) is called on the system. - """ - ctrl_jac::RefValue{Any} - """ - Note: this field will not be defined until - [`generate_factorized_W`](@ref) is called on the system. - """ - Wfact::RefValue{Matrix{Num}} - """ - Note: this field will not be defined until - [`generate_factorized_W`](@ref) is called on the system. - """ - Wfact_t::RefValue{Matrix{Num}} - """ - The name of the system. - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{ODESystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ODEProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - Tearing result specifying how to solve the system. - """ - torn_matching::Union{Matching, Nothing} - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - The schedule for the code generation process. - """ - schedule::Any - """ - Type of the system. - """ - connector_type::Any - """ - Inject assignment statements before the evaluation of the RHS function. - """ - preface::Any - """ - A `Vector{SymbolicContinuousCallback}` that model events. - The integrator will use root finding to guarantee that it steps at each zero crossing. - """ - continuous_events::Vector{SymbolicContinuousCallback} - """ - A `Vector{SymbolicDiscreteCallback}` that models events. Symbolic - analog to `SciMLBase.DiscreteCallback` that executes an affect when a given condition is - true at the end of an integration step. - """ - discrete_events::Vector{SymbolicDiscreteCallback} - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - A boolean indicating if the given `ODESystem` represents a system of DDEs. - """ - is_dde::Bool - """ - A list of points to provide to the solver as tstops. Uses the same syntax as discrete - events. - """ - tstops::Vector{Any} - """ - Cache for intermediate tearing state. - """ - tearing_state::Any - """ - Substitutions generated by tearing. - """ - substitutions::Any - """ - If a model `sys` is complete, then `sys.x` no longer performs namespacing. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - A list of discrete subsystems. - """ - discrete_subsystems::Any - """ - A list of actual unknowns needed to be solved by solvers. - """ - solved_unknowns::Union{Nothing, Vector{Any}} - """ - A vector of vectors of indices for the split parameters. - """ - split_idxs::Union{Nothing, Vector{Vector{Int}}} - """ - The hierarchical parent system before simplification. - """ - parent::Any - - function ODESystem(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, - jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, - torn_matching, initializesystem, initialization_eqs, schedule, - connector_type, preface, cevents, - devents, parameter_dependencies, - metadata = nothing, gui_metadata = nothing, is_dde = false, - tstops = [], tearing_state = nothing, - substitutions = nothing, complete = false, index_cache = nothing, - discrete_subsystems = nothing, solved_unknowns = nothing, - split_idxs = nothing, parent = nothing; checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckComponents) > 0 - check_independent_variables([iv]) - check_variables(dvs, iv) - check_parameters(ps, iv) - check_equations(deqs, iv) - check_equations(equations(cevents), iv) - end - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(dvs, ps, iv) - check_units(u, deqs) - end - new(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac, - ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, torn_matching, - initializesystem, initialization_eqs, schedule, connector_type, preface, - cevents, devents, parameter_dependencies, metadata, - gui_metadata, is_dde, tstops, tearing_state, substitutions, complete, index_cache, - discrete_subsystems, solved_unknowns, split_idxs, parent) - end -end From 78782256b5f3c3f82ae1869b2d6737ea7a4b7acc Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 28 Jan 2025 14:26:12 -0500 Subject: [PATCH 30/50] up --- src/systems/optimization/constraints_system.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/systems/optimization/constraints_system.jl b/src/systems/optimization/constraints_system.jl index 61e190e672..3c2aed192d 100644 --- a/src/systems/optimization/constraints_system.jl +++ b/src/systems/optimization/constraints_system.jl @@ -90,9 +90,6 @@ struct ConstraintsSystem <: AbstractTimeIndependentSystem complete = false, index_cache = nothing; checks::Union{Bool, Int} = true) - ##if checks == true || (checks & CheckComponents) > 0 - ## check_variables(unknowns, constraints) - ##end if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(unknowns, ps) check_units(u, constraints) From 0493b5d2b49e72872976caf2e9c57381e272557a Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 28 Jan 2025 14:28:46 -0500 Subject: [PATCH 31/50] remove lines --- src/systems/optimization/constraints_system.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/systems/optimization/constraints_system.jl b/src/systems/optimization/constraints_system.jl index 3c2aed192d..03225fc900 100644 --- a/src/systems/optimization/constraints_system.jl +++ b/src/systems/optimization/constraints_system.jl @@ -89,7 +89,6 @@ struct ConstraintsSystem <: AbstractTimeIndependentSystem tearing_state = nothing, substitutions = nothing, complete = false, index_cache = nothing; checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(unknowns, ps) check_units(u, constraints) @@ -254,4 +253,3 @@ function get_cmap(sys::ConstraintsSystem, exprs = nothing) cmap = map(x -> x ~ getdefault(x), cs) return cmap, cs end - From 1d32b6ea8438bda25dc91e095458c5f377c801c0 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 28 Jan 2025 14:50:42 -0500 Subject: [PATCH 32/50] up --- src/systems/diffeqs/odesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 8d0e09fce5..1154bcd4c8 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -368,8 +368,8 @@ function ODESystem(eqs, iv; constraints = Equation[], kwargs...) end algevars = setdiff(allunknowns, diffvars) + consvars = OrderedSet() if !isempty(constraints) - consvars = OrderedSet() constraintsystem = process_constraint_system(constraints, allunknowns, new_ps, iv) for st in get_unknowns(constraintsystem) iscall(st) ? From 2b3ca96d23f0010609867f6586449aa7dd9c6ddb Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 28 Jan 2025 15:05:02 -0500 Subject: [PATCH 33/50] up --- src/systems/diffeqs/odesystem.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 1154bcd4c8..1754079745 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -369,6 +369,7 @@ function ODESystem(eqs, iv; constraints = Equation[], kwargs...) algevars = setdiff(allunknowns, diffvars) consvars = OrderedSet() + constraintsystem = nothing if !isempty(constraints) constraintsystem = process_constraint_system(constraints, allunknowns, new_ps, iv) for st in get_unknowns(constraintsystem) From 0324522a419219c8f59f1b584c1ccd65454dc794 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 28 Jan 2025 15:05:57 -0500 Subject: [PATCH 34/50] fix typo --- src/systems/diffeqs/odesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 1754079745..2ed7155f6c 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -291,7 +291,7 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; end if !isempty(systems) - cons = get_constraintsystems.(systems) + cons = get_constraintsystem.(systems) @set! constraintsystem.systems = cons end From 2a079becdd97c10431ad8a85f707adbb39519e59 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 28 Jan 2025 15:46:23 -0500 Subject: [PATCH 35/50] Fix setter --- src/systems/diffeqs/odesystem.jl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 2ed7155f6c..dac0878d26 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -290,9 +290,14 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; is_dde = _check_if_dde(deqs, iv′, systems) end - if !isempty(systems) - cons = get_constraintsystem.(systems) - @set! constraintsystem.systems = cons + if !isempty(systems) && !isnothing(constraintsystem) + conssystems = ConstraintsSystem[] + for sys in systems + cons = get_constraintsystem(sys) + cons !== nothing && push!(conssystems, cons) + end + @show conssystems + @set! constraintsystem.systems = conssystems end ODESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), From d70a470d546e9c79417fb4c0869956cd1800de00 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 28 Jan 2025 16:21:54 -0500 Subject: [PATCH 36/50] fix --- test/odesystem.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/odesystem.jl b/test/odesystem.jl index 963847731c..53f75bf377 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1660,3 +1660,4 @@ end @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) cons = [x(t) * v ~ 3] @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) # Need time argument. +end From 37092f12c1fb6958b4d1ab6a917be99221371e61 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 29 Jan 2025 14:32:17 -0500 Subject: [PATCH 37/50] lower tol --- test/bvproblem.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index c6c124d09e..30fde44531 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -1,6 +1,6 @@ ### TODO: update when BoundaryValueDiffEqAscher is updated to use the normal boundary condition conventions -using BoundaryValueDiffEq, OrdinaryDiffEq, BoundaryValueDiffEqAscher +using BoundaryValueDiffEq, OrdinaryDiffEqDefault, BoundaryValueDiffEqAscher using BenchmarkTools using ModelingToolkit using SciMLBase @@ -166,7 +166,7 @@ let @test sol1 ≈ sol2 ≈ sol3 end -function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.05, atol = 1e-3) +function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.05, atol = 1e-2) for solver in solvers println("Solver: $solver") sol = @btime solve($prob, $solver(), dt = $dt, abstol = $atol) @@ -214,7 +214,7 @@ let bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses) test_solvers(solvers, bvp, u0map, constr; dt = 0.05) - # Testing that more complicated constr give correct solutions. + # Testing that more complicated constraints give correct solutions. constr = [y(.2) + x(.8) ~ 3., y(.3) ~ 2.] @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, u0map, tspan; guesses) From e5eb8bd35a89ab8b0d5346c218ea0bdf6cb15f37 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 29 Jan 2025 15:05:35 -0500 Subject: [PATCH 38/50] fix Project.toml --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index 69ad24c8df..865b88658b 100644 --- a/Project.toml +++ b/Project.toml @@ -143,7 +143,6 @@ SparseArrays = "1" SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "0.10, 0.11, 0.12, 1.0" StochasticDelayDiffEq = "1.8.1" -StochasticDiffEq = "6.72.1" SymbolicIndexingInterface = "0.3.36" SymbolicUtils = "3.10" Symbolics = "6.22.1" From 2ae79ae19941d5502a7a83d13770284cad7b2cc3 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 30 Jan 2025 11:34:36 -0500 Subject: [PATCH 39/50] revert to OrdinaryDiffEq --- test/bvproblem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 30fde44531..069372aef7 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -1,6 +1,6 @@ ### TODO: update when BoundaryValueDiffEqAscher is updated to use the normal boundary condition conventions -using BoundaryValueDiffEq, OrdinaryDiffEqDefault, BoundaryValueDiffEqAscher +using BoundaryValueDiffEq, OrdinaryDiffEq, BoundaryValueDiffEqAscher using BenchmarkTools using ModelingToolkit using SciMLBase From 13a242c35ed4ec7c19143ccde26f3a8e6edddf3f Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 3 Feb 2025 15:34:05 -0500 Subject: [PATCH 40/50] update to use updated codegen --- src/systems/diffeqs/abstractodesystem.jl | 33 ++++++++---------------- test/bvproblem.jl | 11 ++++---- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index f017cb902f..95e966a461 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -881,18 +881,23 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) u0_idxs = has_alg_eqs(sys) ? collect(1:length(sts)) : [stidxmap[k] for (k,v) in u0map] - bc = generate_function_bc(sys, u0, u0_idxs, tspan, iip) + fns = generate_function_bc(sys, u0, u0_idxs, tspan) + bc_oop, bc_iip = eval_or_rgf.(fns; eval_expression, eval_module) + # bc(sol, p, t) = bc_oop(sol, p, t) + bc(resid, u, p, t) = bc_iip(resid, u, p, t) + return BVProblem{iip}(f, bc, u0, tspan, p; kwargs...) end get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") """ - generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan, iip) + generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan) Given an ODESystem with constraints, generate the boundary condition function to pass to boundary value problem solvers. + Expression uses the constraints and the provided initial conditions. """ -function generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan, iip) +function generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan; kwargs...) iv = get_iv(sys) sts = get_unknowns(sys) ps = get_ps(sys) @@ -915,19 +920,6 @@ function generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan, iip) cons = map(c -> Symbolics.substitute(c, Dict(x(t) => sol(t)[idx])), cons) end - - for var in parameters(conssys) - if iscall(var) - x = operation(var) - t = only(arguments(var)) - idx = pidxmap[x] - - cons = map(c -> Symbolics.substitute(c, Dict(x(t) => p[idx])), cons) - else - idx = pidxmap[var] - cons = map(c -> Symbolics.substitute(c, Dict(var => p[idx])), cons) - end - end end init_conds = Any[] @@ -937,12 +929,9 @@ function generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan, iip) end exprs = vcat(init_conds, cons) - bcs = Symbolics.build_function(exprs, sol, p, expression = Val{false}) - if iip - return (resid, u, p, t) -> bcs[2](resid, u, p) - else - return (u, p, t) -> bcs[1](u, p) - end + _p = reorder_parameters(sys, ps) + + build_function_wrapper(sys, exprs, sol, _p..., t; kwargs...) end """ diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 069372aef7..cedd0eef8d 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -1,6 +1,7 @@ ### TODO: update when BoundaryValueDiffEqAscher is updated to use the normal boundary condition conventions -using BoundaryValueDiffEq, OrdinaryDiffEq, BoundaryValueDiffEqAscher +using OrdinaryDiffEqVerner +using BoundaryValueDiffEqMIRK, BoundaryValueDiffEqAscher using BenchmarkTools using ModelingToolkit using SciMLBase @@ -207,22 +208,22 @@ let u0map = [] tspan = (0.0, 1.0) - guesses = [x(t) => 4.0, y(t) => 2.] + guess = [x(t) => 4.0, y(t) => 2.0] constr = [x(.6) ~ 3.5, x(.3) ~ 7.] @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses) + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses = guess) test_solvers(solvers, bvp, u0map, constr; dt = 0.05) # Testing that more complicated constraints give correct solutions. constr = [y(.2) + x(.8) ~ 3., y(.3) ~ 2.] @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) - bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, u0map, tspan; guesses) + bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, u0map, tspan; guesses = guess) test_solvers(solvers, bvp, u0map, constr; dt = 0.05) constr = [α * β - x(.6) ~ 0.0, y(.2) ~ 3.] @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses) + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, u0map, tspan; guesses = guess) test_solvers(solvers, bvp, u0map, constr) end From 2fcb9c930c0e4d607d509f1f13a01cef11df6b12 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 3 Feb 2025 15:34:37 -0500 Subject: [PATCH 41/50] up --- src/systems/diffeqs/abstractodesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 95e966a461..056ecd80e4 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -883,7 +883,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] fns = generate_function_bc(sys, u0, u0_idxs, tspan) bc_oop, bc_iip = eval_or_rgf.(fns; eval_expression, eval_module) - # bc(sol, p, t) = bc_oop(sol, p, t) + bc(sol, p, t) = bc_oop(sol, p, t) bc(resid, u, p, t) = bc_iip(resid, u, p, t) return BVProblem{iip}(f, bc, u0, tspan, p; kwargs...) From 25b56d77859cb4afd8c33bf2c48771e81591859e Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 3 Feb 2025 22:03:33 -0500 Subject: [PATCH 42/50] working codegen --- src/systems/diffeqs/abstractodesystem.jl | 8 ++++---- src/systems/diffeqs/odesystem.jl | 2 +- src/systems/optimization/constraints_system.jl | 2 +- test/odesystem.jl | 12 ++++++++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 056ecd80e4..8d8dd80d47 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -899,14 +899,14 @@ get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") """ function generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan; kwargs...) iv = get_iv(sys) - sts = get_unknowns(sys) - ps = get_ps(sys) + sts = unknowns(sys) + ps = parameters(sys) np = length(ps) ns = length(sts) stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) - @variables sol(..)[1:ns] p[1:np] + @variables sol(..)[1:ns] conssys = get_constraintsystem(sys) cons = Any[] @@ -931,7 +931,7 @@ function generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan; kwargs...) exprs = vcat(init_conds, cons) _p = reorder_parameters(sys, ps) - build_function_wrapper(sys, exprs, sol, _p..., t; kwargs...) + build_function_wrapper(sys, exprs, sol, _p..., t; output_type = Array, kwargs...) end """ diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index c38f20c235..44cc7df46b 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -687,7 +687,6 @@ function process_constraint_system(constraints::Vector{Equation}, sts, ps, iv; c constraintsts = OrderedSet() constraintps = OrderedSet() - # Hack? to extract parameters from callable variables in constraints. for cons in constraints collect_vars!(constraintsts, constraintps, cons, iv) end @@ -712,5 +711,6 @@ function process_constraint_system(constraints::Vector{Equation}, sts, ps, iv; c end end + @show constraints ConstraintsSystem(constraints, collect(constraintsts), collect(constraintps); name = consname) end diff --git a/src/systems/optimization/constraints_system.jl b/src/systems/optimization/constraints_system.jl index 275079297a..ecdbafa044 100644 --- a/src/systems/optimization/constraints_system.jl +++ b/src/systems/optimization/constraints_system.jl @@ -123,7 +123,7 @@ function ConstraintsSystem(constraints, unknowns, ps; name === nothing && throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - cstr = value.(Symbolics.canonical_form.(scalarize(constraints))) + cstr = value.(Symbolics.canonical_form.(vcat(scalarize(constraints)...))) unknowns′ = value.(scalarize(unknowns)) ps′ = value.(ps) diff --git a/test/odesystem.jl b/test/odesystem.jl index 98ad6dd87a..97073457b3 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1670,4 +1670,16 @@ end @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) cons = [x(t) * v ~ 3] @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) # Need time argument. + + # Test array variables + @variables x(..)[1:5] + mat = [1 2 0 3 2 + 0 0 3 2 0 + 0 1 3 0 4 + 2 0 0 2 1 + 0 0 2 0 5] + eqs = D(x(t)) ~ mat * x(t) + cons = [x(3) ~ [2,3,3,5,4]] + @mtkbuild ode = ODESystem(D(x(t)) ~ mat * x(t), t; constraints = cons) + @test length(constraints(ModelingToolkit.get_constraintsystem(ode))) == 5 end From c35b7974f2d96584c96cfbffa2de87a390d92d8d Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 3 Feb 2025 22:43:39 -0500 Subject: [PATCH 43/50] revert to OrdinaryDiffEqDefault --- test/bvproblem.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index cedd0eef8d..311a17e172 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -1,6 +1,6 @@ ### TODO: update when BoundaryValueDiffEqAscher is updated to use the normal boundary condition conventions -using OrdinaryDiffEqVerner +using OrdinaryDiffEqDefault using BoundaryValueDiffEqMIRK, BoundaryValueDiffEqAscher using BenchmarkTools using ModelingToolkit @@ -24,7 +24,7 @@ let @mtkbuild lotkavolterra = ODESystem(eqs, t) op = ODEProblem(lotkavolterra, u0map, tspan, parammap) - osol = solve(op, Vern9()) + osol = solve(op) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( lotkavolterra, u0map, tspan, parammap; eval_expression = true) @@ -61,7 +61,7 @@ let tspan = (0.0, 6.0) op = ODEProblem(pend, u0map, tspan, parammap) - osol = solve(op, Vern9()) + osol = solve(op) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) for solver in solvers From 25e84dbf39a92c569dd3a8f4027c9b33dd99f34e Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 3 Feb 2025 23:08:12 -0500 Subject: [PATCH 44/50] use MIRK --- Project.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index c3646021ff..41c5ad0730 100644 --- a/Project.toml +++ b/Project.toml @@ -85,6 +85,7 @@ BifurcationKit = "0.4" BlockArrays = "1.1" BoundaryValueDiffEq = "5.12.0" BoundaryValueDiffEqAscher = "1.1.0" +BoundaryValueDiffEqMIRK = "1.4.0" ChainRulesCore = "1" Combinatorics = "1" CommonSolve = "0.2.4" @@ -106,11 +107,11 @@ DynamicQuantities = "^0.11.2, 0.12, 0.13, 1" EnumX = "1.0.4" ExprTools = "0.1.10" Expronicon = "0.8" +FMI = "0.14" FindFirstFunctions = "1" ForwardDiff = "0.10.3" FunctionWrappers = "1.1" FunctionWrappersWrappers = "0.1" -FMI = "0.14" Graphs = "1.5.2" HomotopyContinuation = "2.11" InfiniteOpt = "0.5" @@ -157,7 +158,7 @@ julia = "1.9" [extras] AmplNLWriter = "7c4d4715-977e-5154-bfe0-e096adeac482" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -BoundaryValueDiffEq = "764a87c0-6b3e-53db-9096-fe964310641d" +BoundaryValueDiffEqMIRK = "1a22d4ce-7765-49ea-b6f2-13c8438986a6" BoundaryValueDiffEqAscher = "7227322d-7511-4e07-9247-ad6ff830280e" ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e" DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0" @@ -190,4 +191,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEq", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve"] +test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEqMIRK", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve"] From e6a6932d192ef72bc46be8ed6124965372e87f26 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 3 Feb 2025 23:09:04 -0500 Subject: [PATCH 45/50] up --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index 41c5ad0730..cd2c2c4380 100644 --- a/Project.toml +++ b/Project.toml @@ -83,7 +83,6 @@ AbstractTrees = "0.3, 0.4" ArrayInterface = "6, 7" BifurcationKit = "0.4" BlockArrays = "1.1" -BoundaryValueDiffEq = "5.12.0" BoundaryValueDiffEqAscher = "1.1.0" BoundaryValueDiffEqMIRK = "1.4.0" ChainRulesCore = "1" From 5e5c24c071f00a8d50b6e7441a141ca9cbeb1681 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 3 Feb 2025 23:30:44 -0500 Subject: [PATCH 46/50] revert to OrdinaryDiffEq --- test/bvproblem.jl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 311a17e172..1d7d4b3793 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -1,6 +1,6 @@ ### TODO: update when BoundaryValueDiffEqAscher is updated to use the normal boundary condition conventions -using OrdinaryDiffEqDefault +using OrdinaryDiffEq using BoundaryValueDiffEqMIRK, BoundaryValueDiffEqAscher using BenchmarkTools using ModelingToolkit @@ -24,10 +24,9 @@ let @mtkbuild lotkavolterra = ODESystem(eqs, t) op = ODEProblem(lotkavolterra, u0map, tspan, parammap) - osol = solve(op) + osol = solve(op, Vern9()) - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( - lotkavolterra, u0map, tspan, parammap; eval_expression = true) + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap) for solver in solvers sol = solve(bvp, solver(), dt = 0.01) @@ -36,8 +35,7 @@ let end # Test out of place - bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}( - lotkavolterra, u0map, tspan, parammap; eval_expression = true) + bvp2 = SciMLBase.BVProblem{false, SciMLBase.AutoSpecialize}(lotkavolterra, u0map, tspan, parammap) for solver in solvers sol = solve(bvp2, solver(), dt = 0.01) From 5338d4f6b60704ebcc1df0c425a0a721f1a46138 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 4 Feb 2025 00:08:54 -0500 Subject: [PATCH 47/50] tests passing --- test/bvproblem.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 1d7d4b3793..b4032e2927 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -59,7 +59,7 @@ let tspan = (0.0, 6.0) op = ODEProblem(pend, u0map, tspan, parammap) - osol = solve(op) + osol = solve(op, Vern9()) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap) for solver in solvers @@ -111,8 +111,8 @@ let end u0 = [1., 2.]; p = [1.5, 1., 1., 3.] - genbc_iip = ModelingToolkit.generate_function_bc(lksys, u0, [1, 2], tspan, true) - genbc_oop = ModelingToolkit.generate_function_bc(lksys, u0, [1, 2], tspan, false) + fns = ModelingToolkit.generate_function_bc(lksys, u0, [1, 2], tspan) + genbc_oop, genbc_iip = ModelingToolkit.eval_or_rgf.(fns) bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1.,2.], tspan, p) bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1.,2.], tspan, p) @@ -141,8 +141,8 @@ let end u0 = [1, 1.] - genbc_iip = ModelingToolkit.generate_function_bc(lksys, u0, [1], tspan, true) - genbc_oop = ModelingToolkit.generate_function_bc(lksys, u0, [1], tspan, false) + fns = ModelingToolkit.generate_function_bc(lksys, u0, [1], tspan) + genbc_oop, genbc_iip = ModelingToolkit.eval_or_rgf.(fns) bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, u0, tspan, p) bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, u0, tspan, p) From 810d4fa26af98e0231da9323bb61053f02fd13ce Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 4 Feb 2025 02:58:11 -0500 Subject: [PATCH 48/50] remove problematic tests, codegen assumes MTKParameters --- test/bvproblem.jl | 44 ++------------------------------------------ 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index b4032e2927..edc85b4cbd 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -100,33 +100,6 @@ let function lotkavolterra(u, p, t) [p[1]*u[1] - p[2]*u[1]*u[2], -p[4]*u[2] + p[3]*u[1]*u[2]] end - # Compare the built bc function to the actual constructed one. - function bc!(resid, u, p, t) - resid[1] = u[1][1] - 1. - resid[2] = u[1][2] - 2. - nothing - end - function bc(u, p, t) - [u[1][1] - 1., u[1][2] - 2.] - end - - u0 = [1., 2.]; p = [1.5, 1., 1., 3.] - fns = ModelingToolkit.generate_function_bc(lksys, u0, [1, 2], tspan) - genbc_oop, genbc_iip = ModelingToolkit.eval_or_rgf.(fns) - - bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, [1.,2.], tspan, p) - bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, [1.,2.], tspan, p) - - sol1 = solve(bvpi1, MIRK4(), dt = 0.05) - sol2 = solve(bvpi2, MIRK4(), dt = 0.05) - @test sol1 ≈ sol2 - - bvpo1 = BVProblem(lotkavolterra, bc, [1,2], tspan, p) - bvpo2 = BVProblem(lotkavolterra, genbc_oop, [1,2], tspan, p) - - sol1 = solve(bvpo1, MIRK4(), dt = 0.05) - sol2 = solve(bvpo2, MIRK4(), dt = 0.05) - @test sol1 ≈ sol2 # Test with a constraint. constr = [y(0.5) ~ 2.] @@ -140,29 +113,16 @@ let [u(0.0)[1] - 1., u(0.5)[2] - 2.] end - u0 = [1, 1.] - fns = ModelingToolkit.generate_function_bc(lksys, u0, [1], tspan) - genbc_oop, genbc_iip = ModelingToolkit.eval_or_rgf.(fns) - bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, u0, tspan, p) - bvpi2 = SciMLBase.BVProblem(lotkavolterra!, genbc_iip, u0, tspan, p) + bvpi1 = SciMLBase.BVProblem(lotkavolterra, bc, u0, tspan, p) bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.]) - bvpi4 = SciMLBase.BVProblem{true, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.]) + bvpi4 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.]) sol1 = @btime solve($bvpi1, MIRK4(), dt = 0.01) sol2 = @btime solve($bvpi2, MIRK4(), dt = 0.01) sol3 = @btime solve($bvpi3, MIRK4(), dt = 0.01) sol4 = @btime solve($bvpi4, MIRK4(), dt = 0.01) @test sol1 ≈ sol2 ≈ sol3 ≈ sol4 # don't get true equality here, not sure why - - bvpo1 = BVProblem(lotkavolterra, bc, u0, tspan, p) - bvpo2 = BVProblem(lotkavolterra, genbc_oop, u0, tspan, p) - bvpo3 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.]) - - sol1 = @btime solve($bvpo1, MIRK4(), dt = 0.05) - sol2 = @btime solve($bvpo2, MIRK4(), dt = 0.05) - sol3 = @btime solve($bvpo3, MIRK4(), dt = 0.05) - @test sol1 ≈ sol2 ≈ sol3 end function test_solvers(solvers, prob, u0map, constraints, equations = []; dt = 0.05, atol = 1e-2) From 6740b8c89b6992e3ddfc1b004f74fe632ef2295d Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 4 Feb 2025 03:31:53 -0500 Subject: [PATCH 49/50] test fix --- test/bvproblem.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index edc85b4cbd..f05be90281 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -113,8 +113,11 @@ let [u(0.0)[1] - 1., u(0.5)[2] - 2.] end + u0 = [1., 1.] + tspan = (0., 1.) + p = [1.5, 1., 1., 3.] bvpi1 = SciMLBase.BVProblem(lotkavolterra!, bc!, u0, tspan, p) - bvpi1 = SciMLBase.BVProblem(lotkavolterra, bc, u0, tspan, p) + bvpi2 = SciMLBase.BVProblem(lotkavolterra, bc, u0, tspan, p) bvpi3 = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.]) bvpi4 = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}(lksys, [x(t) => 1.], tspan; guesses = [y(t) => 1.]) From 603c894044cd6589a0a15a7f8765369393ed9762 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 10 Feb 2025 06:10:32 -0800 Subject: [PATCH 50/50] Update src/systems/diffeqs/odesystem.jl --- src/systems/diffeqs/odesystem.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 44cc7df46b..5b8041ba33 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -711,6 +711,5 @@ function process_constraint_system(constraints::Vector{Equation}, sts, ps, iv; c end end - @show constraints ConstraintsSystem(constraints, collect(constraintsts), collect(constraintps); name = consname) end