Getting Started

This example runs through the basic steps of building and simulating a bond graph model. For a full list of functions refer to the API Reference.

Our first example will be a simple electric circuit of a capacitor, resistor, and current supply in parallel. We will first model this circuit without the current supply.

Bond graph construction

We first create a BondGraph object which will hold all our components. This is an empty object that will soon be populated with components and bonds.

model = BondGraph("RC Circuit")
BondGraph RC Circuit (0 Nodes, 0 Bonds)

Next we will create a capacitor as a bond graph 'C'-component. The component type's description can be printed for extra information.

C = Component(:C)
description(:C)
Generalised Linear Capacitor
e = (1/C)*q
dq/dt = f
C: Capacitance [1.0]
q: Generalised position [0.0]

Available component types are defined in the DEFAULT_LIBRARY.

print(keys(BondGraphs.DEFAULT_LIBRARY))
[:Re, :SCe, :Sf, :Ce, :R, :re, :Se, :I, :ce, :TF, :C]

We next create a resistor 'R'-component and an EqualEffort node which represents Kirchoff's Voltage Law. In bond graph terminology, 0-Junctions are EqualEffort nodes, and 1-Junctions are EqualFlow nodes.

R = Component(:R)
kvl = EqualEffort()
𝟎

Components and nodes are added to the model, and connected together as a graph network. Note that components must first be added to the model before they can be connected.

add_node!(model, [C, R, kvl])
connect!(model, R, kvl)
connect!(model, C, kvl)
model
BondGraph RC Circuit (3 Nodes, 2 Bonds)

Because our bond graph is fundamentally a graph, we can using existing graph methods on our model.

using Graphs
incidence_matrix(model)

We can also visualise our model structure by plotting it as a graph network using Plots.jl.

using Plots
plot(model)

Simulating our model

With a bond graph we can automatically generate a series of differential equations which combine all the constitutive relations from the components, with efforts and flows shared according to the graph structure.

constitutive_relations(model)

\[ \begin{align} \frac{\mathrm{d} C_{+}q\left( t \right)}{\mathrm{d}t} =& \frac{ - C_{+}q\left( t \right)}{C_{+}C R_{+}R} \end{align} \]

We will set values for the component parameters in the model. Each component comes with default values. When substituted into our equations, we get the following relation for the capacitor charge C.q(t).

C.C = 1
R.R = 2
constitutive_relations(model; sub_defaults=true)

\[ \begin{align} \frac{\mathrm{d} C_{+}q\left( t \right)}{\mathrm{d}t} =& \frac{ - C_{+}q\left( t \right)}{2} \end{align} \]

We can solve this bond graph directly using the in-built simulate function.

tspan = (0., 10.)
u0 = [1] # initial value for C.q(t)
sol = simulate(model, tspan; u0)
plot(sol)

Under the hood, our simulate function is converting our bond graph into an ModelingToolkit.ODESystem. We can chose instead to create an ODESystem directly and handle it with whatever functions we like.

Adding control variables

We will expand our model by adding an external current (flow) supply in parallel, represented by the component Sf (Source of Flow)

Is = Component(:Sf, "Is")
add_node!(model, Is)
connect!(model, Is, kvl)
plot(model)

We will add a the forcing function fs(t) = sin(2t) as an external current input.

Is.fs = t -> sin(2t)
constitutive_relations(model; sub_defaults=true)

\[ \begin{align} \frac{\mathrm{d} C_{+}q\left( t \right)}{\mathrm{d}t} =& \frac{ - C_{+}q\left( t \right) + 2 \sin\left( 2 t \right)}{2} \end{align} \]

sol = simulate(model, tspan; u0)
plot(sol)

The input can be any arbitrary julia function of t, so long as it returns a sensible output. Note that for this to work you must register the custom function with @register_symbolic, so that the library knows not to simplify this function further.

using ModelingToolkit
@register_symbolic f(t)
Is.fs = t -> f(t)

f(t) = t % 2 <= 1 ? 0 : 1 # repeating square wave
sol = simulate(model, tspan; u0)
plot(sol)