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
tikz_options str

TikZ node formatting options

Examples:

>>> block_1 = Block("Start", "green")
>>> block_1.id
'1'
>>> block_2 = Block("End", "red", parent=block_1)
>>> block_2.id
'2'
Source code in pyrbd/block.py
 11
 12
 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
class Block:
    """Block entering a reliability block diagram.

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

    Attributes
    ----------
    tikz_options : str
        TikZ node formatting options


    Examples
    --------
    >>> block_1 = Block("Start", "green")
    >>> block_1.id
    '1'
    >>> block_2 = Block("End", "red", parent=block_1)
    >>> block_2.id
    '2'
    """

    tikz_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

    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.id: str = str(int(self.parent.id) + 1) if self.parent is not None else "1"

    @property
    def position(self) -> str:
        """Block position TikZ string."""

        if self.parent is None:
            return ""

        return " ".join(
            [
                f"[right={self.arrow_length + self.shift[0]}cm",
                f"of {self.parent.id},",
                f"yshift={self.shift[1]}cm]",
            ]
        )

    def arrow(self, connector_position: float) -> str:
        """Get TikZ arrow string.


        Parameters
        ----------
        connector_position : float
            distance in cm to right angle bend in connector

        Returns
        -------
        str
            TikZ string for arrow from `parent` to `self` or empty string if `parent` is `None`
        """

        if self.parent is None:
            return ""

        return "".join(
            [
                f"\\draw[{self.arrow_options}, rectangle connector={connector_position}cm]",
                f"({self.parent.id}.east) to ({self.id}.west);\n\n",
            ]
        )

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

        Parameters
        ----------
        connector_position : Optional[float]
            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

        node = "".join(
            [
                "% Block\n",
                f"\\node[{self.tikz_options.format(fill_color=self.color)}] ",
                f"({self.id}) ",
                self.position,
                f"{{{self.text}}};\n",
                self.arrow(connector_position),
            ]
        )
        return node

    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)

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

        Parameters
        ----------
        value : int
            multiplicative factor

        Returns
        -------
        Group
            `Group` instance with `value` copies of block

        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")

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

        return Group(blocks, parent=self.parent)

    __rmul__ = __mul__

position property

Block position TikZ string.

__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
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
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)

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

Parameters:

Name Type Description Default
value int

multiplicative factor

required

Returns:

Type Description
Group

Group instance with value copies of block

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
def __mul__(self, value: int) -> "Group":
    """Multiply `Block` instance by `value` to make `Group` with repeated blocks.

    Parameters
    ----------
    value : int
        multiplicative factor

    Returns
    -------
    Group
        `Group` instance with `value` copies of block

    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")

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

    return Group(blocks, parent=self.parent)

arrow(connector_position)

Get TikZ arrow string.

Parameters:

Name Type Description Default
connector_position float

distance in cm to right angle bend in connector

required

Returns:

Type Description
str

TikZ string for arrow from parent to self or empty string if parent is None

Source code in pyrbd/block.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def arrow(self, connector_position: float) -> str:
    """Get TikZ arrow string.


    Parameters
    ----------
    connector_position : float
        distance in cm to right angle bend in connector

    Returns
    -------
    str
        TikZ string for arrow from `parent` to `self` or empty string if `parent` is `None`
    """

    if self.parent is None:
        return ""

    return "".join(
        [
            f"\\draw[{self.arrow_options}, rectangle connector={connector_position}cm]",
            f"({self.parent.id}.east) to ({self.id}.west);\n\n",
        ]
    )

get_blocks()

Yield child Block istances.

Source code in pyrbd/block.py
139
140
141
142
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
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
def get_node(self, connector_position: Optional[float] = None) -> str:
    """Get TikZ node string.

    Parameters
    ----------
    connector_position : Optional[float]
        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

    node = "".join(
        [
            "% Block\n",
            f"\\node[{self.tikz_options.format(fill_color=self.color)}] ",
            f"({self.id}) ",
            self.position,
            f"{{{self.text}}};\n",
            self.arrow(connector_position),
        ]
    )
    return node

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

''
parent Optional[Block]

parent Block instance

None

Attributes:

Name Type Description
shift_scale float

scaling factor for vertical shifts of blocks

tikz_options str

TikZ node options

