Skip to content

dargor/python-guess-random-color

Repository files navigation

Colors, algorithms and fun

A standard #RRGGBB color is a 24-bit number, meaning it has 256^3 = 16777216 possible combinations (!).

I wondered how genetic algorithms worked, and how many iterations would be needed to find a given color.

I thought it was a good choice, because colors are easy to compare, right ?

I've never been so wrong, in all my life.

Quick note about terminal emulators

I lost some time wondering why my algorithms sometimes returned me colors that did not match, so I warn you : terminal emulators more or less correctly implement true colors, which are needed in our use case.

You can test your terminal emulator by running ./test_color.py : True colors or not, that is the question.

If you don't have a nice output, try st : it's easy to compile and run locally, no need to install it as root !

wget http://dl.suckless.org/st/st-0.6.tar.gz
tar xzf st-0.6.tar.gz
cd st-0.6/
make
tic -s st.info
./st &

And voilà, ready to run with some nice colors !

run.py

This is the benchmark main entry point.

$ ./run.py -h
usage: run.py [-h] -a {random,brute,genetic1,genetic2,genetic3} [-c COLOR]
              [-d DELAY] [-f FITNESS] [-i ITERATIONS] [-p POPULATION_SIZE]
              [-s SEED]

optional arguments:
  -h, --help            show this help message and exit
  -a {random,brute,genetic1,genetic2,genetic3}, --algorithm {random,brute,genetic1,genetic2,genetic3}
                        Algorithm to run
  -c COLOR, --color COLOR
                        Color to find
  -d DELAY, --delay DELAY
                        Delay between each iteration
  -f FITNESS, --fitness FITNESS
                        ΔE considered acceptable
  -i ITERATIONS, --iterations ITERATIONS
                        Number of iterations
  -p POPULATION_SIZE, --population-size POPULATION_SIZE
                        Size of population at each iteration
  -s SEED, --seed SEED  Seed to initialize the random number generator

Play with the various options, but the only really useful one is -a.

But what's a ΔE, my precious ?

It's a measure of distance between two colors, lesser being closer.

The wikipedia page and python-colormath documentation explain everything :

ΔE are noted below their corresponding colors in the output of run.py.

The -f option sets the minimum ΔE value required to pass the test, and default to 1.0.

Algo*.py

All our algorithms derive an abstract base class Algo, which is used by run.py to abstract annoying details.

The base population of all algorithms is randomly chosen.

AlgoBrute.py

Brute force, the worst possible thing to do when you have 16 millions of combinations.

$ ./run.py -a brute -s 42
[...]
Color to find   : ████ #390C8C
Color found     : ████ #330A8B (ΔE = 0.980)
Iterations      : 139377
Population size : 24
Colors tested   : 3345048
All that in     : 789.034 seconds

This one erases the default random population to set its own, which allows it to try N (N being the population size) variations at each iteration.

AlgoRandom.py

Random, not necessarily a bad idea.

$ ./run.py -a random -s 42
[...]
Color to find   : ████ #390C8C
Color found     : ████ #3A0892 (ΔE = 0.896)
Iterations      : 516
Population size : 24
Colors tested   : 12384
All that in     : 2.984 seconds

This one has absolutely no subtlety : it generates a new random population at each iteration.

AlgoGenetic.py

Base abstract class for genetic algorithms : selection, reproduction and mutations are taken care of by this abstract class.

The fittest parents get many mating possibilities, while the lesser ones get only a few mating opportunities (but they still get at least one, to preserve our genetic pool diversity).

When we have enough children, we keep the best ones to form a new generation.

After some iterations, the algorithm converges to a quite good solution.

See this page for more details on genetic algorithms.

An actual genetic algorithm must at least define OPS (available crossover operations) and crossover (their implementations).

AlgoGenetic1.py

$ ./run.py -a genetic1 -s 42
[...]
Color to find   : ████ #390C8C
Color found     : ████ #360D8A (ΔE = 0.539)
Algorithm       : AlgoGenetic1
Iterations      : 15
Population size : 26
Colors tested   : 390
All that in     : 0.149 seconds

This one simply combines bits together, trying to maximize diversity.

Works quite well.

AlgoGenetic2.py

$ ./run.py -a genetic2 -s 42
[...]
Color to find   : ████ #390C8C
Color found     : ████ #391090 (ΔE = 0.861)
Algorithm       : AlgoGenetic2
Iterations      : 13
Population size : 26
Colors tested   : 338
All that in     : 0.131 seconds

This one is an artificial equivalent of how natural crossover works, trying to enhance each generation fitness.

Can be really slow to converge, but its solutions are quite good.

AlgoGenetic3.py

$ ./run.py -a genetic3 -s 42
[...]
Color to find   : ████ #390C8C
Color found     : ████ #36078C (ΔE = 0.717)
Algorithm       : AlgoGenetic3
Iterations      : 10
Population size : 26
Colors tested   : 260
All that in     : 0.096 seconds

This one combines both approaches, merging their strengths.

Best average performance.

So, what does it look like ?

Example run

As you can see, colors are converging quite rapidly and selected candidate is really close to the real deal.

Durations over 100 runs

Data and scripts used in bench.*.

Durations over 100 runs

$ ./bench.sh
[...]
AlgoGenetic1 : .290 seconds (mean time)
AlgoGenetic2 : .393 seconds (mean time)
AlgoGenetic3 : .212 seconds (mean time)

License

ISC.

About

Pick a random 24-bit color, and see how quickly python can find a similar one.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published