Skip to content

block

Module containing Block, Series and Group class definitions.

Block

Block entering a reliability block diagram.

Parameters:

Name Type Description Default
text str

block text string

required
color str

block color

required
parent Optional[Block]

parent Block instance

None
shift tuple[float, float]

additional position shift (x, y) relative to parent Block instance

(0.0, 0.0)

Attributes:

Name Type Description
node_options str

TikZ node formatting options

arrow_options str, default="arrowcolor, thick"

TikZ arrow formatting options

arrow_length float, default=0.5

default arrow length between nodes (in cm)

Source code in pyrbd/block.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
class Block:
    """Block entering a reliability block diagram.

    Parameters
    ----------
    text : str
        block text string
    color : str
        block color
    parent : Optional["Block"], default=None
        parent `Block` instance
    shift : tuple[float, float], default=(0.0, 0.0)
        additional position shift `(x, y)` relative to `parent` `Block` instance

    Attributes
    ----------
    node_options : str
        TikZ node formatting options
    arrow_options : str, default="arrowcolor, thick"
        TikZ arrow formatting options
    arrow_length : float, default=0.5
        default arrow length between nodes (in cm)
    """

    _template = "block.tex.jinja"
    node_options: str = ", ".join(
        [
            "anchor=west",
            "align=center",
            "fill={fill_color}",
            "draw=black!70!gray",
            "minimum height=1cm",
            "rounded corners=0.3mm",
            "inner sep=4pt",
            "outer sep=0pt",
        ]
    )

    arrow_options: str = "arrowcolor, thick"
    arrow_length: float = 0.5

    _block_count = itertools.count(start=1)

    def __init__(
        self,
        text: str,
        color: str,
        parent: Optional["Block"] = None,
        shift: tuple[float, float] = (0.0, 0.0),
    ) -> None:
        self.text = text
        self.color = color
        self.parent = parent
        self.shift = shift
        self.last = self
        self.id = str(next(self._block_count))

    def get_node(self, connector_position: Optional[float] = None) -> str:
        """Get TikZ node string.

        Parameters
        ----------
        connector_position : Optional[float], default=None
            distance in cm to right angle bend in connector. Defaults to `0.5*arrow_length`.

        Returns
        -------
        str
            TikZ string for rendering block
        """

        if connector_position is None:
            connector_position = self.arrow_length / 2

        environment = JINJA_ENV
        template = environment.get_template(self._template)
        context = {
            "type": "Block",
            "block": self,
            "node_options": self.node_options.format(fill_color=self.color),
            "connector_position": connector_position,
        }

        return template.render(context)

    def get_blocks(self) -> Generator["Block", None, None]:
        """Yield child `Block` istances."""

        yield self

    def __add__(self, block: "Block") -> "Series":
        """Add two `Block` instances to make a `Series` instance.

        Parameters
        ----------
        block : Block
            another `Block` instance

        Returns
        -------
        Series
            `Series` instance with `blocks = [self, block]`

        Raises
        ------
        TypeError
            If `block` is not an instance of `Block`
        """

        if not isinstance(block, Block):
            raise TypeError(
                f"Cannot add object of type {type(block)=} to Block instance."
            )

        return Series([self, block], parent=self.parent)

    @overload
    def __rmul__(self, value: Literal[1]) -> "Block": ...

    @overload
    def __rmul__(self, value: int) -> "Group": ...

    def __rmul__(self, value: int) -> "Group | Block":
        """Right multiply `Block` instance by `value` to make `Group` with repeated blocks.

        Parameters
        ----------
        value : int
            multiplicative factor, a positive integer

        Returns
        -------
        Group | Block
            `Group` instance with `value` copies of block if `value > 1`, `self` otherwise

        Raises
        ------
        ValueError
            If `value` is not a positive integer
        """

        if not isinstance(value, int) or value <= 0:
            raise ValueError("Multiplicative factor `value` must be a positive integer")

        if value == 1:
            return self

        blocks: list[Block] = [deepcopy(self) for _ in range(value)]

        return Group(blocks, parent=self.parent)

    @overload
    def __mul__(self, value: Literal[1]) -> "Block": ...

    @overload
    def __mul__(self, value: int) -> "Series": ...

    def __mul__(self, value: int) -> "Series | Block":
        """Multiply `Block` instance by `value` to make `Series` with repeated blocks.

        Parameters
        ----------
        value : int
            multiplicative factor, a positive integer

        Returns
        -------
        Series | Block
            `Series` instance with `value` copies of block if `value > 1`, self otherwise

        Raises
        ------
        ValueError
            If `value` is not a positive integer
        """

        if not isinstance(value, int) or value <= 0:
            raise ValueError("Multiplicative factor `value` must be a positive integer")

        if value == 1:
            return self

        blocks: list[Block] = [deepcopy(self) for _ in range(value)]

        return Series(blocks, parent=self.parent)

