diff --git a/docs/src/lib/types.md b/docs/src/lib/types.md index b8706d56..fe5e43d9 100644 --- a/docs/src/lib/types.md +++ b/docs/src/lib/types.md @@ -96,6 +96,12 @@ EulerDiscretization @system ``` +## Initial-value problem macro + +```@docs +@ivp +``` + ## Identity operator ```@docs diff --git a/src/MathematicalSystems.jl b/src/MathematicalSystems.jl index abe05c70..09f209b9 100644 --- a/src/MathematicalSystems.jl +++ b/src/MathematicalSystems.jl @@ -163,7 +163,9 @@ Macros ==========================# include("macros.jl") -export @map +export @map, + @system, + @ivp #=================================== Successor state for discrete systems diff --git a/src/macros.jl b/src/macros.jl index 6ce16dce..ca25573d 100644 --- a/src/macros.jl +++ b/src/macros.jl @@ -2,7 +2,6 @@ using Espresso: matchex using LinearAlgebra: I using MacroTools: @capture using InteractiveUtils: subtypes -export @system import Base: sort @@ -244,7 +243,11 @@ function strip_dynamic_equation(expr) return (nothing, nothing, nothing) end -function parse_system(exprs) +function _parse_system(expr::Expr) + return _parse_system((expr,)) +end + +function _parse_system(exprs::NTuple{N, Expr}) where {N} # define default dynamic equation, unknown abstract system type, # and empty list of constraints dynamic_equation = nothing @@ -729,6 +732,15 @@ function sort(parameters::Vector{<:Tuple{Any, Symbol}}, order::NTuple{N, Symbol} return order_parameters end +function _get_system_type(dyn_eq, AT, constr, state, input, noise, dim) + lhs, rhs = extract_dyn_equation_parameters(dyn_eq, state, input, noise, dim, AT) + ordered_rhs = sort(rhs, (:A, :B, :c, :D, :f, :statedim, :inputdim, :noisedim)) + ordered_set = sort(extract_set_parameter.(constr, state, input, noise), (:X, :U, :W)) + field_names, var_names = constructor_input(lhs, ordered_rhs, ordered_set) + sys_type = _corresponding_type(AT, field_names) + return sys_type, var_names +end + """ system(expr...) @@ -846,19 +858,67 @@ ConstrainedBlackBoxControlDiscreteSystem{typeof(f),BallInf{Float64},BallInf{Floa """ macro system(expr...) try - if typeof(expr) == :Expr - dyn_eq, AT, constr, state, input, noise, dim, x0 = parse_system([expr]) + dyn_eq, AT, constr, state, input, noise, dim, x0 = _parse_system(expr) + sys_type, var_names = _get_system_type(dyn_eq, AT, constr, state, input, noise, dim) + sys = Expr(:call, :($sys_type), :($(var_names...))) + if x0 == nothing + return esc(sys) + else + ivp = Expr(:call, InitialValueProblem, :($sys), :($x0)) + return esc(ivp) + end + catch ex + if isa(ex, ArgumentError) + return :(throw($ex)) else - dyn_eq, AT, constr, state, input, noise, dim, x0 = parse_system(expr) + throw(ex) end - lhs, rhs = extract_dyn_equation_parameters(dyn_eq, state, input, noise, dim, AT) - ordered_rhs = sort(rhs, (:A, :B, :c, :D, :f, :statedim, :inputdim, :noisedim)) - ordered_set = sort(extract_set_parameter.(constr, state, input, noise), (:X, :U, :W)) - field_names, var_names = constructor_input(lhs, ordered_rhs, ordered_set) - sys_type = _corresponding_type(AT, field_names) + end +end + +""" + ivp(expr...) + +Return an instance of the initial-value problem type corresponding to the given +expressions. + +### Input + +- `expr` -- expressions separated by commas which define the dynamic equation, + the constraint sets or the dimensionality of the system, and the set + of initial states (required) + +### Output + +An initial-value problem that best matches the given expressions. + +### Notes + +This macro behaves like the `@system` macro, the sole difference being that in +`@ivp` the constraint on the set of initial states is mandatory. For the technical +details we refer to the documentation of [`@system`](@ref). + +### Examples + +```jldoctest ivp_macro +julia> p = @ivp(x' = -x, x(0) ∈ [1.0]); + +julia> typeof(p) +InitialValueProblem{LinearContinuousSystem{Float64,IdentityMultiple{Float64}},Array{Float64,1}} + +julia> initial_state(p) +1-element Array{Float64,1}: + 1.0 +``` +""" +macro ivp(expr...) + try + dyn_eq, AT, constr, state, input, noise, dim, x0 = _parse_system(expr) + sys_type, var_names = _get_system_type(dyn_eq, AT, constr, state, input, noise, dim) sys = Expr(:call, :($sys_type), :($(var_names...))) if x0 == nothing - return esc(sys) + return throw(ArgumentError("an initial-value problem should define the " * + "initial states, but such expression was not found")) else ivp = Expr(:call, InitialValueProblem, :($sys), :($x0)) return esc(ivp) diff --git a/test/@ivp.jl b/test/@ivp.jl new file mode 100644 index 00000000..57dce27d --- /dev/null +++ b/test/@ivp.jl @@ -0,0 +1,6 @@ +@testset "@ivp for a continuous system" begin + P = @ivp(x' = -x, x(0) ∈ Interval(-1.0, 1.0)) + @test P == InitialValueProblem(LinearContinuousSystem(I(-1.0, 1)), Interval(-1, 1)) + # throw error if x(0) is not defined + @test_throws ArgumentError @ivp(x' = -x) +end diff --git a/test/runtests.jl b/test/runtests.jl index 73e13a78..42b1cede 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -17,3 +17,4 @@ include("successor.jl") include("utilities.jl") include("discretize.jl") include("@system.jl") +include("@ivp.jl")