Repository navigation
Clean up the fractals directory #15099
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
cclauss
merged 2 commits into
TheAlgorithms:master
from
priya-sundaram-dev:cleanup-fractals
Aug 28, 2026
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Fractals | ||
|
|
||
| A fractal is a geometric figure that is *self-similar*: zooming into a piece of | ||
| it reveals a copy of the whole. Fractals show up in mathematics, physics, | ||
| computer graphics and even biology (coastlines, ferns, snowflakes). | ||
|
|
||
| This directory collects small, self-contained fractal generators. They fall | ||
| into two groups: | ||
|
|
||
| - **Visual demos** that open a window (via `turtle`) or produce an image | ||
| (via `matplotlib`/`PIL`). Run these directly to see the picture. | ||
| - **Pure-computation** generators whose output can be checked with `doctest`, | ||
| so they run in CI without a display. | ||
|
|
||
| ## Contents | ||
|
|
||
| | File | Fractal | Output | Notes | | ||
| | ---- | ------- | ------ | ----- | | ||
| | [`barnsley_fern.py`](barnsley_fern.py) | Barnsley fern | matplotlib (optional) | Iterated function system; deterministic with a seed | | ||
| | [`julia_sets.py`](julia_sets.py) | Julia sets | matplotlib | Complex-plane escape-time fractal | | ||
| | [`koch_snowflake.py`](koch_snowflake.py) | Koch snowflake | matplotlib | Line-segment subdivision | | ||
| | [`mandelbrot.py`](mandelbrot.py) | Mandelbrot set | PIL image | Complex-plane escape-time fractal | | ||
| | [`sierpinski_carpet.py`](sierpinski_carpet.py) | Sierpinski carpet | text | Integer arithmetic, fully doctested | | ||
| | [`sierpinski_triangle.py`](sierpinski_triangle.py) | Sierpinski triangle | turtle | Recursive midpoint subdivision | | ||
| | [`vicsek.py`](vicsek.py) | Vicsek fractal | turtle | Recursive cross pattern | | ||
|
|
||
| ## Running | ||
|
|
||
| ```bash | ||
| # text fractal – prints to the terminal | ||
| python fractals/sierpinski_carpet.py | ||
|
|
||
| # image fractal – opens a matplotlib window (needs matplotlib) | ||
| python fractals/barnsley_fern.py | ||
|
|
||
| # turtle fractal – opens a drawing window (needs a display) | ||
| python fractals/vicsek.py | ||
| ``` | ||
|
|
||
| ## Further reading | ||
|
|
||
| - Benoit B. Mandelbrot, *The Fractal Geometry of Nature* (1982) | ||
| - Michael Barnsley, *Fractals Everywhere* (1988) | ||
| - <https://en.wikipedia.org/wiki/Fractal> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| """ | ||
| The Barnsley fern is a fractal that resembles the black spleenwort fern. It was | ||
| described by the British mathematician Michael Barnsley in his 1988 book | ||
| *Fractals Everywhere* and is a classic example of an iterated function system | ||
| (IFS). | ||
|
|
||
| An IFS builds a fractal by repeatedly applying a small set of affine | ||
| transformations, each chosen at random with a fixed probability. Starting from | ||
| the point ``(0, 0)`` the fern uses four transformations: | ||
|
|
||
| =============== =========================================== ============ | ||
| Transformation Effect Probability | ||
| =============== =========================================== ============ | ||
| Stem collapse onto the y-axis 1% | ||
| Successive leaf the main self-similar copy of the fern 85% | ||
| Left leaflet a smaller rotated/reflected copy 7% | ||
| Right leaflet another smaller rotated/reflected copy 7% | ||
| =============== =========================================== ============ | ||
|
|
||
| Because the whole picture is produced by chance the doctests below seed Python's | ||
| random generator so that the results are reproducible. Plotting the points with | ||
| matplotlib is optional and only happens when the module is run directly. | ||
|
|
||
| Reference: https://en.wikipedia.org/wiki/Barnsley_fern | ||
| """ | ||
|
|
||
| import random | ||
|
|
||
| # Each row is (a, b, c, d, e, f) for the affine map | ||
| # x' = a*x + b*y + e | ||
| # y' = c*x + d*y + f | ||
| # and the running cumulative probabilities used to pick a transformation. | ||
| TRANSFORMATIONS: tuple[tuple[float, float, float, float, float, float], ...] = ( | ||
| (0.00, 0.00, 0.00, 0.16, 0.00, 0.00), # stem | ||
| (0.85, 0.04, -0.04, 0.85, 0.00, 1.60), # successive smaller leaflets | ||
| (0.20, -0.26, 0.23, 0.22, 0.00, 1.60), # left-hand leaflet | ||
| (-0.15, 0.28, 0.26, 0.24, 0.00, 0.44), # right-hand leaflet | ||
| ) | ||
| CUMULATIVE_PROBABILITIES: tuple[float, ...] = (0.01, 0.86, 0.93, 1.00) | ||
|
|
||
|
|
||
| def transform(point: tuple[float, float], index: int) -> tuple[float, float]: | ||
| """ | ||
| Apply the affine transformation ``index`` to ``point`` and return the image. | ||
|
|
||
| >>> transform((0.0, 0.0), 0) | ||
| (0.0, 0.0) | ||
| >>> transform((1.0, 1.0), 1) | ||
| (0.89, 2.41) | ||
| >>> transform((2.0, 3.0), 3) | ||
| (0.54, 1.68) | ||
| >>> transform((0.0, 0.0), 4) | ||
| Traceback (most recent call last): | ||
| ... | ||
| IndexError: index must be in range 0..3, got 4 | ||
| """ | ||
| if not 0 <= index < len(TRANSFORMATIONS): | ||
| msg = f"index must be in range 0..3, got {index}" | ||
| raise IndexError(msg) | ||
| a, b, c, d, e, f = TRANSFORMATIONS[index] | ||
| x, y = point | ||
| new_x = round(a * x + b * y + e, 12) | ||
| new_y = round(c * x + d * y + f, 12) | ||
| return (new_x, new_y) | ||
|
|
||
|
|
||
| def choose_transformation(sample: float) -> int: | ||
| """ | ||
| Map a value ``sample`` from ``[0, 1)`` to a transformation index using the | ||
| cumulative probabilities of the fern. | ||
|
|
||
| >>> choose_transformation(0.0) | ||
| 0 | ||
| >>> choose_transformation(0.5) | ||
| 1 | ||
| >>> choose_transformation(0.9) | ||
| 2 | ||
| >>> choose_transformation(0.97) | ||
| 3 | ||
| """ | ||
| for index, threshold in enumerate(CUMULATIVE_PROBABILITIES): | ||
| if sample < threshold: | ||
| return index | ||
| return len(CUMULATIVE_PROBABILITIES) - 1 | ||
|
|
||
|
|
||
| def generate_fern( | ||
| iterations: int, seed: int | None = None | ||
| ) -> list[tuple[float, float]]: | ||
| """ | ||
| Generate ``iterations`` points of the Barnsley fern, starting at ``(0, 0)``. | ||
|
|
||
| Passing a ``seed`` makes the (otherwise random) output reproducible, which is | ||
| what keeps the doctests deterministic. | ||
|
|
||
| >>> points = generate_fern(5, seed=0) | ||
| >>> len(points) | ||
| 5 | ||
| >>> points[0] | ||
| (0.0, 0.0) | ||
| >>> points # doctest: +NORMALIZE_WHITESPACE | ||
| [(0.0, 0.0), (0.0, 1.6), (0.064, 2.96), | ||
| (0.1728, 4.11344), (0.3114176, 5.089512)] | ||
|
|
||
| Every fern point lives inside the well known bounding box. | ||
|
|
||
| >>> cloud = generate_fern(2000, seed=42) | ||
| >>> all(-2.182 <= x <= 2.6558 for x, _ in cloud) | ||
| True | ||
| >>> all(0.0 <= y <= 9.9984 for _, y in cloud) | ||
| True | ||
| >>> generate_fern(0) | ||
| Traceback (most recent call last): | ||
| ... | ||
| ValueError: iterations must be positive, got 0 | ||
| """ | ||
| if iterations <= 0: | ||
| msg = f"iterations must be positive, got {iterations}" | ||
| raise ValueError(msg) | ||
| rng = random.Random(seed) | ||
| point = (0.0, 0.0) | ||
| points = [point] | ||
| for _ in range(iterations - 1): | ||
| index = choose_transformation(rng.random()) | ||
| point = transform(point, index) | ||
| points.append(point) | ||
| return points | ||
|
|
||
|
|
||
| def main() -> None: | ||
| try: | ||
| import matplotlib.pyplot as plt | ||
| except ImportError: | ||
| print("matplotlib is required to plot the fern (pip install matplotlib).") | ||
| return | ||
| points = generate_fern(100_000, seed=0) | ||
| xs = [x for x, _ in points] | ||
| ys = [y for _, y in points] | ||
| plt.figure(figsize=(4, 8)) | ||
| plt.scatter(xs, ys, s=0.2, color="forestgreen") | ||
| plt.axis("off") | ||
| plt.title("Barnsley fern") | ||
| plt.show() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| """ | ||
| The Sierpinski carpet is a plane fractal first described by Wacław Sierpiński | ||
| in 1916. It is a two-dimensional generalisation of the Cantor set and a close | ||
| relative of the Sierpinski triangle. | ||
|
|
||
| Construction | ||
| Start from a filled square. Divide it into a 3x3 grid of nine equal | ||
| sub-squares and remove the central one. Then apply the same procedure | ||
| recursively to each of the eight remaining sub-squares, forever. | ||
|
|
||
| A convenient way to decide whether a single cell of the ``3**n x 3**n`` grid is | ||
| filled (part of the carpet) or empty (a hole) is to look at the base-3 digits | ||
| of its row and column indices: the cell is a hole if and only if, at some | ||
| level, both the row digit and the column digit are equal to ``1`` (the centre | ||
| of that 3x3 block). | ||
|
|
||
| This module builds the carpet purely with integer arithmetic, so every | ||
| function is deterministic and can be verified with doctests -- no plotting or | ||
| turtle graphics required. | ||
|
|
||
| Reference: https://en.wikipedia.org/wiki/Sierpi%C5%84ski_carpet | ||
| """ | ||
|
|
||
|
|
||
| def is_filled(row: int, col: int) -> bool: | ||
| """ | ||
| Return ``True`` when the cell at (``row``, ``col``) belongs to the carpet | ||
| and ``False`` when it falls inside one of the removed central squares. | ||
|
|
||
| The result is independent of the fractal depth: a cell is a hole as soon as | ||
| any pair of matching base-3 digits equals ``(1, 1)``. | ||
|
|
||
| >>> is_filled(0, 0) | ||
| True | ||
| >>> is_filled(1, 1) # the very first central square is removed | ||
| False | ||
| >>> is_filled(4, 4) # centre of the centre block -> still a hole | ||
| False | ||
| >>> is_filled(0, 4) | ||
| True | ||
| >>> [is_filled(1, col) for col in range(3)] | ||
| [True, False, True] | ||
|
|
||
| Negative coordinates make no sense for a grid index. | ||
|
|
||
| >>> is_filled(-1, 0) | ||
| Traceback (most recent call last): | ||
| ... | ||
| ValueError: row and col must be non-negative, got (-1, 0) | ||
| """ | ||
| if row < 0 or col < 0: | ||
| msg = f"row and col must be non-negative, got ({row}, {col})" | ||
| raise ValueError(msg) | ||
| while row > 0 or col > 0: | ||
| if row % 3 == 1 and col % 3 == 1: | ||
| return False | ||
| row //= 3 | ||
| col //= 3 | ||
| return True | ||
|
|
||
|
|
||
| def generate_carpet(depth: int, filled: str = "#", hole: str = " ") -> list[str]: | ||
| """ | ||
| Build the Sierpinski carpet of the given ``depth`` as a list of strings. | ||
|
|
||
| A depth of ``0`` is a single filled cell; each extra level multiplies the | ||
| side length by three. | ||
|
|
||
| >>> generate_carpet(0) | ||
| ['#'] | ||
| >>> for line in generate_carpet(1): | ||
| ... print(line) | ||
| ### | ||
| # # | ||
| ### | ||
| >>> for line in generate_carpet(2, filled="X", hole="."): | ||
| ... print(line) | ||
| XXXXXXXXX | ||
| X.XX.XX.X | ||
| XXXXXXXXX | ||
| XXX...XXX | ||
| X.X...X.X | ||
| XXX...XXX | ||
| XXXXXXXXX | ||
| X.XX.XX.X | ||
| XXXXXXXXX | ||
| >>> generate_carpet(-1) | ||
| Traceback (most recent call last): | ||
| ... | ||
| ValueError: depth must be non-negative, got -1 | ||
| """ | ||
| if depth < 0: | ||
| msg = f"depth must be non-negative, got {depth}" | ||
| raise ValueError(msg) | ||
| size = 3**depth | ||
| return [ | ||
| "".join(filled if is_filled(row, col) else hole for col in range(size)) | ||
| for row in range(size) | ||
| ] | ||
|
|
||
|
|
||
| def count_filled_cells(depth: int) -> int: | ||
| """ | ||
| Return how many cells are filled in a carpet of the given ``depth``. | ||
|
|
||
| Each level keeps eight of the nine sub-squares, so the count is ``8**depth``. | ||
| Verifying this closed form against a brute-force scan is a nice sanity check. | ||
|
|
||
| >>> [count_filled_cells(depth) for depth in range(4)] | ||
| [1, 8, 64, 512] | ||
| >>> all( | ||
| ... count_filled_cells(depth) | ||
| ... == sum(line.count("#") for line in generate_carpet(depth)) | ||
| ... for depth in range(4) | ||
| ... ) | ||
| True | ||
| """ | ||
| if depth < 0: | ||
| msg = f"depth must be non-negative, got {depth}" | ||
| raise ValueError(msg) | ||
| return 8**depth | ||
|
|
||
|
|
||
| def main() -> None: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file |
||
| for line in generate_carpet(3): | ||
| print(line) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As there is no test file in this pull request nor any test function or class in the file
fractals/barnsley_fern.py, please provide doctest for the functionmain