__add__(block)

Add two Block instances to make a Series instance.

Parameters:

Name Type Description Default
block Block

another Block instance

required

Returns:

Type Description
Series

Series instance with blocks = [self, block]

Raises:

Type Description
TypeError

If block is not an instance of Block

Source code in pyrbd/block.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def __add__(self, block: "Block") -> "Series":
    """Add two `Block` instances to make a `Series` instance.

    Parameters
    ----------
    block : Block
        another `Block` instance

    Returns
    -------
    Series
        `Series` instance with `blocks = [self, block]`

    Raises
    ------
    TypeError
        If `block` is not an instance of `Block`
    """

    if not isinstance(block, Block):
        raise TypeError(
            f"Cannot add object of type {type(block)=} to Block instance."
        )

    return Series([self, block], parent=self.parent)

__mul__(value)

__mul__(value: Literal[1]) -> Block
__mul__(value: int) -> Series

Multiply Block instance by value to make Series with repeated blocks.

Parameters:

Name Type Description Default
value int

multiplicative factor, a positive integer

required

Returns:

Type Description
Series | Block

Series instance with value copies of block if value > 1, self otherwise

Raises:

Type Description
ValueError

If value is not a positive integer

Source code in pyrbd/block.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
def __mul__(self, value: int) -> "Series | Block":
    """Multiply `Block` instance by `value` to make `Series` with repeated blocks.

    Parameters
    ----------
    value : int
        multiplicative factor, a positive integer

    Returns
    -------
    Series | Block
        `Series` instance with `value` copies of block if `value > 1`, self otherwise

    Raises
    ------
    ValueError
        If `value` is not a positive integer
    """

    if not isinstance(value, int) or value <= 0:
        raise ValueError("Multiplicative factor `value` must be a positive integer")

    if value == 1:
        return self

    blocks: list[Block] = [deepcopy(self) for _ in range(value)]

    return Series(blocks, parent=self.parent)

__rmul__(value)

__rmul__(value: Literal[1]) -> Block
__rmul__(value: int) -> Group

Right multiply Block instance by value to make Group with repeated blocks.

Parameters:

Name Type Description Default
value int

multiplicative factor, a positive integer

required

Returns:

Type Description
Group | Block

Group instance with value copies of block if value > 1, self otherwise

Raises:

Type Description
ValueError

If value is not a positive integer

Source code in pyrbd/block.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def __rmul__(self, value: int) -> "Group | Block":
    """Right multiply `Block` instance by `value` to make `Group` with repeated blocks.

    Parameters
    ----------
    value : int
        multiplicative factor, a positive integer

    Returns
    -------
    Group | Block
        `Group` instance with `value` copies of block if `value > 1`, `self` otherwise

    Raises
    ------
    ValueError
        If `value` is not a positive integer
    """

    if not isinstance(value, int) or value <= 0:
        raise ValueError("Multiplicative factor `value` must be a positive integer")

    if value == 1:
        return self

    blocks: list[Block] = [deepcopy(self) for _ in range(value)]

    return Group(blocks, parent=self.parent)

get_blocks()

Yield child Block istances.

Source code in pyrbd/block.py
 98
 99
100
101
def get_blocks(self) -> Generator["Block", None, None]:
    """Yield child `Block` istances."""

    yield self

get_node(connector_position=None)

Get TikZ node string.

Parameters:

Name Type Description Default
connector_position Optional[float]

distance in cm to right angle bend in connector. Defaults to 0.5*arrow_length.

None

Returns:

Type Description
str

TikZ string for rendering block

Source code in pyrbd/block.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def get_node(self, connector_position: Optional[float] = None) -> str:
    """Get TikZ node string.

    Parameters
    ----------
    connector_position : Optional[float], default=None
        distance in cm to right angle bend in connector. Defaults to `0.5*arrow_length`.

    Returns
    -------
    str
        TikZ string for rendering block
    """

    if connector_position is None:
        connector_position = self.arrow_length / 2

    environment = JINJA_ENV
    template = environment.get_template(self._template)
    context = {
        "type": "Block",
        "block": self,
        "node_options": self.node_options.format(fill_color=self.color),
        "connector_position": connector_position,
    }

    return template.render(context)

Group

Bases: Block

Group of Block instances for vertical stacking.

Parameters:

Name Type Description Default
blocks list[Block]

list of Block instances

required
text str

group label text

""
color str

group color, defaults to white

""
parent Optional[Block]

parent Block instance

None

Attributes:

Name Type Description
shift_scale float, default=1.2

scaling factor for vertical shifts of blocks

