BlockSystems
Basics
An input-output-system is characterized by a set of equations. These equations can be either first order ODEs or explicit algebraic equations.
\[\begin{aligned} \dot{\mathbf x}(t) &= f(\mathbf x(t), \mathbf y(t), \mathbf i(t), p)\\ \mathbf y(t) &= g(\mathbf x(t), \mathbf y(t), \mathbf i(t), p) \end{aligned}\]
such system contains of
- states ($x$ and $y$) and
- parameters ($i$, $p$).
States are determined by the given equations (i.e. the equations describe how the states change). Parameters are externally given. For IO systems we define subgroups
- states
- internal states (
istates
) which are meant for internal use - output states (
outputs
) which might be used as inputs for other systems
- internal states (
- parameters
- internal parameters (
iparams
) which are typically constant and - inputs (
inputs
) which can be connected to the outputs of other systems.
- internal parameters (
Types
The base type for the BlockSystems is AbstractIOSystem
with the has two concrete implementations.
BlockSystems.AbstractIOSystem
— TypeAn IOBlock
consists of a set of equations, a set of inputs and outputs and a name.
BlockSystems.IOBlock
— Typestruct IOBlock <: AbstractIOSystem
A basic IOSystem which consists of a single ODESystem.
name::Symbol
inputs::Vector{SymbolicUtils.Symbolic}
iparams::Vector{SymbolicUtils.Symbolic}
istates::Vector{SymbolicUtils.Symbolic}
outputs::Vector{SymbolicUtils.Symbolic}
system::ODESystem
removed_states::Vector{SymbolicUtils.Symbolic}
removed_eqs::Vector{Equation}
BlockSystems.IOBlock
— MethodIOBlock(eqs, inputs, outputs; name, iv, warn)
Construct a new IOBlock for the given arguments.
using BlockSystems, ModelingToolkit
@parameters t i(t)
@variables x(t) o(t)
D = Differential(t)
iob = IOBlock([D(x) ~ i, o ~ x], [i], [o], name=:iob)
An IOSystem
consists of multiple AbstractIOSystems
and the connections between them.
BlockSystems.IOSystem
— Typestruct IOSystem <: AbstractIOSystem
A composite IOSystem
which consists of multiple AbstractIOSystem
which are connected via a vector of namespaced pairs (subsys1.out => subsys2.in
).
An IOSystem
contains maps how to promote the namespaced variables of the subsystem to the new scope subsys1₊x(t) => x(t) subsys1₊y(t) => subsys1₊y(t) subsys2₊y(t) => subsys2₊y(t)
name::Symbol
inputs::Vector{SymbolicUtils.Symbolic}
iparams::Vector{SymbolicUtils.Symbolic}
istates::Vector{SymbolicUtils.Symbolic}
outputs::Vector{SymbolicUtils.Symbolic}
removed_states::Vector{SymbolicUtils.Symbolic}
connections::Vector{Pair{SymbolicUtils.Symbolic, SymbolicUtils.Symbolic}}
namespace_map::Dict{SymbolicUtils.Symbolic, SymbolicUtils.Symbolic}
systems::Vector{AbstractIOSystem}
BlockSystems.IOSystem
— MethodIOSystem(cons, io_systems; namespace_map, outputs, name, autopromote)
Construct a new IOSystem from various subsystems. Arguments:
cons
:The connections in the form
sub1.output => sub2.input
. It is also possible to use simple algebraic equations such assub1.o1 + sub2.o2 => sub3.input
.io_systems
: Vector of subsystemsnamespace_map
: Provide collection of custom namespace promotions / renamings i.e. sub1.input => voltage. Variables without entry in the map will be promoted automatically. Automatic promotion means that the sub-namespace is removed whenever it is possible without naming conflicts. The map may contain inputs, outputs, istates, iparams and removed_states. The rhs of the map can be provided as as Symbol:
sub1.input => :newname`.outputs
: Per default, all of the subsystem outputs will become system outputs. However, by providing a list of variables as outputs only these will become outputs of the new system. All other sub-outputs will become internal states of the connected system (and might be optimized away inconnect_system
).name
: namespaceautopromote=true
: enable/disable automatic promotion of variable names to system namespace
Transformations
Each IOSystem
can be transformed into an IOBlock
. At this step the actual manipulation of the equations happen.
BlockSystems.connect_system
— Functionconnect_system(ios; verbose, simplify_eqs, remove_superflous_states, substitute_algebraic_states, substitute_derivatives, warn)
Recursively transform IOSystems
to IOBlocks
.
- substitute inputs with connected outputs
- try to eliminate equations for internal states which are not used to calculate the specified outputs of the system.
- try to eliminate explicit algebraic equations (i.e. outputs of internal blocks) by substituting each occurrence with their rhs. Explicit algebraic states which are marked as system outputs won't be removed.
Arguments:
ios
: system to connectverbose=false
: toggle verbosity (show equations at different steps)remove_superflous_states=true
: toggle whether the system should try to get rid of unused statessubstitute_algebraic_states=true
: toggle whether the algorithm tries to get rid of explicit algebraic equationssubstitute_derivatives=true
: toggle whether to expand all derivatives and try to substitute themsimplify_eqs=true
: toggle simplification of all equations at the end
There are also other transformations available
BlockSystems.rename_vars
— Functionrename_vars(blk::IOBLock; warn=true, kwargs...)
rename_vars(blk::IOBlock, subs::Dict{Symbolic,Symbolic}; warn=true)
Returns new IOBlock which is similar to blk but with new variable names. Variable renaming should be provided as keyword arguments, i.e.
rename_vars(blk; x=:newx, k=:knew)
to rename x(t)=>newx(t)
and k=>knew
. Substitutions can be also provided as dict of Symbolic
types (Sym
s and Term
s).
BlockSystems.remove_superfluous_states
— Functionremove_superfluous_states(iob::IOBlock; verbose=false, warn=true)
This function removes equations from block, which are not used in order to generate the outputs. It looks for equations which have no path to the outputs equations in the dependency graph. Returns a new IOBlock.
The removed equations will be not available as removed equations of the new IOBlock
TODO: Maybe we should try to reduce the inputs to.
BlockSystems.substitute_algebraic_states
— Functionsubstitute_algebraic_states(iob::IOBlock; verbose=false, warn=true)
Reduces the number of equations by substituting explicit algebraic equations. Returns a new IOBlock with the reduced equations. The removed eqs are stored together with the previous removed_eqs
in the new IOBlock. Won't reduce algebraic states which are labeled as output
.
BlockSystems.substitute_derivatives
— Functionsubstitute_derivatives(iob::IOBlock; verbose=false, warn=true)
Expand all derivatives in the RHS of the system. Try to substitute in the lhs with their definition.
I.e.
D(o) ~ 1 + D(i) => D(o) ~ 2 + a
D(i) ~ 1 + a D(i) ~ 1 + a
Process happens in multiple steps:
- try to find explicit equation for differential
- if none found try to recursively substitute inside differential with known algebraic states
- expand derivatives and try again to substitute with known differentials
BlockSystems.simplify_eqs
— Functionsimplify_eqs(iob::IOBlock; verbose=false, warn=true)
Simplify eqs and removed eqs and return new IOBlock.
BlockSystems.set_p
— Functionset_p(blk::IOBlock, p::Dict; warn=true)
set_p(blk::IOBlock, p::Pair; warn=true)
Substitutes certain parameters by actual Float values. Returns an IOBlock without those parameters.
Keys of dict can be either Symbols
or the Symbolic
subtypes. I.e. blk.u => 1.0
is as valid as :u => 1.0
.
Function building
BlockSystems.generate_io_function
— Functiongenerate_io_function(ios; f_states, f_inputs, f_params, f_rem_states, expression, verbose, type, warn)
Generate callable functions for an AbstractIOSystem
. An IOSystem
will be transformed to an IOBlock
first. At this level there is no more distinction between internal states and outputs: states=(istates ∪ outputs)
.
Arguments:
ios
: the system to build the function
optional:
type=:auto
::ode
or:static
, determines the output of the functionf_states
: define states=(istates ∪ outputs) which should appear firstf_inputs
: define inputs which should appear firstf_params
: define parameters which should appear firstf_rem_states
: define removed states algebraic state orderexpression=Val{false}
: toggle expression and callable function outputwarn=true
: toggle warnings for missingf_*
parameters
Returns an named tuple with the fields
- for
type=:ode
:f_ip
in-place functionf(dstates, states, inputs, params, iv)
f_oop
out-of-place functionf(states, inputs, params, iv) => dstates
- for
type=:static
:f_ip
in-place functionf(states, inputs, params, iv)
f_oop
out-of-place functionf(inputs, params, iv) => states
- always:
massm
mass matrix of the system (nothing
if :static)states
symbols of states (in order)inputs
symbols of inputs (in order)params
symbols of parameters (in order)rem_states
symbols of removed states (in order)g_ip
,g_oop
functionsg((opt. out), states, inputs, params, iv)
to calculate the removed states (substituted expl. algebraic equations).nothing
if empty.
Block specifications
Sometimes it is useful to define a the input/output structure for a block.
BlockSystems.BlockSpec
— Typestruct BlockSpec
BlockSpec(in::Vector, out::Vector; in_strict=true, out_strict=false)
Block specification, defines which inputs/outputs an AbstractIOSystem
should have. Contains two vectors of Symbols
. Can be initialized with Vectors of Symbols
, Num
or <:Symbolic
.
If strict=true
the in/outputs must equal the specification. If strict=false
the block must contain the in/outputs from the specification.
Object is functor: call (::BlockSpec)(ios)
to check whether ios
fulfills specification. See also fulfills
.
iob = IOBlock(...)
spec = BlockSpec([:uᵣ, :uᵢ], [:iᵣ, :iᵢ])
fulfills(iob, spec)
spec(iob)
BlockSystems.fulfills
— Functionfulfills(io, bs::BlockSpec)::Bool
Check whether io
fulfills the given BlockSpec
.