Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "GraphDynamics"
uuid = "bcd5d0fe-e6b7-4ef1-9848-780c183c7f4c"
version = "0.5.0"
version = "0.6.0"

[workspace]
projects = ["test", "scrap"]
Expand Down
21 changes: 12 additions & 9 deletions src/GraphDynamics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -208,19 +208,19 @@ 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.

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
Expand All @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions src/subsystems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -191,16 +191,16 @@ 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)
getproperty(states, prop)
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
Expand All @@ -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
Expand Down
13 changes: 7 additions & 6 deletions src/symbolic_indexing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions test/particle_osc_example.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down