diff --git a/Project.toml b/Project.toml index 39a770b..c0863e1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "GraphDynamics" uuid = "bcd5d0fe-e6b7-4ef1-9848-780c183c7f4c" -version = "0.5.0" +version = "0.6.0" [workspace] projects = ["test", "scrap"] diff --git a/src/GraphDynamics.jl b/src/GraphDynamics.jl index 33422ed..e26f07e 100644 --- a/src/GraphDynamics.jl +++ b/src/GraphDynamics.jl @@ -208,11 +208,11 @@ states.x # 1.0 function get_states end """ - computed_properties(::Subsystem{T}) :: NamedTuple{props, NTuple{N, funcs}} + computed_properties(::Type{T}) :: NamedTuple{props, NTuple{N, funcs}} Signal that a subsystem has properties which can be computed on-the-fly based on it's existing properties. In the termoinology used by ModelingToolkit.jl, these are "observed states". -This function takes in a `Subsystem` and returns a `NamedTuple` where each key is a property name that can be computed, and each value is a function that takes in the subsystem and returns a computed value. +This function takes in a tag type `T` and returns a `NamedTuple` where each key is a property name that can be computed, and each value is a function that takes in a `Subsystem{T}` and returns a computed value. By default, this function returns an empty NamedTuple. @@ -220,7 +220,7 @@ Example: ```julia struct Position end -function GraphDynamics.computed_properties(::Subsystem{Position}) +function GraphDynamics.computed_properties(::Type{Position}) (;r = (;x, y) -> √(x^2 + y^2), θ = (;x, y) -> atan(y, x)) end @@ -229,22 +229,25 @@ let sys = Subsystem{Position}(states=(x=1, y=2), params=(;)) sys.r == √(sys.x^2 + sys.y^2) end ``` -""" -computed_properties(s::Subsystem) = (;) +Implementing this method will make the the reported properties work with SymbolicIndexingInterface.jl +""" +computed_properties(s::Type{<:Any}) = (;) +computed_properties(s::Type{Union{}}) = error("This should be unreachable") """ - computed_properties_with_inputs(s::Subsystem) + computed_properties_with_inputs(::Type{T}) :: NamedTuple{props, NTuple{N, funcs}} Signal that a subsystem has properties which can be computed on-the-fly based on it's existing properties. In the termoinology used by ModelingToolkit.jl, these are "observed states", but they also require the inputs to the subsystem to compute (and thus are non-local to the subsystem). This function takes in a `Subsystem` and returns a `NamedTuple` where each key is a property name that can be computed, and each value is a function that takes in the subsystem and returns a computed value. -By default, this function returns an empty NamedTuple. +By default, this function returns an empty `NamedTuple`. -This is typically only usable on objects like ODESolutions. +Implementing this method will make the the reported properties work with SymbolicIndexingInterface.jl. """ -computed_properties_with_inputs(s::Subsystem) = (;) +computed_properties_with_inputs(::Type{<:Any}) = (;) +computed_properties_with_inputs(s::Type{Union{}}) = error("This should be unreachable") """ subsystem_differential(subsystem, input, t) diff --git a/src/subsystems.jl b/src/subsystems.jl index 37d03ba..92e1b23 100644 --- a/src/subsystems.jl +++ b/src/subsystems.jl @@ -191,8 +191,8 @@ get_tag(::Subsystem{Name}) where {Name} = Name get_tag(::Type{<:Subsystem{Name}}) where {Name} = Name -function Base.getproperty(s::Subsystem{<:Any, States, Params}, - prop::Symbol) where {States, Params} +function Base.getproperty(s::Subsystem{Name, States, Params}, + prop::Symbol) where {Name, States, Params} states = NamedTuple(get_states(s)) params = NamedTuple(get_params(s)) if prop ∈ keys(states) @@ -200,7 +200,7 @@ function Base.getproperty(s::Subsystem{<:Any, States, Params}, elseif prop ∈ keys(params) getproperty(params, prop) else - comp_props = computed_properties(s) + comp_props = computed_properties(Name) if prop ∈ keys(comp_props) comp_props[prop](s) else @@ -209,7 +209,7 @@ function Base.getproperty(s::Subsystem{<:Any, States, Params}, end end @noinline subsystem_prop_err(s::Subsystem{Name}, prop) where {Name} = error(ArgumentError( - "property $(prop) of ::Subsystem{$Name} not found, valid properties are $(propertynames(merge(NamedTuple(get_states(s)), NamedTuple(get_params(s)))))" + "property $(prop) of ::Subsystem{$Name} not found, valid properties are $(propertynames(merge(NamedTuple(get_states(s)), NamedTuple(get_params(s)), computed_properties(Name))))" )) Base.eltype(::Subsystem{<:Any, T}) where {T} = T diff --git a/src/symbolic_indexing.jl b/src/symbolic_indexing.jl index f31fc16..daab92e 100644 --- a/src/symbolic_indexing.jl +++ b/src/symbolic_indexing.jl @@ -64,20 +64,21 @@ end function make_compu_namemap(names_partitioned::NTuple{N, Vector{Symbol}}, - states_partitioned::NTuple{N, AbstractVector{<:SubsystemStates}}, - params_partitioned::NTuple{N, AbstractVector{<:SubsystemParams}}) where {N} + states_partitioned::NTuple{N, AbstractVector{<:SubsystemStates}}, + params_partitioned::NTuple{N, AbstractVector{<:SubsystemParams}}) where {N} namemap = OrderedDict{Symbol, CompuIndex}() for i ∈ eachindex(names_partitioned, states_partitioned, params_partitioned) for j ∈ eachindex(names_partitioned[i], states_partitioned[i], params_partitioned[i]) states = states_partitioned[i][j] params = params_partitioned[i][j] sys = Subsystem(states, params) - for name ∈ keys(computed_properties(sys)) + tag = get_tag(sys) + for name ∈ keys(computed_properties(tag)) requires_inputs = false propname = Symbol(names_partitioned[i][j], "₊", name) namemap[propname] = CompuIndex(i, j, name, requires_inputs) end - for name ∈ keys(computed_properties_with_inputs(sys)) + for name ∈ keys(computed_properties_with_inputs(tag)) requires_inputs = true propname = Symbol(names_partitioned[i][j], "₊", name) namemap[propname] = CompuIndex(i, j, name, requires_inputs) @@ -233,8 +234,8 @@ function SymbolicIndexingInterface.observed(sys::PartitionedGraphSystem, sym) i = valueof(val_tup_index) subsys = Subsystem(states_partitioned[i][v_index], params_partitioned[i][v_index]) input = calculate_inputs(val_tup_index, v_index, states_partitioned, params_partitioned, connection_matrices, t) - - comp_props = computed_properties_with_inputs(subsys) + tag = get_tag(subsys) + comp_props = computed_properties_with_inputs(tag) comp_props[prop](subsys, input) end else diff --git a/test/particle_osc_example.jl b/test/particle_osc_example.jl index d0b11c9..dae73b0 100644 --- a/test/particle_osc_example.jl +++ b/test/particle_osc_example.jl @@ -40,7 +40,7 @@ function GraphDynamics.subsystem_differential(sys::Subsystem{Particle}, input, t # Return the differential of the current state: SubsystemStates{Particle}(;x=dx, v=dv) end -function GraphDynamics.computed_properties_with_inputs(::Subsystem{Particle}) +function GraphDynamics.computed_properties_with_inputs(::Type{Particle}) a(sys, input) = input.F / sys.m (; a) end @@ -80,7 +80,7 @@ function GraphDynamics.subsystem_differential(sys::Subsystem{Oscillator}, input, dv = (F - k*(x - x₀))/m # Derivative of v is the acceleration due to the input force, and the acceleration due to the spring. SubsystemStates{Oscillator}(;x=dx, v=dv) end -GraphDynamics.computed_properties(::Subsystem{Oscillator}) = (;ω₀ = ((;m, k),) -> √(k/m)) +GraphDynamics.computed_properties(::Type{Oscillator}) = (;ω₀ = ((;m, k),) -> √(k/m)) # Now lets construct some `ConnectionRule`s struct Spring{T} <: ConnectionRule