Skip to content

Subsystems

Peter Corke edited this page Apr 17, 2023 · 2 revisions

This example is given in examples/subsys.py. We will build the example incrementally

#!/usr/bin/env python3

import bdsim

sim = bdsim.BDSim()

is the standard boilerplate for using the bdsim package.

Next, we define our subsystem. It is a block diagram created in the conventional way

# create simple subsystem as a blockdiagram
ss = sim.blockdiagram(name="subsystem1")

squarer = ss.FUNCTION(lambda x: x**2)
sum = ss.SUM("++")
inp = ss.INPORT(2)
outp = ss.OUTPORT(1)

ss.connect(inp[0], squarer)
ss.connect(squarer, sum[0])
ss.connect(inp[1], sum[1])
ss.connect(sum, outp)

The only thing different so far is that there are INPORT and OUTPORT blocks. These are the interface between this block diagram and the block diagram that encloses (or contains) it.

INPORT(2) specifies that this subsystem has two inputs, from the outside it looks like a 2-input block. Within the subsystem, the outputs of this block are the signals fed to the subsystem's input ports from the enclosing block diagram.

OUTPORT(1) specifies that this subsystem has one output, from the outside it looks like a 1-output block. Within the subsystem, the inputs of this block are the signals from the subsystem's output ports that are used in the enclosing block diagram.

# create main system as a blockdiagram
main = sim.blockdiagram()

x = main.WAVEFORM("sine", 1, "Hz")
const = main.CONSTANT(1)
scope = main.SCOPE()
subsys = main.SUBSYSTEM(ss, name="squarer")  # instantiate the subsystem

main.connect(x, subsys[0])
main.connect(const, subsys[1])
main.connect(subsys, scope)

The wiring connects the waveform generator and the constant to inputs 0 and 1 respectively of the subsystem. The single output of the subsystem is wired to the scope.

The compilation step effectively inserts the subsystem into the top-level block diagram

main.compile(verbose=False)
sim.report(main)

The report shows the resulting system. The subsystem has been inlined and we now have a flat wirelist

┌───────────────────┬────────┬────────────────────┬─────────────┐
│      block        │ inport │       source       │ source type │
├───────────────────┼────────┼────────────────────┼─────────────┤
│constant.0         │        │                    │             │
├───────────────────┼────────┼────────────────────┼─────────────┤
│scope.0            │ 0      │ squarer/outport.0  │ float       │
├───────────────────┼────────┼────────────────────┼─────────────┤
│squarer/function.0 │ 0      │ squarer/inport.0   │ float       │
├───────────────────┼────────┼────────────────────┼─────────────┤
│squarer/inport.0   │ 0      │ waveform.0         │ float       │
│                   │ 1      │ constant.0         │ int         │
├───────────────────┼────────┼────────────────────┼─────────────┤
│squarer/outport.0  │ 0      │ squarer/sum.0      │ float       │
├───────────────────┼────────┼────────────────────┼─────────────┤
│squarer/sum.0      │ 0      │ squarer/function.0 │ float       │
│                   │ 1      │ squarer/inport.0   │ int         │
├───────────────────┼────────┼────────────────────┼─────────────┤
│waveform.0@        │        │                    │             │
└───────────────────┴────────┴────────────────────┴─────────────┘

Note: @ = event source

Note that the input and output port blocks are still present in the block diagram. They are essentially no op blocks that pass the signals through. They can be useful for diagnostics because we can add the outputs of these blocks to the watch list. We could just as easily watch any signal inside the subsystem, for example we could watch squarer/function.0.

The blocks within the subsystem have names like Unix paths, they are prefixed by the subsystem's name. A block in subsystem1 which was enclosed by subsystem2 would have a name like subsystem2/subsystem1/block.

We could instantiate the subsystem multiple times, but for each instantiation the subsystem must have a unique name, since bdsim requires that all blocks have unique names. Here we forced the subsystem's name to be squarer but if had left that out, the first instantiation would have been named subsystem.0, the next one subsystem.1 and so on. If a block within the subsystem had a user-assigned name then the different subsystem prefixes would ensure that each instance of that block would have a unique name.

Finally, we can run the system in the usual manner

sim.run(main, T=5)  # simulate for 5s