-
-
Notifications
You must be signed in to change notification settings - Fork 39
Coding patterns
There are a few different ways that you can write your system using bdsim.
import bdsim
sim = bdsim.BDSim(animation=True) # create simulator
bd = sim.blockdiagram() # create an empty block diagram
# define the blocks
demand = bd.STEP(T=1, name="demand")
sum = bd.SUM("+-")
gain = bd.GAIN(10)
plant = bd.LTI_SISO(0.5, [2, 1], name="plant")
scope = bd.SCOPE(styles=["k", "r--"])
# connect the blocks
bd.connect(demand, sum[0], scope[1])
bd.connect(plant, sum[1])
bd.connect(sum, gain)
bd.connect(gain, plant)
bd.connect(plant, scope[0])import bdsim
sim = bdsim.BDSim(animation=True) # create simulator
bd = sim.blockdiagram() # create an empty block diagram
# define the blocks
demand = bd.STEP(T=1, name="demand")
plant = bd.LTI_SISO(0.5, [2, 1], name="plant")
scope = bd.SCOPE(styles=["k", "r--"])
# connect the blocks using Python syntax
scope[0] = plant
scope[1] = demand
plant[0] = 10 * (demand - plant)We can also use the >> operator, A >> B is the same as
bd.connect(A, B)and the result of the expression is B.
Either or both of A or B can have indexes to indicate a port. The result of A >> B[1] is
B.
Now we can write the example as
sim = bdsim.BDSim(animation=True) # create simulator
bd = sim.blockdiagram() # create an empty block diagram
# define the blocks
demand = bd.STEP(T=1, name="demand")
sum = bd.SUM("+-")
plant = sum >> bd.GAIN(10) >> bd.LTI_SISO(0.5, [2, 1], name="plant")
scope = bd.SCOPE(styles=["k", "r--"])
# connect the blocks
bd.connect(demand, sum[0], scope[1])
bd.connect(plant, sum[1])
bd.connect(plant, scope[0])sim = bdsim.BDSim(animation=True) # create simulator
bd = sim.blockdiagram() # create an empty block diagram
# define the blocks
demand = bd.STEP(T=1, name="demand")
plant = bd.LTI_SISO(0.5, [2, 1], name="plant")
sum = bd.SUM("+-", inputs=[demand, plant])
gain = bd.GAIN(10, inputs=[sum])
scope = bd.SCOPE(styles=["k", "r--"], inputs=[demand, plant])
# connect the blocks using Python syntax
plant[0] = gain # only 1 wire remains to be addedAll of these syntactic tricks can be used in your code, mixed as you see fit. You should probably choose the ones that work best with your way of thinking and stick to them. The following example is minimal but rather cryptic.
import bdsim
sim = bdsim.BDSim(animation=True) # create simulator
bd = sim.blockdiagram() # create an empty block diagram
# define the blocks
demand = bd.STEP(T=1, name="demand")
plant = (gain := bd.GAIN(10)) >> bd.LTI_SISO(0.5, [2, 1], name="plant")
scope = bd.SCOPE(styles=["k", "r--"], inputs=[demand, plant])
gain[0] = bd.SUM("+-", inputs=[demand, plant])In the patterns above the blocks are created by factory methods of the bd object like STEP or SCOPE. These are dynamically loaded at run time and in fact correspond to the constructors of classes called Step and Scope defined in files in the blocks folder. We can can construct instances of those classes directly, but at the expense of slightly increased verbosity.
import bdsim
from bdsim.blockdiagram import BlockDiagram
from bdsim.blocks import Sum, Gain, Scope, LTI_SISO, Step
sim = bdsim.BDSim(load=False, animation=True) # create simulator, don't dynamically load blocks
bd = BlockDiagram(name="main") # create an empty block diagram directly, bypassing sim.blockdiagram()
bd.runtime = sim # sim.blockdiagram() would normally set this for us
# define the blocks
demand = Step(T=1, name="demand", bd=bd)
sum = Sum("+-", bd=bd)
gain = Gain(10, bd=bd)
plant = LTI_SISO(0.5, [2, 1], name="plant", bd=bd)
scope = Scope(styles=["k", "r--"], loc="lower right", bd=bd)We need to tell each block which block diagram it belongs to by passing in the bd argument.
We construct BlockDiagram directly here rather than calling sim.blockdiagram() — with load=False
no block library was loaded, and sim.blockdiagram() currently asserts that one exists (it needs it to
bind the upper-case factory methods we're deliberately not using), so it will raise AssertionError
even though we don't need those factory methods. Constructing BlockDiagram directly sidesteps that,
but means we then have to set bd.runtime = sim ourselves, since that's normally done inside
sim.blockdiagram() too. This is a rough edge in the current API worth simplifying — the underlying
capability works fine once you know the workaround.
Implicit wiring using Python operators can be used with this programming pattern.
This approach might be advantageous when running in an environment that cannot support the dynamic block loading, perhaps an
embedded system running Micro/Circuit Python. This would allow enormous simplification of the BDSim class, but this is not part
of the current development path.
Copyright (c) Peter Corke 2020-
- Home
- API reference (Sphinx)
- Block catalog
- Control Systems Magazine article
- Adding blocks to your model
- Block path
- Connecting blocks
- Compiling
- Running
- Watching a simulation variable
- Simulation results
- Runtime options
- Environment variables
- Discrete-time blocks
- Subsystems
- Figures
- Notebook animation
- Animation and movies
- PID control
- Coding patterns
- Block methods and attributes
- Time stepping: integration, animation & events
- Blocks, wires and plugs
- Graphics blocks
- Evaluation
- Runtimes and simulator state
- Creating a new block
- Related packages
Under development on feat/realtime branch, planned for release before end of 2026.