Cantor
The Cantor set as the substitution of every interval by its two outer parts.
- Name:
cantor_set - Field:
mathematics/fractals - Formalism: rewriting system
- References: Cantor 1883; Thurner, Hanel, and Klimek 2018; Trzaska 2023
Factory
@register_model(
field='mathematics/fractals',
tags=('fractal', 'substitution', 'rewriting system'),
references=('Cantor 1883', 'Thurner, Hanel, and Klimek 2018', 'Trzaska 2023'),
defaults={'compiler': RewritingCompiler(), 'until': 8, 'dt': 1},
)
def cantor_set(ratio: float = 1 / 3) -> System:
"""Return the Cantor set as the substitution of every interval by its two outer parts.
Every generation keeps the first and the last part of each interval, each `ratio` of its length, and removes the
middle. With the ratio of one third, generation `n` has `2^n` intervals of total length `(2/3)^n`, and the limit set
has the similarity dimension `log 2 / log 3`, about 0.631.
Args:
ratio:
The length of each kept part relative to its interval, below one half.
Returns:
system:
The model, with the patch `cantor` and its number of intervals `cantor.size`.
Raises:
ValidationError:
The ratio is not between 0 and 1/2.
Examples:
>>> from skcomplex.models import run
>>> run('cantor_set', settings={'until': 3}).trajectory_.values('cantor.size').tolist()
[1.0, 2.0, 4.0, 8.0]
"""
if not 0 < ratio < LARGEST_RATIO:
error_msg = f'ratio = {ratio!r} must lie between 0 and 1/2.'
raise ValidationError(error_msg)
interval = PrototileSpec('interval', [(0.0, 0.0), (1.0, 0.0)])
return System(
elements=[CantorElement('cantor', prototiles=[interval], bounds=(-0.03, -0.1, 1.03, 0.1), ratio=ratio)],
)
Components
class CantorElement(PatchElement):
"""Patch of intervals, each replaced by its first and its last part of the length `ratio`.
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.
ratio:
The length of each kept part relative to its interval.
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 CantorElement
>>> CantorElement('cantor', ratio=0.25).ratio
0.25
"""
def __init__(
self,
label: str,
tiles: Sequence[tuple[str, AffineSpec]] | None = None,
*,
ratio: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1 / 3,
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.ratio = ratio
def _get_laws(self, s: SimpleNamespace) -> list[SubstitutionLaw]:
"""Return the substitution of every interval by its two outer parts.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The substitution of the patch.
"""
pieces = [
('interval', AffineSpec(scale=s.ratio)),
('interval', AffineSpec(scale=s.ratio, translation=(1 - s.ratio, 0))),
]
return [SubstitutionLaw('interval', pieces)]