node_options str

TikZ node options

internal_arrow_length float, default=0.3

distance between blocks in series

end_arrow_scaling float, default=0.75

scale factor of last arrow in Group

pad Padding, default=Padding(1, 1, 1, 1)

namedtuple (north, east, south, west) defining padding (in mmm) between blocks and series frame

label_height float, default=5.0

height of series label (in mm)

Source code in pyrbd/block.py
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
class Group(Block):
    """Group of `Block` instances for vertical stacking.

    Parameters
    ----------
    blocks : list[Block]
        list of `Block` instances
    text : str, default=""
        group label text
    color : str, default=""
        group color, defaults to white
    parent : Optional[Block], default=None
        parent `Block` instance

    Attributes
    ----------
    shift_scale : float, default=1.2
        scaling factor for vertical shifts of blocks
    node_options : str
        TikZ node options
    internal_arrow_length : float, default=0.3
        distance between blocks in series
    end_arrow_scaling : float, default=0.75
        scale factor of last arrow in `Group`
    pad : Padding, default=Padding(1, 1, 1, 1)
        `namedtuple` `(north, east, south, west)` defining padding (in mmm) between
        blocks and series frame
    label_height : float, default=5.0
        height of series label (in mm)
    """

    _template = "group.tex.jinja"
    shift_scale: float = 1.2
    node_options: str = ", ".join(
        [
            "anchor=west",
            "align=center",
            "inner sep=0pt",
            "outer sep=0pt",
        ]
    )
    internal_arrow_length: float = 0.3
    end_arrow_scaling: float = 0.75
    pad: Padding = Padding(1, 1, 1, 1)
    label_height: float = 5.0

    def __init__(
        self,
        blocks: list[Block],
        text: str = "",
        color: str = "",
        parent: Optional[Block] = None,
    ) -> None:
        Block.__init__(self, text, color, parent)

        self.blocks = blocks
        for i, (block, shift) in enumerate(zip(self.blocks, self.shifts)):
            block.shift = (0, shift)
            block.parent = self
            block.id = f"{self.id}-{i}"
            block.arrow_length = self.internal_arrow_length

    @property
    def shifts(self) -> list[float]:
        """List of vertical position shifts for each `Block` instance in group.

        Returns
        -------
        list[float]
            list of vertical position shifts for each `Block` instance in group
        """

        n_blocks = len(self.blocks)

        return list(-self.shift_scale * n for n in range(n_blocks))

    @property
    def sorted_blocks(self) -> list[Block]:
        """Get TikZ string for arrow connecting stacked blocks."""

        # scaling = 0.75

        series_blocks = [block for block in self.blocks if isinstance(block, Series)]
        series_blocks.sort(
            key=lambda block: len(list(block.get_blocks())), reverse=True
        )

        if len(series_blocks) > 0:
            longest_series_index = self.blocks.index(series_blocks[0])
        else:
            longest_series_index = 0
        blocks = deepcopy(self.blocks)
        longest_series = blocks.pop(longest_series_index)

        return [longest_series, *blocks]

    def get_node(self, connector_position: Optional[float] = None) -> str:
        """Get TikZ node string.

        Parameters
        ----------
        connector_position : Optional[float], default=None
            distance in cm to right angle bend in connector.
            Locked to 0.0 for `Group` class

        Returns
        -------
        str
            TikZ string for rendering group
        """

        connector_position = 0.0

        block_nodes = [block.get_node(connector_position) for block in self.blocks]

        environment = JINJA_ENV
        template = environment.get_template(self._template)
        context = {
            "type": "Group",
            "block": self,
            "node_options": self.node_options,
            "connector_position": connector_position,
            "pad": self.pad,
            "block_nodes": block_nodes,
        }

        return template.render(context)

    def get_blocks(self) -> Generator[Block, None, None]:
        yield from self.blocks

shifts property

List of vertical position shifts for each Block instance in group.

Returns:

Type Description
list[float]

list of vertical position shifts for each Block instance in group

sorted_blocks property

Get TikZ string for arrow connecting stacked blocks.

get_node(connector_position=None)

Get TikZ node string.

Parameters:

Name Type Description Default
connector_position Optional[float]

distance in cm to right angle bend in connector. Locked to 0.0 for Group class

None

Returns:

Type Description
str

TikZ string for rendering group

Source code in pyrbd/block.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
def get_node(self, connector_position: Optional[float] = None) -> str:
    """Get TikZ node string.

    Parameters
    ----------
    connector_position : Optional[float], default=None
        distance in cm to right angle bend in connector.
        Locked to 0.0 for `Group` class

    Returns
    -------
    str
        TikZ string for rendering group
    """

    connector_position = 0.0

    block_nodes = [block.get_node(connector_position) for block in self.blocks]

    environment = JINJA_ENV
    template = environment.get_template(self._template)
    context = {
        "type": "Group",
        "block": self,
        "node_options": self.node_options,
        "connector_position": connector_position,
        "pad": self.pad,
        "block_nodes": block_nodes,
    }

    return template.render(context)

