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 dtype, respectively:
from sionna.phy import Object
obj = Object(precision="single")
print(obj.cdtype)
print(obj.dtype)
torch.complex64
torch.float32
If the precision argument is not provided,
Object instances use the
global precision parameter of the
config singleton, 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.dtype)
torch.complex128
torch.float64
Understanding Sionna Blocks#
Sionna Blocks inherit from Object 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 arbitrary
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 torch
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 = torch.tensor([3.], dtype=torch.float32)
y = torch.complex(torch.tensor(2.), torch.tensor(3.))
my_block(x, y)
torch.float64
torch.complex128
As the internal precision of all
Blocks was set via the global
precision flag to double precision,
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:
from sionna.phy import Block
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 (e.g. shapes or indices) so that control flow can be traced correctly when using torch.compile.
In many cases, a Block requires some initialization that depends on the shapes of
its inputs. The first time a Block is called, it executes the build() method
which receives the shapes of all arguments and keyword arguments. The next
example demonstrates this feature:
import numpy as np
import torch
from sionna.phy import config
from sionna.phy import Block
config.precision = "double"
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=torch.zeros([10, 12]))
(2,)
torch.float64
(10, 12)
Note that the argument x was provided as a NumPy array which is
converted to a PyTorch tensor within the Block. This is in contrast to the
example above, where an integer input was left unchanged. For a detailed
understanding of type conversions within Blocks, see the method
_convert() in the Object class.