Skip to content

Introduce seed in random_color method to produce colors deterministically #4265

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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
118 changes: 113 additions & 5 deletions manim/utils/color/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1501,17 +1501,124 @@
def random_color() -> ManimColor:
"""Return a random :class:`ManimColor`.

.. warning::
This operation is very expensive. Please keep in mind the performance loss.

Returns
-------
ManimColor
A random :class:`ManimColor`.
"""
import manim.utils.color.manim_colors as manim_colors
return RandomColorGenerator._random_color()


class RandomColorGenerator:
_singleton: RandomColorGenerator | None = None
"""A generator for producing random colors from a given list of Manim colors,
optionally in a reproducible sequence using a seed value.

When initialized with a specific seed, this class produces a deterministic
sequence of :class:`.ManimColor` instances. If no seed is provided, the selection is
non-deterministic using Python’s global random state.

Parameters
----------
seed
A seed value to initialize the internal random number generator.
If ``None`` (the default), colors are chosen using the global random state.

sample_colors
A custom list of Manim colors to sample from. Defaults to the full Manim
color palette.

Examples
--------
Without a seed (non-deterministic)::

>>> from manim import RandomColorGenerator, ManimColor, RED, GREEN, BLUE
>>> rnd = RandomColorGenerator()
>>> isinstance(rnd.next(), ManimColor)
True

With a seed (deterministic sequence)::

>>> rnd = RandomColorGenerator(42)
>>> rnd.next()
ManimColor('#ECE7E2')
>>> rnd.next()
ManimColor('#BBBBBB')
>>> rnd.next()
ManimColor('#BBBBBB')

Re-initializing with the same seed gives the same sequence::

>>> rnd2 = RandomColorGenerator(42)
>>> rnd2.next()
ManimColor('#ECE7E2')
>>> rnd2.next()
ManimColor('#BBBBBB')
>>> rnd2.next()
ManimColor('#BBBBBB')

Using a custom color list::

>>> custom_palette = [RED, GREEN, BLUE]
>>> rnd_custom = RandomColorGenerator(1, sample_colors=custom_palette)
>>> rnd_custom.next() in custom_palette
True
>>> rnd_custom.next() in custom_palette
True

Without a seed and custom palette (non-deterministic)::

>>> rnd_nodet = RandomColorGenerator(sample_colors=[RED])
>>> rnd_nodet.next()
ManimColor('#FC6255')
"""

def __init__(
self,
seed: int | None = None,
sample_colors: list[ManimColor] | None = None,
) -> None:
self.choice = random.choice if seed is None else random.Random(seed).choice

from manim.utils.color.manim_colors import _all_manim_colors

Check notice

Code scanning / CodeQL

Cyclic import Note

Import of module
manim.utils.color.manim_colors
begins an import cycle.

return random.choice(manim_colors._all_manim_colors)
self.colors = _all_manim_colors if sample_colors is None else sample_colors

def next(self) -> ManimColor:
"""Returns the next color from the configured color list.

Returns
-------
ManimColor
A randomly selected color from the specified color list.

Examples
--------
Usage::

>>> from manim import RandomColorGenerator, RED
>>> rnd = RandomColorGenerator(sample_colors=[RED])
>>> rnd.next()
ManimColor('#FC6255')
"""
return self.choice(self.colors)

@classmethod
def _random_color(cls) -> ManimColor:
"""Internal method to generate a random color using the singleton instance of
`RandomColorGenerator`.
It will be used by proxy method `random_color` publicly available
and makes it backwards compatible.

Returns
-------
ManimColor:
A randomly selected color from the configured color list of
the singleton instance.
"""
if cls._singleton is None:
cls._singleton = cls()
return cls._singleton.next()


def get_shaded_rgb(
Expand Down Expand Up @@ -1567,6 +1674,7 @@
"average_color",
"random_bright_color",
"random_color",
"RandomColorGenerator",
"get_shaded_rgb",
"HSV",
"RGBA",
Expand Down