-
Notifications
You must be signed in to change notification settings - Fork 37
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,CONSTANTblocks andWAVEFORMgenerator blocks -
SinkBlock, input only. For example,PRINTblocks orNULLblocks. -
GraphicsBlock, a subclass ofSinkfor blocks that produce graphical output. For exampleSCOPEblocks. -
FunctionBlock, output is a direct function of input. For exampleGAINblocks orFUNCTIONblocks. -
TransferBlock, output is a function of state self.x (no direct pass through). For exampleLTI_SISOblocks.
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:
-
namethe block name -
onamesa tuple of names for output ports, can bemathtextformat -
inamesa tuple of names for input ports, can bemathtextformat -
snamesa tuple of names for states, can bemathtextformat -
posthe 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 theWaveformclass. updatederiv
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:
-
SourceBlockorFunctionBlockit will be calledoutput -
SinkBlockit will be calledstep -
TranferBlockyou will need to create anoutputmethod and aderivmethod
For SourceBlock, FunctionBlock, or TranferBlock compute the block's output port values TransferBlock only),
def output(self, t, inports, x):
- The current time is
tas a float - The inputs to the block are available in the list
inputs - The state vector of this block is available as
xwhich is a 1D Numpy array. - Block parameters are available as attributes of
selfcreated 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
tas a float - The inputs to the block are available in the list
inputs - Block parameters are available as attributes of
selfcreated 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
tas a float - The inputs to the block are available in the list
inputs - The state vector of this block is available as
xwhich is a 1D Numpy array. - Block parameters are available as attributes of
selfcreated 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
tas a float - The inputs to the block are available in the list
inputs - The state vector of this block is available as
xwhich is a 1D Numpy array. - Block parameters are available as attributes of
selfcreated 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
bdeditwhich 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