Series

Bases: Block

Series configuration of Block instances for horisontal grouping.

Parameters:

Name Type Description Default
blocks list[Block]

list of Block instances

required
text str

series label text

''
color str

series color, defaults to white

'white'
parent Optional[Block]

parent Block instance

None

Attributes:

Name Type Description
node_options str

TikZ node options

internal_arrow_length float, default=0.3

distance between blocks in series

pad Padding, default=Padding(1, 1, 1, 2.5)

namedtuple (north, east, south, west) defining padding (in mmm) between blocks and series frame

label_height float, default=5.0

height of series label (in mm)

Source code in pyrbd/block.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
class Series(Block):
    """Series configuration of `Block` instances for horisontal grouping.

    Parameters
    ----------
    blocks : list[Block]
        list of `Block` instances
    text: str, default=""
        series label text
    color: str, default="white"
        series color, defaults to white
    parent : Optional[Block], default=None
        parent `Block` instance

    Attributes
    ----------
    node_options : str
        TikZ node options
    internal_arrow_length : float, default=0.3
        distance between blocks in series
    pad : Padding, default=Padding(1, 1, 1, 2.5)
        `namedtuple` `(north, east, south, west)` defining padding (in mmm) between
        blocks and series frame
    label_height : float, default=5.0
        height of series label (in mm)

    """

    _template = "series.tex.jinja"
    node_options: str = ", ".join(
        [
            "anchor=west",
            "align=center",
            "inner sep=0pt",
            "outer sep=0pt",
        ]
    )
    internal_arrow_length: float = 0.3
    pad: Padding = Padding(1, 1, 1, 2.5)
    label_height: float = 5.0

    def __init__(
        self,
        blocks: list[Block],
        text: str = "",
        color: str = "white",
        parent: Optional[Block] = None,
    ) -> None:
        Block.__init__(self, text, color, parent)

        self.blocks = blocks
        self.last = self.blocks[-1]

        self.blocks[0].id = f"{self.id}+0"
        for i, (block, new_parent) in enumerate(
            zip(self.blocks[1::], self.blocks[0:-1]), start=1
        ):
            block.parent = new_parent
            block.id = f"{self.id}+{i}"
            if not isinstance(block, Series):
                block.arrow_length = self.internal_arrow_length

        if True in [isinstance(block, Series) for block in self.blocks]:
            self.shift = (0, 0.25)

    def get_node(self, connector_position: Optional[float] = None) -> str:
        """Get TikZ node string.

        Parameters
        ----------
        connector_position : Optional[float], default=None
            distance in cm to right angle bend in connector. Defaults to `0.5 * arrow_length`

        Returns
        -------
        str
            TikZ string for rendering series

        """
        if connector_position is None:
            connector_position = self.arrow_length / 2

        block_nodes = [block.get_node(connector_position) for block in self.blocks]

        environment = JINJA_ENV
        template = environment.get_template(self._template)
        context = {
            "type": "Series",
            "block": self,
            "node_options": self.node_options,
            "connector_position": connector_position,
            "pad": self.pad,
            "block_nodes": block_nodes,
            "first": self.blocks[0],
        }

        return template.render(context)

    def get_blocks(self) -> Generator[Block, None, None]:
        yield from [
            children for block in self.blocks for children in block.get_blocks()
        ]

get_node(connector_position=None)

Get TikZ node string.

Parameters:

Name Type Description Default
connector_position Optional[float]

distance in cm to right angle bend in connector. Defaults to 0.5 * arrow_length

None

Returns:

Type Description
str

TikZ string for rendering series

Source code in pyrbd/block.py
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
def get_node(self, connector_position: Optional[float] = None) -> str:
    """Get TikZ node string.

    Parameters
    ----------
    connector_position : Optional[float], default=None
        distance in cm to right angle bend in connector. Defaults to `0.5 * arrow_length`

    Returns
    -------
    str
        TikZ string for rendering series

    """
    if connector_position is None:
        connector_position = self.arrow_length / 2

    block_nodes = [block.get_node(connector_position) for block in self.blocks]

    environment = JINJA_ENV
    template = environment.get_template(self._template)
    context = {
        "type": "Series",
        "block": self,
        "node_options": self.node_options,
        "connector_position": connector_position,
        "pad": self.pad,
        "block_nodes": block_nodes,
        "first": self.blocks[0],
    }

    return template.render(context)