Source code in pyrbd/block.py
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
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
class Group(Block):
    """Group of `Block` instances for vertical stacking.

    Parameters
    ----------
    blocks : list[Block]
        list of `Block` instances
    text : str, optional
        group label text
    color : str, optional
        group color
    parent : Optional[Block]
        parent `Block` instance

    Attributes
    ----------
    shift_scale : float
        scaling factor for vertical shifts of blocks
    tikz_options : str
        TikZ node options
    """

    shift_scale: float = 1.2
    tikz_options: str = ", ".join(
        [
            "anchor=west",
        ]
    )
    internal_arrow_length = 0.3
    pad = Padding(1, 1, 1, 1)
    label_height = 5

    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 background(self) -> str:
        """Background rectangle TikZ string."""

        if self.color in ("white", ""):
            return ""

        pad = self.pad

        return "".join(
            [
                "\\begin{pgfonlayer}{background}\n",
                f"\\coordinate (sw) at ($({self.id}.south west)+(-{pad.w}mm, -{pad.s}mm)$);\n",
                f"\\coordinate (ne) at ($({self.id}.north east)+({pad.e}mm, {pad.n}mm)$);\n",
                f"\\draw[{self.color}, thick] (sw) rectangle (ne);\n",
                "\\end{pgfonlayer}\n",
            ]
        )

    @property
    def label(self) -> str:
        """Series label string."""

        if len(self.text) == 0:
            return ""

        pad = self.pad

        return "".join(
            [
                f"\\coordinate (nw) at ($({self.id}.north west)+(-{pad.w}mm, {pad.n}mm)$);\n",
                f"\\coordinate (ne) at ($({self.id}.north east)+({pad.e}mm, {pad.n}mm)$);\n",
                "\\coordinate (n) at ",
                f"($({self.id}.north)+(0mm, {self.label_height / 2 + pad.n}mm)$);\n",
                f"\\draw[{self.color}, fill={self.color}!50, thick] (nw) ",
                f"rectangle ($(ne)+(0, {self.label_height}mm)$);\n",
                f"\\node[anchor=center, inner sep=0pt, outer sep=0pt] at (n) {{{self.text}}};\n",
            ]
        )

    def arrow(self, connector_position: float) -> str:
        """Get TikZ arrow string.

        Parameters
        ----------
        connector_position : float
            distance in cm to right angle bend in connector (not used in `Group` class)

        Returns
        -------
        str
            TikZ string for arrow from `parent` to `self` or empty string if `parent` is `None`
        """

        if self.parent is None:
            return ""

        return f"\\draw[{self.arrow_options}] ({self.parent.id}.east) to ({self.id}.west);\n"

    @property
    def arrows(self) -> str:
        """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 "\n".join(
            [
                " ".join(
                    [
                        f"\\draw[{self.arrow_options},",
                        f"rectangle line={scaling * self.internal_arrow_length}cm]",
                        f"({longest_series.id}.east) to ({block.id}.east);\n",
                    ]
                )
                for block in blocks
            ]
        )

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

        Parameters
        ----------
        connector_position : Optional[float]
            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 = "\n".join(
            block.get_node(connector_position) for block in self.blocks
        )

        group_node = "".join(
            [
                "%%% Group\n"
                f"\\node[anchor=west, outer sep=0pt, inner sep=0pt, align=center] ({self.id}) ",
                self.position,
                "{\\begin{tikzpicture}\n",
                f"\\coordinate ({self.id}) at (0, 0);\n",
                block_nodes,
                self.arrows,
                "\\end{tikzpicture}};\n\n",
                self.arrow(connector_position),
                self.background,
                self.label,
            ]
        )

        return group_node

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

arrows property

Get TikZ string for arrow connecting stacked blocks.

background property

Background rectangle TikZ string.

label property

Series label string.

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

arrow(connector_position)

Get TikZ arrow string.

Parameters:

Name Type Description Default
connector_position float

distance in cm to right angle bend in connector (not used in Group class)

required

Returns:

Type Description
str

TikZ string for arrow from parent to self or empty string if parent is None

Source code in pyrbd/block.py
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
def arrow(self, connector_position: float) -> str:
    """Get TikZ arrow string.

    Parameters
    ----------
    connector_position : float
        distance in cm to right angle bend in connector (not used in `Group` class)

    Returns
    -------
    str
        TikZ string for arrow from `parent` to `self` or empty string if `parent` is `None`
    """

    if self.parent is None:
        return ""

    return f"\\draw[{self.arrow_options}] ({self.parent.id}.east) to ({self.id}.west);\n"

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
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
def get_node(self, connector_position: Optional[float] = None) -> str:
    """Get TikZ node string.

    Parameters
    ----------
    connector_position : Optional[float]
        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 = "\n".join(
        block.get_node(connector_position) for block in self.blocks
    )

    group_node = "".join(
        [
            "%%% Group\n"
            f"\\node[anchor=west, outer sep=0pt, inner sep=0pt, align=center] ({self.id}) ",
            self.position,
            "{\\begin{tikzpicture}\n",
            f"\\coordinate ({self.id}) at (0, 0);\n",
            block_nodes,
            self.arrows,
            "\\end{tikzpicture}};\n\n",
            self.arrow(connector_position),
            self.background,
            self.label,
        ]
    )

    return group_node

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

