Choose the random future you want, then construct the seed that produces it.
TimeLord is a small Python demonstration showing that a spectacularly unlikely sequence from a pseudorandom number generator does not necessarily imply spectacular luck.
The program constructs an ordinary Python integer seed such that normal code like
import random
r = random.Random(seed)
for _ in range(100):
print("H" if r.randrange(2) else "T")
produces:
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
That is 100 consecutive heads.
TimeLord can construct seeds for runs of up to 1,000 consecutive heads.
There is no modified random-number generator, no setstate(), no monkey-patching and no hidden intervention after the seed has been supplied. The demonstration uses an ordinary integer passed directly to:
random.Random(seed)
The trick is that the seed is chosen after the desired future has been specified.
If a fair coin is tossed 100 times, the probability of obtaining 100 heads is
$$ 2^{-100} \approx 7.9\times10^{-31}. $$
For 1,000 heads it is
$$ 2^{-1000} \approx 9.3\times10^{-302}. $$
If the seed had been selected independently beforehand, either result would therefore be extraordinary.
But that is not what TimeLord does.
Instead, we first decide:
I want the next 100 random coin tosses to be heads.
TimeLord then works backwards and constructs an initial seed that makes Python's pseudorandom number generator produce exactly that future.
The resulting sequence is completely reproducible. Anyone given the seed can run ordinary Python and obtain the same 100 heads.
But reproducibility does not establish that the seed itself was independently or randomly selected.
That is the point of the demonstration.
A pseudorandom generator is deterministic.
Once its internal state is fixed, its future output is fixed too.
Normally we work in the forward direction:
seed
↓
internal state
↓
random-looking outputs
TimeLord solves the inverse problem:
desired future outputs
↓
compatible internal state
↓
integer seed
The desired result is therefore not predicted.
It is selected.
This is closely related to statistical ideas such as post-selection, the look-elsewhere effect, and selection bias. An outcome can appear extraordinarily improbable if we calculate its probability as though the conditions that produced it had been fixed independently in advance.
They were not.
Python's standard random module in CPython uses the MT19937 Mersenne Twister pseudorandom number generator.
For the coin toss used here,
r.randrange(2)
CPython ultimately calls:
getrandbits(2)
and repeats if the resulting value is not below 2.
getrandbits(2) takes the top two bits from a tempered 32-bit MT19937 output word.
To force the result to be 1, corresponding here to HEADS, those two bits can simply be constrained to:
01
Because 01 is already below 2, no rejection occurs.
So each required head gives TimeLord just two bit constraints on the MT19937 output.
For 100 heads there are 200 constraints.
For 1,000 heads there are 2,000.
MT19937 has a state containing roughly 20,000 bits, leaving enormous freedom even after those constraints have been imposed.
The important property exploited here is that the MT19937 twist and temper transformations can be represented as linear operations over the two-element field (GF(2)).
In other words, at the bit level they can be expressed using systems of XOR-based linear equations.
TimeLord:
01;This produces an MT19937 state whose future outputs begin with the requested sequence of heads.
The program works across multiple MT19937 output blocks, which is why runs longer than the generator's 624-word state array can also be constructed.
This is the more interesting part.
It would be easy to construct the required MT state and then use:
random.setstate(...)
But that would weaken the demonstration.
TimeLord does not do that.
Instead, it reverses CPython's MT19937 integer-seeding procedure.
CPython turns an arbitrary-size Python integer into a series of 32-bit words and feeds them through the MT19937 init_by_array initialisation algorithm.
TimeLord works backwards through those mixing operations to find the 624 little-endian 32-bit seed words that generate the state it has just constructed.
Those words are then combined into one ordinary, although very large, positive Python integer.
The final result is simply:
seed = <very large integer>
r = random.Random(seed)
From that point onwards everything is completely standard Python.
No third-party packages are required.
Generate a seed producing the default 100 heads:
python3 find_heads_seed.py
Generate a seed for 500 heads:
python3 find_heads_seed.py 500
Generate one for 1,000 heads:
python3 find_heads_seed.py 1000
The supported range is currently:
1–1000 heads
The generated seed is written to:
seed_<N>_heads.txt
For example:
seed_100_heads.txt
seed_1000_heads.txt
Once a seed has been generated:
python3 demo_heads.py 100
or:
python3 demo_heads.py 1000
The important point is that demo_heads.py contains none of the inversion machinery.
It simply loads the integer seed and uses ordinary Python random-number generation.
That separation is deliberate: the demo is intended to make clear that nothing unusual happens while the apparent "coin tossing" takes place.
The unusual step occurred earlier, when the seed was selected.
python3 find_text_seed.py "THE FUTURE IS ALREADY WRITTEN."
python3 demo_text.py
The demo prints THE FUTURE IS ALREADY WRITTEN. using only an ordinary
random.Random(seed) and successive chr(r.randrange(128)) calls.
Hand someone just demo_text.py and seed_text.txt: the file contains only
a hexadecimal integer (0x...), just like the heads seed files. No message
length is stored or known by the demo. The constructor appends ASCII 30 and 31
(record separator and unit separator); the demo stops at this pair without
printing it, then prints a final newline. A one-character buffer keeps the
terminator out of the output. Individual control characters remain supported,
but the consecutive pair \x1e\x1f is reserved and rejected in input.
Coin tossing constrains the future to one of two symbols. Text generation
uses a larger alphabet. For 128-character ASCII, CPython's randrange(128)
requests eight bits (128.bit_length() is 8), rejecting values at least
128. getrandbits(8) takes bits 31 through 24 of a tempered MT19937 word,
most significant bit first. Constraining these to the desired ASCII value
makes every draw accepted immediately: eight equations per character.
This behavior is checked against the installed CPython in the tests.
choose a message
↓
construct its required future MT outputs
↓
solve backwards for the state
↓
construct an ordinary Python integer seed
↓
give that seed to an otherwise trivial Python program
↓
the "random" program writes the chosen message
Both constructors share the original GF(2) solver and reverse integer-seeding
machinery in timelord_mt.py. Free state bits retain random values; use
--free-seed 42 for reproducible construction. Seeds are saved in hexadecimal;
--show-seed optionally prints the large integer. Diagnostics report constraint
count, achieved rank, remaining free bits, seed size, timing and verification.
Only ASCII values 0..127 are accepted. For control characters, use
python3 find_text_seed.py --file message.bin; the file is read as raw bytes
with no encoding or newline conversion. Empty input is supported too.
Capacity is determined by constraint consistency, not a hard-coded message
length. The two terminator characters add 16 constraints. On CPython 3.9.6,
a repeated The future is already written. prefix of 2490 message characters
plus the terminator verified at rank 19,936 with no free state bits. A prefix
of 2491 message characters plus the terminator was inconsistent. This is a
measured boundary for that message, not a guaranteed maximum: dependent but
consistent constraints are accepted, and failure reports the rank achieved
before the contradiction.
MT19937 is an established generator; this demonstration extends the existing seed construction to text. It is not suitable for cryptographic use.
By default, TimeLord fills the unconstrained parts of the MT19937 state using operating-system entropy.
This means repeated runs will normally produce different seeds, all satisfying the requested future.
To make the construction itself reproducible, supply --free-seed:
python3 find_heads_seed.py 1000 --free-seed 42
This fixes the otherwise unconstrained bits and therefore reproduces the same generated seed.
The constructed seeds are very large integers.
Python 3.11 and later impose a default limit on decimal integer/string conversions of 4,300 digits. Hexadecimal representation avoids this limitation conveniently and is also a natural representation for the underlying 32-bit seed words.
The test suite uses only the Python standard library:
python3 -m unittest discover -s tests -v
TimeLord does not show that fair random processes naturally produce 1,000 heads with appreciable probability.
They do not.
Nor does it show that Python's random module is statistically defective for its intended simulation uses.
It demonstrates something different:
An apparently improbable pseudorandom outcome tells us very little unless we also know how the initial conditions were selected.
A result may be:
and yet completely unsurprising once we learn that the seed was chosen conditional on obtaining that result.
This distinction matters well beyond toy coin tosses. It is the same general issue encountered whenever researchers search many possibilities and subsequently report the one producing an interesting outcome.
A Time Lord does not need to wait passively to discover which future happens.
They select the future they want.
TimeLord does much the same thing to MT19937:
choose future
↓
solve backwards
↓
construct initial conditions
↓
watch the chosen future unfold
No time travel required.
TimeLord is tied to the MT19937 implementation and integer-seeding behaviour used by CPython.
It relies on details corresponding to CPython's:
Modules/_randommodule.c
Consequently, generated seeds should not be assumed to produce the same behaviour in unrelated Python implementations or generators with different seeding algorithms.
The repository includes verified seed fixtures for both 100 and 1,000 consecutive heads.
Python
100.0%
Choose the random future you want, then construct the seed that produces it.
TimeLord is a small Python demonstration showing that a spectacularly unlikely sequence from a pseudorandom number generator does not necessarily imply spectacular luck.
The program constructs an ordinary Python integer seed such that normal code like
import random
r = random.Random(seed)
for _ in range(100):
print("H" if r.randrange(2) else "T")
produces:
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
That is 100 consecutive heads.
TimeLord can construct seeds for runs of up to 1,000 consecutive heads.
There is no modified random-number generator, no setstate(), no monkey-patching and no hidden intervention after the seed has been supplied. The demonstration uses an ordinary integer passed directly to:
random.Random(seed)
The trick is that the seed is chosen after the desired future has been specified.
If a fair coin is tossed 100 times, the probability of obtaining 100 heads is
$$ 2^{-100} \approx 7.9\times10^{-31}. $$
For 1,000 heads it is
$$ 2^{-1000} \approx 9.3\times10^{-302}. $$
If the seed had been selected independently beforehand, either result would therefore be extraordinary.
But that is not what TimeLord does.
Instead, we first decide:
I want the next 100 random coin tosses to be heads.
TimeLord then works backwards and constructs an initial seed that makes Python's pseudorandom number generator produce exactly that future.
The resulting sequence is completely reproducible. Anyone given the seed can run ordinary Python and obtain the same 100 heads.
But reproducibility does not establish that the seed itself was independently or randomly selected.
That is the point of the demonstration.
A pseudorandom generator is deterministic.
Once its internal state is fixed, its future output is fixed too.
Normally we work in the forward direction:
seed
↓
internal state
↓
random-looking outputs
TimeLord solves the inverse problem:
desired future outputs
↓
compatible internal state
↓
integer seed
The desired result is therefore not predicted.
It is selected.
This is closely related to statistical ideas such as post-selection, the look-elsewhere effect, and selection bias. An outcome can appear extraordinarily improbable if we calculate its probability as though the conditions that produced it had been fixed independently in advance.
They were not.
Python's standard random module in CPython uses the MT19937 Mersenne Twister pseudorandom number generator.
For the coin toss used here,
r.randrange(2)
CPython ultimately calls:
getrandbits(2)
and repeats if the resulting value is not below 2.
getrandbits(2) takes the top two bits from a tempered 32-bit MT19937 output word.
To force the result to be 1, corresponding here to HEADS, those two bits can simply be constrained to:
01
Because 01 is already below 2, no rejection occurs.
So each required head gives TimeLord just two bit constraints on the MT19937 output.
For 100 heads there are 200 constraints.
For 1,000 heads there are 2,000.
MT19937 has a state containing roughly 20,000 bits, leaving enormous freedom even after those constraints have been imposed.
The important property exploited here is that the MT19937 twist and temper transformations can be represented as linear operations over the two-element field (GF(2)).
In other words, at the bit level they can be expressed using systems of XOR-based linear equations.
TimeLord:
01;This produces an MT19937 state whose future outputs begin with the requested sequence of heads.
The program works across multiple MT19937 output blocks, which is why runs longer than the generator's 624-word state array can also be constructed.
This is the more interesting part.
It would be easy to construct the required MT state and then use:
random.setstate(...)
But that would weaken the demonstration.
TimeLord does not do that.
Instead, it reverses CPython's MT19937 integer-seeding procedure.
CPython turns an arbitrary-size Python integer into a series of 32-bit words and feeds them through the MT19937 init_by_array initialisation algorithm.
TimeLord works backwards through those mixing operations to find the 624 little-endian 32-bit seed words that generate the state it has just constructed.
Those words are then combined into one ordinary, although very large, positive Python integer.
The final result is simply:
seed = <very large integer>
r = random.Random(seed)
From that point onwards everything is completely standard Python.
No third-party packages are required.
Generate a seed producing the default 100 heads:
python3 find_heads_seed.py
Generate a seed for 500 heads:
python3 find_heads_seed.py 500
Generate one for 1,000 heads:
python3 find_heads_seed.py 1000
The supported range is currently:
1–1000 heads
The generated seed is written to:
seed_<N>_heads.txt
For example:
seed_100_heads.txt
seed_1000_heads.txt
Once a seed has been generated:
python3 demo_heads.py 100
or:
python3 demo_heads.py 1000
The important point is that demo_heads.py contains none of the inversion machinery.
It simply loads the integer seed and uses ordinary Python random-number generation.
That separation is deliberate: the demo is intended to make clear that nothing unusual happens while the apparent "coin tossing" takes place.
The unusual step occurred earlier, when the seed was selected.
python3 find_text_seed.py "THE FUTURE IS ALREADY WRITTEN."
python3 demo_text.py
The demo prints THE FUTURE IS ALREADY WRITTEN. using only an ordinary
random.Random(seed) and successive chr(r.randrange(128)) calls.
Hand someone just demo_text.py and seed_text.txt: the file contains only
a hexadecimal integer (0x...), just like the heads seed files. No message
length is stored or known by the demo. The constructor appends ASCII 30 and 31
(record separator and unit separator); the demo stops at this pair without
printing it, then prints a final newline. A one-character buffer keeps the
terminator out of the output. Individual control characters remain supported,
but the consecutive pair \x1e\x1f is reserved and rejected in input.
Coin tossing constrains the future to one of two symbols. Text generation
uses a larger alphabet. For 128-character ASCII, CPython's randrange(128)
requests eight bits (128.bit_length() is 8), rejecting values at least
128. getrandbits(8) takes bits 31 through 24 of a tempered MT19937 word,
most significant bit first. Constraining these to the desired ASCII value
makes every draw accepted immediately: eight equations per character.
This behavior is checked against the installed CPython in the tests.
choose a message
↓
construct its required future MT outputs
↓
solve backwards for the state
↓
construct an ordinary Python integer seed
↓
give that seed to an otherwise trivial Python program
↓
the "random" program writes the chosen message
Both constructors share the original GF(2) solver and reverse integer-seeding
machinery in timelord_mt.py. Free state bits retain random values; use
--free-seed 42 for reproducible construction. Seeds are saved in hexadecimal;
--show-seed optionally prints the large integer. Diagnostics report constraint
count, achieved rank, remaining free bits, seed size, timing and verification.
Only ASCII values 0..127 are accepted. For control characters, use
python3 find_text_seed.py --file message.bin; the file is read as raw bytes
with no encoding or newline conversion. Empty input is supported too.
Capacity is determined by constraint consistency, not a hard-coded message
length. The two terminator characters add 16 constraints. On CPython 3.9.6,
a repeated The future is already written. prefix of 2490 message characters
plus the terminator verified at rank 19,936 with no free state bits. A prefix
of 2491 message characters plus the terminator was inconsistent. This is a
measured boundary for that message, not a guaranteed maximum: dependent but
consistent constraints are accepted, and failure reports the rank achieved
before the contradiction.
MT19937 is an established generator; this demonstration extends the existing seed construction to text. It is not suitable for cryptographic use.
By default, TimeLord fills the unconstrained parts of the MT19937 state using operating-system entropy.
This means repeated runs will normally produce different seeds, all satisfying the requested future.
To make the construction itself reproducible, supply --free-seed:
python3 find_heads_seed.py 1000 --free-seed 42
This fixes the otherwise unconstrained bits and therefore reproduces the same generated seed.
The constructed seeds are very large integers.
Python 3.11 and later impose a default limit on decimal integer/string conversions of 4,300 digits. Hexadecimal representation avoids this limitation conveniently and is also a natural representation for the underlying 32-bit seed words.
The test suite uses only the Python standard library:
python3 -m unittest discover -s tests -v
TimeLord does not show that fair random processes naturally produce 1,000 heads with appreciable probability.
They do not.
Nor does it show that Python's random module is statistically defective for its intended simulation uses.
It demonstrates something different:
An apparently improbable pseudorandom outcome tells us very little unless we also know how the initial conditions were selected.
A result may be:
and yet completely unsurprising once we learn that the seed was chosen conditional on obtaining that result.
This distinction matters well beyond toy coin tosses. It is the same general issue encountered whenever researchers search many possibilities and subsequently report the one producing an interesting outcome.
A Time Lord does not need to wait passively to discover which future happens.
They select the future they want.
TimeLord does much the same thing to MT19937:
choose future
↓
solve backwards
↓
construct initial conditions
↓
watch the chosen future unfold
No time travel required.
TimeLord is tied to the MT19937 implementation and integer-seeding behaviour used by CPython.
It relies on details corresponding to CPython's:
Modules/_randommodule.c
Consequently, generated seeds should not be assumed to produce the same behaviour in unrelated Python implementations or generators with different seeding algorithms.
The repository includes verified seed fixtures for both 100 and 1,000 consecutive heads.
Python
100.0%