-
Notifications
You must be signed in to change notification settings - Fork 34
Creating a new block
WORK IN PROGRESS
Blocks are defined by class definitions in modules in the bdsim/blocks
folder or the blocks
folder of a compatible toolbox.
The class defining the block must subclass one of:
-
SourceBlock
, output is a constant or function of time. For example,CONSTANT
blocks andWAVEFORM
generator blocks -
SinkBlock
, input only. For example,PRINT
blocks orNULL
blocks. -
GraphicsBlock
, a subclass ofSink
for blocks that produce graphical output. For exampleSCOPE
blocks. -
FunctionBlock
, output is a direct function of input. For exampleGAIN
blocks orFUNCTION
blocks. -
TransferBlock
, output is a function of state self.x (no direct pass through). For exampleLTI_SISO
blocks.
which are imported by bdsim.components
. These classes all subclass Block
.
All block classes have names starting with a capital letter, and if long can use camel case, eg. the GAIN
block is a defined by a class
called Gain
.
When the BDSim
instance is constructed
import bdsim
sim = bdsim.BDSim() # create simulator
it searches all modules on the block search path. Any class found which is a subclass of Block
is added to a list which can be displayed
by
sim.blocks()
This list includes metadata about where the block definition was found, a reference to its constructor (__init__
method), and its doctoring.
When the BlockDiagram
object is constructed
bd = sim.blockdiagram() # create an empty block diagram
the __init__
method of every block class becomes a factory method of the BlockDiagram
instance.
The factory method name is derived from the class name: leading underscores are stripped and the name is capitalized, eg. the class Gain
becomes the factory method GAIN
.
Any docstring for the original block class (class or __init__
) becomes a
docstring of the factory method.
Every class must provide several methods:
-
__init__
mandatory to handle block specific parameter arguments. Additional common block parameters are handled by the superclasses. -
start
, to setup just before simulation starts -
output
, to compute the output value as a function of self.inputs which is a dict indexed by input number -
deriv
, for Transfer subclass only, return the state derivative vector -
check
, to validate parameter settings
During block diagram network evaluation the blocks are executed in order according to a schedule which ensures that their inputs have been
computed by other blocks and are available as self.input(i)
where i
is the input port number, or as a list self.inputs
ordered by input port number.
Common to all blocks and handled by the superclass constructor include:
-
name
the block name -
onames
a tuple of names for output ports, can bemathtext
format -
inames
a tuple of names for input ports, can bemathtext
format -
snames
a tuple of names for states, can bemathtext
format -
pos
the position of the block in a graphical representation
The first important decision is to decide what type of block you are creating, as per the list above.
A good way to create a new block is to start with an existing block that is somewhat close to the new block you want to create.
You can find examples of many blocks, organised by their type in the files in the bdsim/blocks
folder, for example bdsim/blocks/functions.py
contains all the FunctionBlock
subclasses like GAIN
, SUM
etc.
Block classes have camel-case names and start with a capital letter, eg. Waveform
. Exceptions are acronyms which are all capital letters, eg. LTI_SISO
.
Each block class must provide a number of methods:
-
__init__
invoked when the block is created. Invokingbd.WAVEFORM(...)
invokes the constructor__init__(...)
of theWaveform
class. update
deriv
class MyBlock(FunctionBlock):
nin = 1
nout = 1
which declares to bdsim
the number of input and output ports the block has. If they are not known, or are variable, set the corresponding value to -1. These are provided to inform bdedit
about a block without having to instantiate it.
Use a signature line like
def __init__(self, ..., **blockargs):
super().__init__(**blockargs)
where the ellipsis represents your block's parameters. blockargs
are standard parameters that are handled by the superclass constructor. These parameters include nin
and nout
which can be used to dynamically set the number of block inputs and outputs.
This method should do the following:
- validate the passed parameters
- save those that are required for execution time as attributes of the object. Do not use attribute names with underscore prefixes since that namespace is reserved for
bdsim
. You may also wish to stash other data at this time
The method you need to create will depend on the type of block you are creating. For a:
-
SourceBlock
orFunctionBlock
it will be calledoutput
-
SinkBlock
it will be calledstep
-
TranferBlock
you will need to create anoutput
method and aderiv
method
For SourceBlock
, FunctionBlock
, or TranferBlock
compute the block's output port values TransferBlock
only),
def output(self, t, inports, x):
- The current time is
t
as a float - The inputs to the block are available in the list
inputs
- The state vector of this block is available as
x
which is a 1D Numpy array. - Block parameters are available as attributes of
self
created by the__init__
method
The method must return a list of output port values. For a single-output block it must return a list with a single element.
For SinkBlock
or GraphicsBlock
only, since it has no outputs.
def step(self, t, inports):
# body in here
super().step(t, inports) # last line
- The current time is
t
as a float - The inputs to the block are available in the list
inputs
- Block parameters are available as attributes of
self
created by the__init__
method
A block of this type has no return value. It typically takes the input and saves it or transmit in some fashion.
The last line should call the superclass method, for a graphics block this will update graphics, save animation frames etc.
For TransferBlock
only. The dynamics of the block are described generally by
def deriv(self, t, inports, x):
- The current time is
t
as a float - The inputs to the block are available in the list
inputs
- The state vector of this block is available as
x
which is a 1D Numpy array. - Block parameters are available as attributes of
self
created by the__init__
method
The method must return a 1D Numpy array of the same length as x
.
For discrete-time TransferBlock
only. The dynamics of the block are described generally by
def next(self, t, inports, x):
- The current time is
t
as a float - The inputs to the block are available in the list
inputs
- The state vector of this block is available as
x
which is a 1D Numpy array. - Block parameters are available as attributes of
self
created by the__init__
method
The method must return a 1D Numpy array of the same length as x
.
def start(self, simstate):
super().start(simstate)
# your code here
The method is passed the runtime state which contains a grab bag of useful references regarding the execution environment of the diagram.
This method is called before simulation commences and generally doesn't need to be implemented.
The exception is for graphics blocks that need to get access to the graphic subsystem to create a plot window. Command line options are available in simstate.options
and the maximum simulation time is simstate.T
.
If other methods need simstate
, which is pretty rare, then the required references should be stashed as a block instance variable in this method, so that it can be accessed by other methods.
See the documentation in the blocks/Icons
folder.
Unit tests are important. Have a look at the tests in the tests
folder. You can instantiatiate your block and use the T_xxx
methods to evaluate its performance without having to create a block diagram or runtime.
Docstrings are important for several reasons:
- let the code reader know what the block is doing
- provide hinting inside the IDE.
- provide key data structures to
bdedit
which it uses for input validation
The docstrings should be written in the general format of docstrings for existing blocks. There is a docstring for the class, and one for the __init__
method. For the latter, each parameter should be described in Sphinx format with roles :param:
and a :type:
. Some specific syntax used by bdedit
is available here.
Copyright (c) Peter Corke 2020-23
- Home
- FAQ
- Changes
- Adding blocks
- Block path
- Connecting blocks
- Subsystems
- Compiling
- Running
- Runtime options
- Discrete-time blocks
- Figures
- Real time control
- PID control
- Coding patterns