''
parent Optional[Block]

parent Block instance

None

Attributes:

Name Type Description
tikz_options str

TikZ node options

Source code in pyrbd/block.py
199
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
302
303
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
class Series(Block):
    """Series configuration of `Block` instances for horisontal grouping.

    Parameters
    ----------
    blocks : list[Block]
        list of `Block` instances
    text: str, optional
        series label text
    color: str, optional
        series color
    parent : Optional[Block]
        parent `Block` instance

    Attributes
    ----------
    tikz_options : str
        TikZ node options

    """

    tikz_options: str = ", ".join(
        [
            "anchor=west",
            "align=center",
            "inner sep=0pt",
            "outer sep=0pt",
        ]
    )

    internal_arrow_length = 0.3
    pad = Padding(1, 1, 1, 2.5)
    label_height = 5

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

        self.blocks = blocks
        self.blocks[0].id = f"{self.id}+0"
        self.blocks[0].shift = (self.internal_arrow_length, 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}"
            block.arrow_length = self.internal_arrow_length

    @property
    def background(self) -> str:
        """Background rectangle TikZ string."""

        if self.color in ("white", ""):
            return ""

        pad = self.pad

        return "".join(
            [
                "\\begin{pgfonlayer}{background}\n",
                f"\\coordinate (sw) at ($({self.id}.south west)+(-{pad.w}mm, -{pad.s}mm)$);\n",
                f"\\coordinate (ne) at ($({self.id}.north east)+({pad.e}mm, {pad.n}mm)$);\n",
                f"\\draw[{self.color}, thick] (sw) rectangle (ne);\n",
                "\\end{pgfonlayer}\n",
            ]
        )

    @property
    def label(self) -> str:
        """Series label string."""

        if len(self.text) == 0:
            return ""

        pad = self.pad

        return "".join(
            [
                f"\\coordinate (nw) at ($({self.id}.north west)+(-{pad.w}mm, {pad.n}mm)$);\n",
                f"\\coordinate (ne) at ($({self.id}.north east)+({pad.e}mm, {pad.n}mm)$);\n",
                "\\coordinate (n) at "
                f"($({self.id}.north)+(0mm, {self.label_height / 2 + pad.n}mm)$);\n",
                f"\\draw[{self.color}, fill={self.color}!50, thick] (nw) ",
                f"rectangle ($(ne)+(0, {self.label_height}mm)$);\n",
                f"\\node[anchor=center, inner sep=0pt, outer sep=0pt] at (n) {{{self.text}}};\n",
            ]
        )

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

        Parameters
        ----------
        connector_position : Optional[float]
            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 = "\n".join(
            block.get_node(connector_position) for block in self.blocks
        )
        series_node = "".join(
            [
                f"%%% Series\n\\node[{self.tikz_options}]",
                f"({self.id})",
                self.position,
                "{\\begin{tikzpicture}\n",
                block_nodes,
                "\\end{tikzpicture}};\n\n",
                self.arrow(connector_position),
                self.background,
                self.label,
            ]
        )
        return series_node

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

background property

Background rectangle TikZ string.

label property

Series label string.

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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
def get_node(self, connector_position: Optional[float] = None) -> str:
    """Get TikZ node string.

    Parameters
    ----------
    connector_position : Optional[float]
        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 = "\n".join(
        block.get_node(connector_position) for block in self.blocks
    )
    series_node = "".join(
        [
            f"%%% Series\n\\node[{self.tikz_options}]",
            f"({self.id})",
            self.position,
            "{\\begin{tikzpicture}\n",
            block_nodes,
            "\\end{tikzpicture}};\n\n",
            self.arrow(connector_position),
            self.background,
            self.label,
        ]
    )
    return series_node