Skip to content

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