Sionna Block and Object
All of Sionna PHY’s components inherit from the Sionna Object class.
A Sionna Object is instantiated with an optional precision argument from which it
derives complex- and real-valued data types which can be accessed via the
properties cdtype and rdtype, respectively:
from sionna.phy import Object
obj = Object(precision="single")
print(obj.cdtype)
print(obj.rdtype)
<dtype: 'complex64'>
<dtype: 'float32'>
If the precision argument is not provided, Objects use the
global config.precision parameter, as shown next:
from sionna.phy import config
from sionna.phy import Object
config.precision = "double" # Set global precision
obj = Object()
print(obj.cdtype)
print(obj.rdtype)
<dtype: 'complex128'>
<dtype: 'float64'>
Understanding Sionna Blocks
Sionna Blocks inherit from Objects and are used to implement most of Sionna’s components.
To get an understanding of their features, let us implement a simple custom
Block. Every Block must implement the method call which can take arbitray
arguments and keyword arguments. It is important to understand that all tensor arguments
are cast to the Block’s internal precision. The
following code snippet demonstrates this behavior:
import tensorflow as tf
from sionna.phy import config
from sionna.phy import Block
config.precision = "double"
class MyBlock(Block):
def call(self, x, y=None):
print(x.dtype)
if y is not None:
print(y.dtype)
my_block = MyBlock()
x = tf.constant([3], dtype=tf.float32)
y = tf.complex(2., 3.)
my_block(x, y)
<dtype: 'float64'>
<dtype: 'complex128'>
As the internal precision of all Blocks was set via the global precision
flag to double preceision,
the inputs x and y were cast to the corresponding dtypes prior to executing
the Block’s call method. Note that only floating data types are cast, as can be
seen from the following example:
class MyBlock(Block):
def call(self, x):
print(type(x))
my_block = MyBlock()
my_block(3)
<class 'int'>
The reason for this behavior is that we sometimes need to pass non-tensor arguments to a function so that algorithms can be unrolled during the creation of the computation graph.
In many cases, a Block require some initialization that requires the shapes of
its inputs. The first time a Block is called, it executes the build method
which provides the shapes of all arguments and keyword arguments. The next
example demonstrates this feature:
import numpy as np
import tensorflow as tf
from sionna.phy import Block
class MyBlock(Block):
def build(self, *args, **kwargs):
self.x_shape = args[0]
self.y_shape = kwargs["y"]
def call(self, x, y=None):
print(self.x_shape)
print(x.dtype)
print(self.y_shape)
my_block = MyBlock()
my_block(np.array([3, 3]), y=tf.zeros([10, 12]))
(2,)
<dtype: 'float64'>
(10, 12)
Note that the argument x was provided as NumPy array which is
converted to a TensorFlow tensor within the Block. This is in contrast to the
example above, where this did not happen for an integer input. For a detailed
understanding of type conversions within Blocks, we refer to the source code of
the class method Block._convert_to_tensor.