diff --git a/.github/workflows/Downstream.yml b/.github/workflows/Downstream.yml index 1953828..531d600 100644 --- a/.github/workflows/Downstream.yml +++ b/.github/workflows/Downstream.yml @@ -18,6 +18,7 @@ jobs: os: [ubuntu-latest] package: - {user: SciML, repo: RecursiveArrayTools.jl, group: All} + - {user: JuliaSymbolics, repo: Symbolics.jl, group: SymbolicIndexingInterface} steps: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@v1 diff --git a/Project.toml b/Project.toml index dc81671..82ee6fd 100644 --- a/Project.toml +++ b/Project.toml @@ -1,13 +1,9 @@ name = "SymbolicIndexingInterface" uuid = "2efcf032-c050-4f8e-a9bb-153293bab1f5" authors = ["Aayush Sabharwal and contributors"] -version = "0.2.2" - -[deps] -DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.3.0" [compat] -DocStringExtensions = "0.9" julia = "1" [extras] diff --git a/docs/Project.toml b/docs/Project.toml index f2af07c..c81e0b6 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -4,4 +4,4 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" [compat] Documenter = "0.27" -SymbolicIndexingInterface = "0.2" +SymbolicIndexingInterface = "0.3" diff --git a/docs/make.jl b/docs/make.jl index afd6757..23c8508 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -6,13 +6,13 @@ cp("./docs/Project.toml", "./docs/src/assets/Project.toml", force = true) include("pages.jl") makedocs(sitename = "SymbolicIndexingInterface.jl", - authors = "Chris Rackauckas", - modules = [SymbolicIndexingInterface], - clean = true, doctest = false, - format = Documenter.HTML(analytics = "UA-90474609-3", - assets = ["assets/favicon.ico"], - canonical = "https://docs.sciml.ai/SymbolicIndexingInterface/stable/"), - pages = pages) + authors = "Chris Rackauckas", + modules = [SymbolicIndexingInterface], + clean = true, doctest = false, + format = Documenter.HTML(analytics = "UA-90474609-3", + assets = ["assets/favicon.ico"], + canonical = "https://docs.sciml.ai/SymbolicIndexingInterface/stable/"), + pages = pages) deploydocs(repo = "github.com/SciML/SymbolicIndexingInterface.jl.git"; - push_preview = true) + push_preview = true) diff --git a/docs/src/api.md b/docs/src/api.md index c29ad76..065a104 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -1,20 +1,37 @@ # Interface Functions -Default methods cast all symbols to `Symbol` before comparing. +```@docs +symbolic_container +is_variable +variable_index +variable_symbols +is_parameter +parameter_index +parameter_symbols +is_independent_variable +independent_variable_symbols +is_observed +observed +is_time_dependent +constant_structure +parameter_values +getp +setp +``` + +# Traits ```@docs -independent_variables -is_indep_sym -states -state_sym_to_index -is_state_sym -parameters -param_sym_to_index -is_param_sym +ScalarSymbolic +ArraySymbolic +NotSymbolic +symbolic_type +hasname +getname ``` -## Concrete Types +# Types ```@docs SymbolCache -``` \ No newline at end of file +``` diff --git a/docs/src/index.md b/docs/src/index.md index 6e6f536..7d6b3e9 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,7 +1,7 @@ # SymbolicIndexingInterface.jl: Arrays of Arrays and Even Deeper SymbolicIndexingInterface.jl is a set of interface functions for handling containers -of symbolic variables. It also contains one such container: `SymbolCache`. +of symbolic variables. ## Installation diff --git a/src/SymbolicIndexingInterface.jl b/src/SymbolicIndexingInterface.jl index b0e4199..7a33e0d 100644 --- a/src/SymbolicIndexingInterface.jl +++ b/src/SymbolicIndexingInterface.jl @@ -1,13 +1,17 @@ module SymbolicIndexingInterface -using DocStringExtensions +export ScalarSymbolic, ArraySymbolic, NotSymbolic, symbolic_type, hasname, getname +include("trait.jl") +export is_variable, variable_index, variable_symbols, is_parameter, parameter_index, + parameter_symbols, is_independent_variable, independent_variable_symbols, is_observed, + observed, is_time_dependent, constant_structure, symbolic_container include("interface.jl") -include("symbolcache.jl") -export independent_variables, is_indep_sym, states, state_sym_to_index, is_state_sym, - parameters, param_sym_to_index, is_param_sym, observed, observed_sym_to_index, - is_observed_sym, get_state_dependencies, get_observed_dependencies, - get_deps_of_observed, SymbolCache, unknown_states +export SymbolCache +include("symbol_cache.jl") + +export parameter_values, getp, setp +include("parameter_indexing.jl") end diff --git a/src/interface.jl b/src/interface.jl index 588e1bb..bcce0b2 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -1,162 +1,112 @@ """ -$(TYPEDSIGNATURES) + symbolic_container(p) -Get an iterable over the independent variables for the given system. Default to an empty -vector. -""" -function independent_variables end -independent_variables(::Any) = [] +Using `p`, return an object that implements the symbolic indexing interface. In case `p` +itself implements the interface, `p` can be returned as-is. All symbolic indexing interface +methods fall back to calling the same method on `symbolic_container(p)`, so this may be +used for trivial implementations of the interface that forward all calls to another object. +This is also used by [`ParameterIndexingProxy`](@ref) """ -$(TYPEDSIGNATURES) +function symbolic_container end -Check if the given sym is an independent variable in the given system. Default to checking -if the given `sym` exists in the iterable returned by `independent_variables`. """ -function is_indep_sym end - -function is_indep_sym(store, sym) - any(isequal(Symbol(sym)), Symbol.(independent_variables(store))) -end + is_variable(sys, sym) +Check whether the given `sym` is a variable in `sys`. """ -$(TYPEDSIGNATURES) +is_variable(sys, sym) = is_variable(symbolic_container(sys), sym) -Get an iterable over the states for the given system. Default to an empty vector. """ -function states end - -states(::Any) = [] + variable_index(sys, sym, [i]) +Return the index of the given variable `sym` in `sys`, or `nothing` otherwise. If +[`constant_structure`](@ref) is `false`, this accepts the current time index as an +additional parameter `i`. """ -$(TYPEDSIGNATURES) +variable_index(sys, sym) = variable_index(symbolic_container(sys), sym) +variable_index(sys, sym, i) = variable_index(symbolic_container(sys), sym, i) -Get an iterable over the unknown states for the given system. Default to an empty vector. """ -function unknown_states end - -unknown_states(::Any) = [] + variable_symbols(sys, [i]) +Return a vector of the symbolic variables being solved for in the system `sys`. If +`constant_structure(sys) == false` this accepts an additional parameter indicating +the current time index. The returned vector should not be mutated. """ -$(TYPEDSIGNATURES) +variable_symbols(sys) = variable_symbols(symbolic_container(sys)) +variable_symbols(sys, i) = variable_symbols(symbolic_container(sys), i) -Find the index of the given sym in the given system. Default to the index of the first -symbol in the iterable returned by `states` which matches the given `sym`. Return -`nothing` if the given `sym` does not match. """ -function state_sym_to_index end - -function state_sym_to_index(store, sym) - findfirst(isequal(Symbol(sym)), Symbol.(states(store))) -end + is_parameter(sys, sym) +Check whether the given `sym` is a parameter in `sys`. """ -$(TYPEDSIGNATURES) +is_parameter(sys, sym) = is_parameter(symbolic_container(sys), sym) -Check if the given sym is a state variable in the given system. Default to checking if -the value returned by `state_sym_to_index` is not `nothing`. """ -function is_state_sym end - -is_state_sym(store, sym) = !isnothing(state_sym_to_index(store, sym)) + parameter_index(sys, sym) +Return the index of the given parameter `sym` in `sys`, or `nothing` otherwise. """ -$(TYPEDSIGNATURES) +parameter_index(sys, sym) = parameter_index(symbolic_container(sys), sym) -Get an iterable over the parameters variables for the given system. Default to an empty -vector. """ -function parameters end - -parameters(::Any) = [] + parameter_symbols(sys) +Return a vector of the symbolic parameters of the given system `sys`. The returned +vector should not be mutated. """ -$(TYPEDSIGNATURES) +parameter_symbols(sys) = parameter_symbols(symbolic_container(sys)) -Find the index of the given sym in the given system. Default to the index of the first -symbol in the iterable retruned by `parameters` which matches the given `sym`. Return -`nothing` if the given `sym` does not match. """ -function param_sym_to_index end - -param_sym_to_index(store, sym) = findfirst(isequal(Symbol(sym)), Symbol.(parameters(store))) + is_independent_variable(sys, sym) +Check whether the given `sym` is an independent variable in `sys`. The returned vector +should not be mutated. """ -$(TYPEDSIGNATURES) +is_independent_variable(sys, sym) = is_independent_variable(symbolic_container(sys), sym) -Check if the given sym is a parameter variable in the given system. Default -to checking if the value returned by `param_sym_to_index` is not `nothing`. """ -function is_param_sym end - -is_param_sym(store, sym) = !isnothing(param_sym_to_index(store, sym)) + independent_variable_symbols(sys) +Return a vector of the symbolic independent variables of the given system `sys`. """ -$(TYPEDSIGNATURES) +independent_variable_symbols(sys) = independent_variable_symbols(symbolic_container(sys)) -Get an iterable over the observed variable expressions for the given system. -Default to an empty vector. """ -function observed end - -observed(::Any) = [] + is_observed(sys, sym) +Check whether the given `sym` is an observed value in `sys`. """ -$(TYPEDSIGNATURES) +is_observed(sys, sym) = is_observed(symbolic_container(sys), sym) -Check if the given sym is an observed variable in the given system. Default -to checking if the value returned by `observed_sym_to_index` is not `nothing`. """ -function is_observed_sym end + observed(sys, sym, [states]) -is_observed_sym(store, sym) = !isnothing(observed_sym_to_index(store, sym)) +Return the observed function of the given `sym` in `sys`. The returned function should +have the signature `(u, p) -> [values...]` where `u` and `p` is the current state and +parameter vector. If `istimedependent(sys) == true`, the function should accept +the current time `t` as its third parameter. If `constant_structure(sys) == false`, +accept a third parameter which can either be a vector of symbols indicating the order +of states or a time index which identifies the order of states. +See also: [`is_time_dependent`](@ref), [`constant_structure`](@ref) """ -$(TYPEDSIGNATURES) +observed(sys, sym) = observed(symbolic_container(sys), sym) +observed(sys, sym, states) = observed(symbolic_container(sys), sym, states) -Find the index of the given sym in the given system. Default to the index of the first -symbol in the iterable returned by `states` which matches the given `sym`. Return -`nothing` if the given `sym` does not match. """ -function observed_sym_to_index end - -function observed_sym_to_index(store, sym) - findfirst(o -> isequal(sym, o.lhs), observed(store)) -end + is_time_dependent(sys) +Check if `sys` has time as (one of) its independent variables. """ -$(TYPEDSIGNATURES) +is_time_dependent(sys) = is_time_dependent(symbolic_container(sys)) -Return a list of the dependent state variables of an observed variable. Default to returning -an empty list. """ -function get_state_dependencies end + constant_structure(sys) -get_state_dependencies(store, sym) = [] - -""" -$(TYPEDSIGNATURES) - -Return a list of the dependent observed variables of an observed variable. Default to returning -an empty list. -""" -function get_observed_dependencies end - -get_observed_dependencies(store, sym) = [] - -""" -$(TYPEDSIGNATURES) - -Return a list of the dependent state variables of all observed equations of the system. -Default to returning an empty list. +Check if `sys` has a constant structure. Constant structure systems do not change the +number of variables or parameters over time. """ -function get_deps_of_observed end - -function get_deps_of_observed(store) - obs = observed(store) - deps = mapreduce(vcat, obs, init = []) do eq - get_state_dependencies(store, eq.lhs) - end |> unique - - return deps -end +constant_structure(sys) = constant_structure(symbolic_container(sys)) diff --git a/src/parameter_indexing.jl b/src/parameter_indexing.jl new file mode 100644 index 0000000..62608d8 --- /dev/null +++ b/src/parameter_indexing.jl @@ -0,0 +1,89 @@ +""" + parameter_values(p) + +Return an indexable collection containing the value of each parameter in `p`. +""" +function parameter_values end + +""" + getp(sys, p) + +Return a function that takes an integrator or solution of `sys`, and returns the value of +the parameter `p`. Requires that the integrator or solution implement +[`parameter_values`](@ref). +""" +function getp(sys, p) + symtype = symbolic_type(p) + elsymtype = symbolic_type(eltype(p)) + if symtype != NotSymbolic() + return _getp(sys, symtype, p) + else + return _getp(sys, elsymtype, p) + end +end + +function _getp(sys, ::NotSymbolic, p) + return function getter(sol) + return parameter_values(sol)[p] + end +end + +function _getp(sys, ::ScalarSymbolic, p) + idx = parameter_index(sys, p) + return function getter(sol) + return parameter_values(sol)[idx] + end +end + +function _getp(sys, ::ScalarSymbolic, p::Union{Tuple, AbstractArray}) + idxs = parameter_index.((sys,), p) + return function getter(sol) + return getindex.((parameter_values(sol),), idxs) + end +end + +function _getp(sys, ::ArraySymbolic, p) + return getp(sys, collect(p)) +end + +""" + setp(sys, p) + +Return a function that takes an integrator of `sys` and a value, and sets the +the parameter `p` to that value. Requires that the integrator implement +[`parameter_values`](@ref) and the returned collection be a mutable reference +to the parameter vector in the integrator. +""" +function setp(sys, p) + symtype = symbolic_type(p) + elsymtype = symbolic_type(eltype(p)) + if symtype != NotSymbolic() + return _setp(sys, symtype, p) + else + return _setp(sys, elsymtype, p) + end +end + +function _setp(sys, ::NotSymbolic, p) + return function setter!(sol, val) + parameter_values(sol)[p] = val + end +end + +function _setp(sys, ::ScalarSymbolic, p) + idx = parameter_index(sys, p) + return function setter!(sol, val) + parameter_values(sol)[idx] = val + end +end + +function _setp(sys, ::ScalarSymbolic, p::Union{Tuple, AbstractArray}) + idxs = parameter_index.((sys,), p) + return function setter!(sol, val) + setindex!.((parameter_values(sol),), val, idxs) + end +end + +function _setp(sys, ::ArraySymbolic, p) + return setp(sys, collect(p)) +end diff --git a/src/symbol_cache.jl b/src/symbol_cache.jl new file mode 100644 index 0000000..c2d89fa --- /dev/null +++ b/src/symbol_cache.jl @@ -0,0 +1,80 @@ +""" + struct SymbolCache{V,P,I} + function SymbolCache(vars, [params, [indepvars]]) + +A struct implementing the symbolic indexing interface for the trivial case +of having a vector of variables, parameters and independent variables. This +struct does not implement `observed`, and `is_observed` returns `false` for +all input symbols. It is considered to be time dependent if it contains +at least one independent variable. + +The independent variable may be specified as a single symbolic variable instead of an +array containing a single variable if the system has only one independent variable. +""" +struct SymbolCache{ + V <: Union{Nothing, AbstractVector}, + P <: Union{Nothing, AbstractVector}, + I, +} + variables::V + parameters::P + independent_variables::I +end + +function SymbolCache(vars = nothing, params = nothing, indepvars = nothing) + return SymbolCache{typeof(vars), typeof(params), typeof(indepvars)}(vars, + params, + indepvars) +end + +function is_variable(sc::SymbolCache, sym) + sc.variables !== nothing && any(isequal(sym), sc.variables) +end +function variable_index(sc::SymbolCache, sym) + sc.variables === nothing ? nothing : findfirst(isequal(sym), sc.variables) +end +variable_symbols(sc::SymbolCache, i = nothing) = something(sc.variables, []) +function is_parameter(sc::SymbolCache, sym) + sc.parameters !== nothing && any(isequal(sym), sc.parameters) +end +function parameter_index(sc::SymbolCache, sym) + sc.parameters === nothing ? nothing : findfirst(isequal(sym), sc.parameters) +end +parameter_symbols(sc::SymbolCache) = something(sc.parameters, []) +function is_independent_variable(sc::SymbolCache, sym) + sc.independent_variables === nothing && return false + if symbolic_type(sc.independent_variables) == NotSymbolic() + return any(isequal(sym), sc.independent_variables) + elseif symbolic_type(sc.independent_variables) == ScalarSymbolic() + return sym == sc.independent_variables + else + return any(isequal(sym), collect(sc.independent_variables)) + end +end +function independent_variable_symbols(sc::SymbolCache) + sc.independent_variables === nothing && return [] + if symbolic_type(sc.independent_variables) == NotSymbolic() + return sc.independent_variables + elseif symbolic_type(sc.independent_variables) == ScalarSymbolic() + return [sc.independent_variables] + else + return collect(sc.independent_variables) + end +end +is_observed(sc::SymbolCache, sym) = false +function is_time_dependent(sc::SymbolCache) + sc.independent_variables === nothing && return false + if symbolic_type(sc.independent_variables) == NotSymbolic() + return !isempty(sc.independent_variables) + else + return true + end +end +constant_structure(::SymbolCache) = true + +function Base.copy(sc::SymbolCache) + return SymbolCache(sc.variables === nothing ? nothing : copy(sc.variables), + sc.parameters === nothing ? nothing : copy(sc.parameters), + sc.independent_variables isa AbstractArray ? copy(sc.independent_variables) : + sc.independent_variables) +end diff --git a/src/symbolcache.jl b/src/symbolcache.jl deleted file mode 100644 index f9583ff..0000000 --- a/src/symbolcache.jl +++ /dev/null @@ -1,26 +0,0 @@ -""" - SymbolCache(syms, indepsym, paramsyms) - -A container that simply stores a vector of all syms, indepsym and paramsyms. -""" -struct SymbolCache{S, T, U} - syms::S - indepsym::T - paramsyms::U -end - -independent_variables(sc::SymbolCache) = sc.indepsym -independent_variables(::SymbolCache{S, Nothing}) where {S} = [] -is_indep_sym(::SymbolCache{S, Nothing}, _) where {S} = false -states(sc::SymbolCache) = sc.syms -states(::SymbolCache{Nothing}) = [] -state_sym_to_index(::SymbolCache{Nothing}, _) = nothing -parameters(sc::SymbolCache) = sc.paramsyms -parameters(::SymbolCache{S, T, Nothing}) where {S, T} = [] -param_sym_to_index(::SymbolCache{S, T, Nothing}, _) where {S, T} = nothing - -function Base.copy(VA::SymbolCache) - typeof(VA)((VA.syms === nothing) ? nothing : copy(VA.syms), - (VA.indepsym === nothing) ? nothing : copy(VA.indepsym), - (VA.paramsyms === nothing) ? nothing : copy(VA.paramsyms)) -end diff --git a/src/trait.jl b/src/trait.jl new file mode 100644 index 0000000..6660e91 --- /dev/null +++ b/src/trait.jl @@ -0,0 +1,61 @@ +abstract type SymbolicTypeTrait end + +""" + struct ScalarSymbolic <: SymbolicTypeTrait end + +Trait indicating a type is a scalar symbolic variable. + +See also: [`ArraySymbolic`](@ref), [`NotSymbolic`](@ref), [`symbolic_type`](@ref) +""" +struct ScalarSymbolic <: SymbolicTypeTrait end + +""" + struct ArraySymbolic <: SymbolicTypeTrait end + +Trait indicating type is a symbolic array. Calling `collect` on a symbolic array must +return an `AbstractArray` containing `ScalarSymbolic` variables for each element in the +array, in the same shape as the represented array. For example, if `a` is a symbolic array +representing a 2x2 matrix, `collect(a)` must return a 2x2 array of scalar symbolic variables. + +See also: [`ScalarSymbolic`](@ref), [`NotSymbolic`](@ref), [`symbolic_type`](@ref) +""" +struct ArraySymbolic <: SymbolicTypeTrait end + +""" + struct NotSymbolic <: SymbolicTypeTrait end + +Trait indicating a type is not symbolic. + +See also: [`ScalarSymbolic`](@ref), [`ArraySymbolic`](@ref), [`symbolic_type`](@ref) +""" +struct NotSymbolic <: SymbolicTypeTrait end + +""" + symbolic_type(x) = symbolic_type(typeof(x)) + symbolic_type(::Type) + +Get the symbolic type trait of a type. Default to [`NotSymbolic`](@ref) for all types +except `Symbol`. + +See also: [`ScalarSymbolic`](@ref), [`ArraySymbolic`](@ref), [`NotSymbolic`](@ref) +""" +symbolic_type(x) = symbolic_type(typeof(x)) +symbolic_type(::Type) = NotSymbolic() +symbolic_type(::Type{Symbol}) = ScalarSymbolic() + +""" + hasname(x) + +Check whether the given symbolic variable (for which `symbolic_type(x) != NotSymbolic()`) has a valid name as per `getname`. +""" +function hasname end + +hasname(::Symbol) = true +hasname(::Any) = false + +""" + getname(x)::Symbol + +Get the name of a symbolic variable as a `Symbol` +""" +function getname end diff --git a/test/default_function_test.jl b/test/default_function_test.jl deleted file mode 100644 index be7b92e..0000000 --- a/test/default_function_test.jl +++ /dev/null @@ -1,15 +0,0 @@ -using SymbolicIndexingInterface, Test - -@test independent_variables(nothing) == [] -@test states(nothing) == [] -@test parameters(nothing) == [] -@test observed(nothing) == [] -@test !is_indep_sym(nothing, :a) -@test !is_state_sym(nothing, :a) -@test !is_param_sym(nothing, :a) -@test !is_observed_sym(nothing, :a) -@test isnothing(state_sym_to_index(nothing, :a)) -@test isnothing(param_sym_to_index(nothing, :a)) -@test get_state_dependencies(nothing, :a) == [] -@test get_observed_dependencies(nothing, :a) == [] -@test get_deps_of_observed(nothing) == [] diff --git a/test/example_test.jl b/test/example_test.jl new file mode 100644 index 0000000..260e8aa --- /dev/null +++ b/test/example_test.jl @@ -0,0 +1,117 @@ +struct SystemMockup + static::Bool + vars::Vector{Symbol} + params::Vector{Symbol} + indepvar::Union{Symbol, Nothing} +end + +SymbolicIndexingInterface.is_variable(sys::SystemMockup, sym) = sym in sys.vars +function SymbolicIndexingInterface.variable_index(sys::SystemMockup, sym, t = nothing) + if !constant_structure(sys) && t === nothing + error("time index must be present") + end + findfirst(isequal(sym), variable_symbols(sys, t)) +end +function SymbolicIndexingInterface.variable_symbols(sys::SystemMockup, i = nothing) + return constant_structure(sys) ? sys.vars : circshift(sys.vars, i) +end +SymbolicIndexingInterface.is_parameter(sys::SystemMockup, sym) = sym in sys.params +function SymbolicIndexingInterface.parameter_index(sys::SystemMockup, sym) + findfirst(isequal(sym), sys.params) +end +SymbolicIndexingInterface.parameter_symbols(sys::SystemMockup) = sys.params +function SymbolicIndexingInterface.is_independent_variable(sys::SystemMockup, sym) + sys.indepvar !== nothing && isequal(sym, sys.indepvar) +end +function SymbolicIndexingInterface.independent_variable_symbols(sys::SystemMockup) + if sys.indepvar === nothing + return [] + else + return [sys.indepvar] + end +end +function SymbolicIndexingInterface.is_observed(sys::SystemMockup, sym) + is_variable(sys, sym) || is_parameter(sys, sym) || is_independent_variable(sys, sym) +end +function SymbolicIndexingInterface.observed(sys::SystemMockup, sym, states = nothing) + if !constant_structure(sys) && states === nothing + error("States required") + end + states = states isa Vector ? states : variable_symbols(sys, states) + if is_variable(sys, sym) + return is_time_dependent(sys) ? + (u, p, t) -> u[findfirst(isequal(sym), states)] : + (u, p) -> u[findfirst(isequal(sym), states)] + end + idx = parameter_index(sys, sym) + if idx !== nothing + return is_time_dependent(sys) ? (u, p, t) -> p[idx] : (u, p) -> p[idx] + end + if is_independent_variable(sys, sym) + return is_time_dependent(sys) ? (u, p, t) -> t : (u, p) -> 1 + end +end +SymbolicIndexingInterface.is_time_dependent(sys::SystemMockup) = isequal(sys.indepvar, :t) +SymbolicIndexingInterface.constant_structure(sys::SystemMockup) = sys.static + +sys = SystemMockup(true, [:x, :y, :z], [:a, :b, :c], :t) + +@test all(is_variable.((sys,), [:x, :y, :z])) +@test all(.!is_variable.((sys,), [:a, :b, :c, :t, :p, :q, :r])) +@test all(variable_index.((sys,), [:x, :z, :y]) .== [1, 3, 2]) +@test all(variable_index.((sys,), [:a, :b, :c, :t, :p, :q, :r]) .=== nothing) +@test all(is_parameter.((sys,), [:a, :b, :c])) +@test all(.!is_parameter.((sys,), [:x, :y, :z, :t, :p, :q, :r])) +@test all(parameter_index.((sys,), [:c, :a, :b]) .== [3, 1, 2]) +@test all(parameter_index.((sys,), [:x, :y, :z, :t, :p, :q, :r]) .=== nothing) +@test is_independent_variable(sys, :t) +@test all(.!is_independent_variable.((sys,), [:x, :y, :z, :a, :b, :c, :p, :q, :r])) +@test all(is_observed.((sys,), [:x, :y, :z, :a, :b, :c, :t])) +@test all(observed(sys, :x)(1:3, 4:6, 1.5) .== 1) +@test all(observed(sys, :y)(1:3, 4:6, 1.5) .== 2) +@test all(observed(sys, :z)(1:3, 4:6, 1.5) .== 3) +@test observed(sys, :a)(1:3, 4:6, 1.5) == 4 +@test observed(sys, :b)(1:3, 4:6, 1.5) == 5 +@test observed(sys, :c)(1:3, 4:6, 1.5) == 6 +@test observed(sys, :t)(1:3, 4:6, 1.5) == 1.5 +@test is_time_dependent(sys) +@test constant_structure(sys) +@test variable_symbols(sys) == [:x, :y, :z] +@test parameter_symbols(sys) == [:a, :b, :c] +@test independent_variable_symbols(sys) == [:t] + +sys = SystemMockup(true, [:x, :y, :z], [:a, :b, :c], nothing) + +@test !is_time_dependent(sys) +@test all(observed(sys, :x)(1.0:3.0, 4:6) .== 1.0) +@test all(observed(sys, :y)(1.0:3.0, 4:6) .== 2.0) +@test all(observed(sys, :z)(1.0:3.0, 4:6) .== 3.0) +@test observed(sys, :a)(1:3, 4:6) == 4 +@test observed(sys, :b)(1:3, 4:6) == 5 +@test observed(sys, :c)(1:3, 4:6) == 6 +@test constant_structure(sys) +@test variable_symbols(sys) == [:x, :y, :z] +@test parameter_symbols(sys) == [:a, :b, :c] +@test independent_variable_symbols(sys) == [] + +sys = SystemMockup(false, [:x, :y, :z], [:a, :b, :c], :t) +@test !constant_structure(sys) +for variable in [:x, :y, :z, :a, :b, :c, :t] + @test_throws ErrorException variable_index(sys, variable) + @test_throws ErrorException observed(sys, variable) +end +@test all(variable_index.((sys,), [:z, :y, :x], 1) .== [1, 3, 2]) +@test all(variable_index.((sys,), [:a, :b, :c, :t], 1) .== nothing) +@test all(observed(sys, :x, 2)(1:3, 4:6, 1.5) .== 3) +@test all(observed(sys, :y, 2)(1:3, 4:6, 1.5) .== 1) +@test all(observed(sys, :z, 2)(1:3, 4:6, 1.5) .== 2) +@test observed(sys, :a, 2)(1:3, 4:6, 1.5) == 4 +@test observed(sys, :b, 2)(1:3, 4:6, 1.5) == 5 +@test observed(sys, :c, 2)(1:3, 4:6, 1.5) == 6 +@test observed(sys, :t, 2)(1:3, 4:6, 1.5) == 1.5 +@test_throws Exception variable_symbols(sys) +@test variable_symbols(sys, 1) == [:z, :x, :y] +@test variable_symbols(sys, 2) == [:y, :z, :x] +@test variable_symbols(sys, 3) == [:x, :y, :z] +@test parameter_symbols(sys) == [:a, :b, :c] +@test independent_variable_symbols(sys) == [:t] diff --git a/test/fallback_test.jl b/test/fallback_test.jl new file mode 100644 index 0000000..4c354bf --- /dev/null +++ b/test/fallback_test.jl @@ -0,0 +1,24 @@ +using SymbolicIndexingInterface + +struct Wrapper{W} + wrapped::W +end + +SymbolicIndexingInterface.symbolic_container(w::Wrapper) = w.wrapped + +sc = SymbolCache([:x, :y, :z], [:a, :b], [:t]) +sys = Wrapper(sc) + +all_syms = [:x, :y, :z, :a, :b, :t] +@test is_variable.((sys,), all_syms) == is_variable.((sc,), all_syms) +@test variable_index.((sys,), all_syms) == variable_index.((sc,), all_syms) +@test is_parameter.((sys,), all_syms) == is_parameter.((sc,), all_syms) +@test parameter_index.((sys,), all_syms) == parameter_index.((sc,), all_syms) +@test is_independent_variable.((sys,), all_syms) == + is_independent_variable.((sc,), all_syms) +@test is_observed.((sys,), all_syms) == is_observed.((sc,), all_syms) +@test is_time_dependent(sys) == is_time_dependent(sc) +@test constant_structure(sys) == constant_structure(sc) +@test variable_symbols(sys) == variable_symbols(sc) +@test parameter_symbols(sys) == parameter_symbols(sc) +@test independent_variable_symbols(sys) == independent_variable_symbols(sc) diff --git a/test/parameter_indexing_test.jl b/test/parameter_indexing_test.jl new file mode 100644 index 0000000..66b1893 --- /dev/null +++ b/test/parameter_indexing_test.jl @@ -0,0 +1,21 @@ +using SymbolicIndexingInterface + +struct FakeIntegrator{P} + p::P +end + +SymbolicIndexingInterface.symbolic_container(fp::FakeIntegrator) = fp.sys +SymbolicIndexingInterface.parameter_values(fp::FakeIntegrator) = fp.p + +sys = SymbolCache([:x, :y, :z], [:a, :b], [:t]) +p = [1.0, 2.0] +fi = FakeIntegrator(copy(p)) +for (i, sym) in [(1, :a), (2, :b), ([1, 2], [:a, :b]), ((1, 2), (:a, :b))] + get = getp(sys, sym) + set! = setp(sys, sym) + true_value = i isa Tuple ? getindex.((p,), i) : p[i] + @test get(fi) == true_value + set!(fi, 0.5 .* i) + @test get(fi) == 0.5 .* i + set!(fi, true_value) +end diff --git a/test/runtests.jl b/test/runtests.jl index 50a538a..20538d7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,18 @@ using SymbolicIndexingInterface using Test -@time begin @time @testset begin include("symbolcache.jl") end end -@time begin @time @testset begin include("default_function_test.jl") end end +@testset "Interface test" begin + @time include("example_test.jl") +end +@testset "Trait test" begin + @time include("trait_test.jl") +end +@testset "SymbolCache test" begin + @time include("symbol_cache_test.jl") +end +@testset "Fallback test" begin + @time include("fallback_test.jl") +end +@testset "Parameter indexing test" begin + @time include("parameter_indexing_test.jl") +end diff --git a/test/symbol_cache_test.jl b/test/symbol_cache_test.jl new file mode 100644 index 0000000..8754b4f --- /dev/null +++ b/test/symbol_cache_test.jl @@ -0,0 +1,43 @@ +sc = SymbolCache([:x, :y, :z], [:a, :b], [:t]) + +@test all(is_variable.((sc,), [:x, :y, :z])) +@test all(.!is_variable.((sc,), [:a, :b, :t, :q])) +@test variable_index.((sc,), [:x, :y, :z, :a]) == [1, 2, 3, nothing] +@test all(is_parameter.((sc,), [:a, :b])) +@test all(.!is_parameter.((sc,), [:x, :y, :z, :t, :q])) +@test parameter_index.((sc,), [:a, :b, :x]) == [1, 2, nothing] +@test is_independent_variable(sc, :t) +@test all(.!is_independent_variable.((sc,), [:x, :y, :z, :a, :b, :q])) +@test all(.!is_observed.((sc,), [:x, :y, :z, :a, :b, :t, :q])) +@test is_time_dependent(sc) +@test constant_structure(sc) +@test variable_symbols(sc) == [:x, :y, :z] +@test parameter_symbols(sc) == [:a, :b] +@test independent_variable_symbols(sc) == [:t] + +sc = SymbolCache([:x, :y], [:a, :b]) +@test !is_time_dependent(sc) +# make sure the constructor works +@test_nowarn SymbolCache([:x, :y]) + +sc = SymbolCache() +@test all(.!is_variable.((sc,), [:x, :y, :a, :b, :t])) +@test all(variable_index.((sc,), [:x, :y, :a, :b, :t]) .== nothing) +@test variable_symbols(sc) == [] +@test all(.!is_parameter.((sc,), [:x, :y, :a, :b, :t])) +@test all(parameter_index.((sc,), [:x, :y, :a, :b, :t]) .== nothing) +@test parameter_symbols(sc) == [] +@test all(.!is_independent_variable.((sc,), [:x, :y, :a, :b, :t])) +@test independent_variable_symbols(sc) == [] +@test !is_time_dependent(sc) + +sc = SymbolCache(nothing, nothing, :t) +@test all(.!is_independent_variable.((sc,), [:x, :y, :a, :b])) +@test is_independent_variable(sc, :t) +@test independent_variable_symbols(sc) == [:t] +@test is_time_dependent(sc) + +sc2 = copy(sc) +@test sc.variables == sc2.variables +@test sc.parameters == sc2.parameters +@test sc.independent_variables == sc2.independent_variables diff --git a/test/symbolcache.jl b/test/symbolcache.jl deleted file mode 100644 index 04ca4fa..0000000 --- a/test/symbolcache.jl +++ /dev/null @@ -1,28 +0,0 @@ -using SymbolicIndexingInterface, Test - -sc = SymbolCache(nothing, nothing, nothing) -@test isempty(independent_variables(sc)) -@test !is_indep_sym(sc, :a) -@test isempty(states(sc)) -@test isnothing(state_sym_to_index(sc, :a)) -@test !is_state_sym(sc, :a) -@test isempty(parameters(sc)) -@test isnothing(param_sym_to_index(sc, :a)) -@test !is_param_sym(sc, :a) - -sc = SymbolCache([:a, :b], [:t], [:c, :d]) -@test independent_variables(sc) == [:t] -@test is_indep_sym(sc, :t) -@test !is_indep_sym(sc, :a) -@test states(sc) == [:a, :b] -@test state_sym_to_index(sc, :a) == 1 -@test state_sym_to_index(sc, :b) == 2 -@test isnothing(state_sym_to_index(sc, :t)) -@test all(is_state_sym.((sc,), [:a, :b])) -@test !is_state_sym(sc, :c) -@test parameters(sc) == [:c, :d] -@test param_sym_to_index(sc, :c) == 1 -@test param_sym_to_index(sc, :d) == 2 -@test isnothing(param_sym_to_index(sc, :a)) -@test all(is_param_sym.((sc,), [:c, :d])) -@test !is_param_sym(sc, :b) diff --git a/test/trait_test.jl b/test/trait_test.jl new file mode 100644 index 0000000..de7eb85 --- /dev/null +++ b/test/trait_test.jl @@ -0,0 +1,6 @@ +using SymbolicIndexingInterface +using Test + +@test all(symbolic_type.([Int, Float64, String, Bool, UInt, Complex{Float64}]) .== + (NotSymbolic(),)) +@test symbolic_type(Symbol) == ScalarSymbolic()