Sierpiński substitutions
The Sierpiński triangle or carpet as the substitution of a polygon by scaled copies of itself.
- Name:
sierpinski_substitution - Field:
mathematics/fractals - Formalism: rewriting system
- References: Sierpiński 1915; Sierpiński 1916; Thurner, Hanel, and Klimek 2018; Trzaska 2023
Factory
@register_model(
field='mathematics/fractals',
tags=('fractal', 'substitution', 'rewriting system'),
references=('Sierpiński 1915', 'Sierpiński 1916', 'Thurner, Hanel, and Klimek 2018', 'Trzaska 2023'),
defaults={'compiler': RewritingCompiler(), 'until': 6, 'dt': 1},
)
def sierpinski_substitution(shape: str = 'triangle', *, filled: bool = True) -> System:
"""Return the Sierpiński triangle or carpet as the substitution of a polygon by scaled copies of itself.
The triangle is replaced by its three corner copies of half its side, so generation `n` has `3^n` triangles of
total area `(3/4)^n` times the first, and the gasket has the similarity dimension `log 3 / log 2`, about 1.585. The
square is replaced by the eight copies of a third of its side around its middle, so generation `n` has `8^n`
squares of area `(8/9)^n`, and the carpet has the dimension `log 8 / log 3`, about 1.893.
Args:
shape:
`'triangle'` or `'carpet'`.
filled:
Whether the geometry visual draws the polygons as a filled image instead of outlines.
Returns:
system:
The model, with the patch `sierpinski` and its number of polygons `sierpinski.size`.
Raises:
ValidationError:
The shape is unknown.
Examples:
>>> from skcomplex.models import run
>>> run('sierpinski_substitution', settings={'until': 2}).trajectory_.values('sierpinski.size').tolist()
[1.0, 3.0, 9.0]
"""
prototile, _ = SierpinskiElement._sierpinski_tiles(shape)
return System(elements=[SierpinskiElement('sierpinski', shape=shape, prototiles=[prototile], filled=filled)])
Components
class SierpinskiElement(PatchElement):
"""Patch of triangles or squares, each replaced by the scaled copies of the Sierpiński triangle or carpet.
Args:
label:
The label of the patch.
tiles:
Pairs of a prototile name and its placement at generation 0, or `None` for the first prototile in place.
shape:
`'triangle'` or `'carpet'`.
prototiles:
The prototiles of the patch.
filled:
Whether the geometry visual draws polygons as a filled image instead of outlines.
max_size:
The largest number of tiles a generation may produce.
bounds:
The box `(xmin, ymin, xmax, ymax)` the geometry visual shows, or `None` for the box of the initial tiles.
visual:
How the patch is drawn, or `None` for `default_visual_`.
Examples:
>>> from skcomplex.models.mathematics.fractals import SierpinskiElement
>>> len(SierpinskiElement('sierpinski', shape='carpet')._get_laws(None)[0].pieces)
8
"""
def __init__(
self,
label: str,
tiles: Sequence[tuple[str, AffineSpec]] | None = None,
*,
shape: str = 'triangle',
prototiles: Sequence[PrototileSpec] = (),
filled: bool = False,
max_size: int = 10_000_000,
bounds: Sequence[float] | None = None,
visual: VisualSpec | None = None,
) -> None:
super().__init__(
label,
tiles,
prototiles=prototiles,
filled=filled,
max_size=max_size,
bounds=bounds,
visual=visual,
)
self.shape = shape
def _get_laws(self, s: SimpleNamespace) -> list[SubstitutionLaw]:
"""Return the substitution of every polygon by its scaled copies.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The substitution of the patch.
Raises:
ValidationError:
The shape is unknown.
"""
del s
prototile, pieces = SierpinskiElement._sierpinski_tiles(self.shape)
return [SubstitutionLaw(prototile.name, pieces)]
@staticmethod
def _sierpinski_tiles(shape: str) -> tuple[PrototileSpec, list[tuple[str, AffineSpec]]]:
"""Return the prototile of a Sierpiński shape and the scaled copies that replace it."""
if shape == 'triangle':
height = np.sqrt(3) / 2
corners = [(0.0, 0.0), (0.5, 0.0), (0.25, height / 2)]
pieces = [('triangle', AffineSpec(scale=0.5, translation=corner)) for corner in corners]
return PrototileSpec('triangle', [(0.0, 0.0), (1.0, 0.0), (0.5, height)]), pieces
if shape == 'carpet':
cells = [(column, row) for row in range(3) for column in range(3) if (column, row) != (1, 1)]
pieces = [('square', AffineSpec(scale=1 / 3, translation=(column / 3, row / 3))) for column, row in cells]
return PrototileSpec('square', [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]), pieces
error_msg = f'Unknown shape {shape!r}. Use one of {", ".join(SHAPES)}.'
raise ValidationError(error_msg)