4.2. Impementing a multiplexer (demonstrates the use of a multi-input connector as arbitrarily many single-input connectors)¶
This tutorial shows and explains a simple implementation of a multiplexer. With this example, the usage of a multi-input connector as arbitrarily many single-input connectors is demonstrated. A follow-up for this tutorial demonstrates the use of conditional input connectors, to avoid unnecessary computations.
4.2.1. What is a multiplexer¶
A multiplexer is a device with many inputs and one output, which allows to select, which one of the inputs shall be routed to the output.
A very common application for multiplexers is signal routing in electronic circuits, for which there is a huge variety of integrated circuits, such as the 74LS151 or the CMOS 4097.
In some occasions, a multiplexer can also be helpful to implement a processing networs, which is why the Connectors package provides the
4.2.2. Arbitrarily many input connectors¶
To suit most applications, the number of inputs of the multiplexer should not be hard coded. Instead it should dynamically scale the number of input connectors. Also, the keys for selecting the input, that shall be routed to the output, should ideally be arbitrary, so the user can decide, if the keys are integers, strings or any other objects.
Theoretically, it is possible to implement such an array of an arbitrary number of input connectors by instantiating
But such an implementation would require a lot of code and it would depend on implementation details of the Connectors package, that might change in the future.
For applications like this, a
MultiInputConnector can be accessed with the
 operator, which returns a an object, that behaves like a single-input connector.
This virtual input connector can be called directly or be connected to an output connector.
The key, that is passed to the
 operator is the data ID, under which the
MultiInputConnector stores the given input value.
This allows the user to select the data ID manually, rather than having it generated by the connector, which in turn allows to use the data ID as selector for a multiplexer.
4.2.3. Implementation of the multiplexer¶
>>> import connectors
The following code shows the implementation of a multiplexer.
>>> class Multiplexer: ... def __init__(self, selector=None): ... self.__selector = selector ... self.__data = connectors.MultiInputData() ... ... @connectors.Output() ... def output(self): ... if self.__selector in self.__data: ... return self.__data[self.__selector] ... else: ... return None ... ... @connectors.Input("output") ... def select(self, selector): ... self.__selector = selector ... return self ... ... @connectors.MultiInput("output") ... def input(self, data): ... return self.__data.add(data) ... ... @input.remove ... def remove(self, data_id): ... del self.__data[data_id] ... return self ... ... @input.replace ... def replace(self, data_id, data): ... self.__data[data_id] = data ... return data_id
Note, that it is required, that the
replace() method returns the ID, under which the new input value is stored.
Apart from this, the implementation is straight forward.
replace()methods implement a very common pattern for multi-input connectors, in which the input values are stored in a
select()method is an input connector, through which the key for selecting the input, that is routed to the output.
output()method returns the value from the selected input or
None, if the selector key is invalid.
selfto allow method chaining.
4.2.4. Usage of the multiplexer¶
Instantiating the multiplexer is done the usual way.
>>> multiplexer = Multiplexer()
When calling the input, it can be accessed with the
 operator to specify the selector key.
>>> multiplexer.input["key 1"]("value 1") <__main__.Multiplexer object at 0x...> >>> multiplexer.input["key 2"]("value 2") <__main__.Multiplexer object at 0x...> >>> multiplexer.select("key 2") <__main__.Multiplexer object at 0x...> >>> multiplexer.output() 'value 2'
Note, that the call of the virtual single-input method returns the multiplexer instance. This is an implementation choice of the Connectors package and cannot be influenced by how the decorated method is implemented. The idea behind this choice is, that it allows chaining the calls of the input method. Theoretically, all of the above can be written in one line:
>>> Multiplexer().input["key 1"]("value 1").input["key 2"]("value 2").select("key 2").output() 'value 2'
Under the hood, the virtual single-inputs, that are created with the
 operator, call the
So the above script is equivalent to the following.
>>> multiplexer.replace("key 1", "value 1") 'key 1' >>> multiplexer.replace("key 2", "value 2") 'key 2' >>> _ = multiplexer.select("key 2") >>> multiplexer.output() 'value 2'
input() method can also be called like an ordinary multi-input connector.
In this case, the returned data ID must be stored in a variable, so it can be used as selector key.
>>> key1 = multiplexer.input("value 1") >>> key2 = multiplexer.input("value 2") >>> _ = multiplexer.select(key2) >>> multiplexer.output() 'value 2'
The latter two approaches do not work in the context of connecting an output connector to one of the inputs of the multiplexer.
replace() method does not accept connections, while when using the
input() method the usual way, the data ID is unknown to the user, so it cannot be used as a selector key.
Therefore, connections to the multiplexer have to use the virtual single-inputs from the
>>> data_source = connectors.blocks.PassThrough("value 3 (value from the data source)") >>> _ = data_source.output.connect(multiplexer.input["key 3"]) >>> _ = multiplexer.select("key 3") >>> multiplexer.output() 'value 3 (value from the data source)'
4.2.5. Restrictions and requirements for virtual single-input connectors¶
 operator calls the
replace method of the multi-input connector, it is possible, that the data ID, which is passed to the method, does not exist, yet.
replace methods of multi-input connectors, that shall be used as virtual single-inputs, must be able to hanlde unknown data IDs in a reasonable manner.
This is usually the case, when the input data is managed by dictionaries like a
For ordinary multi-input connectors, it is optional to specify a
If none is specified, replacing data is done with the
remove method and the decorated input method.
This will obviously not work with the
 operator, because calling the decorated input method will generate a new data ID, that is not known outside the class.
When managing the input data of a multi-input connector with dictionaries like a
MultiInputData instance, the data IDs must be hashable.
Therefore it is not possible to use mutable objects like
list instances as selector keys for this