look_and_say

A python module for exploring look and say sequences in the spirit of John H Conway.

The following assumes familiarity with the terminology and notation for look and sequences introduced by Conway in his delightful article The Weird and Wonderful Chemistry of Audioactive Decay.

This module can be used to recover several of Conway's results on standard look and say sequences. Additionally, it can be used to discover new results on various nonstandard look and say sequences. For an introduction to nonstandard look and say sequences, see the notes here.

Installation

pip install look-and-say

Straight to example sessions

You can read more details about all the objects and methods in the look_and_say module here. The following example sessions skip those details and get straight to some results. More example sessions can be found here.

Standard decimal

The following session illustrates how the look_and_say module can be used to recover some of Conway's results.

>>> from look_and_say import *
>>> 
>>> # The default LookAndSay object uses the standard decimal number system:
... decimal = LookAndSay()
>>> 
>>> # Perform the fundamental look and say operation:
... decimal.say_what_you_see('1222111')
'113231'
>>> 
>>> # Generate a look and say sequence
... decimal.generate_sequence(seed='1', terms=5)
>>> decimal.get_sequence()
['1', '11', '21', '1211', '111221']
>>> 
>>> # Use Conway's splitting theorem to search for all the elements 
... # in the look and say sequence generated from the seed '1'. 
... # This will generate Conway's 92 common elements: 
... chem = Chemistry(decimal)
>>> chem.generate_elements('1')
>>> chem.get_elements()
[H, He, Li, Be, B, C, N, O, F, Ne, Na, Mg, Al, Si, P, S, Cl, Ar, K, Ca, Sc, Ti, V, Cr, Mn, Fe, Co, Ni, Cu, Zn, Ga, Ge, As, Se, Br, Kr, Rb, Sr, Y, Zr, Nb, Mo, Tc, Ru, Rh, Pd, Ag, Cd, In, Sn, Sb, Te, I, Xe, Cs, Ba, La, Ce, Pr, Nd, Pm, Sm, Eu, Gd, Tb, Dy, Ho, Er, Tm, Yb, Lu, Hf, Ta, W, Re, Os, Ir, Pt, Au, Hg, Tl, Pb, Bi, Po, At, Rn, Fr, Ra, Ac, Th, Pa, U]
>>> 
>>> # The periodic table is a dictionary holding the chemical properties of each element.
... pt = chem.get_periodic_table(abundance_sum=10**6)
>>> print('Hydrogen:', pt['H'])
Hydrogen: {'string': '22', 'abundance': 91790.383216, 'decay': [H]}
>>> print('Thulium:', pt['Tm'])
Thulium: {'string': '11131221133112', 'abundance': 1204.9083841, 'decay': [Er, Ca, Co]}
>>> 
>>> # Conway's constant can be found as the dominant (i.e. maximal real) eigenvalue of the decay matrix:
... chem.get_dom_eigenvalue()
1.3035772690342982
>>> # Conway's constant is root of the degree 71 factor of the characteristic polynomial:
... chem.get_char_poly()
lambda**18*(lambda - 1)**2*(lambda + 1)*(lambda**71 - lambda**69 - 2*lambda**68 - lambda**67 + 2*lambda**66 + 2*lambda**65 + lambda**64 - lambda**63 - lambda**62 - lambda**61 - lambda**60 - lambda**59 + 2*lambda**58 + 5*lambda**57 + 3*lambda**56 - 2*lambda**55 - 10*lambda**54 - 3*lambda**53 - 2*lambda**52 + 6*lambda**51 + 6*lambda**50 + lambda**49 + 9*lambda**48 - 3*lambda**47 - 7*lambda**46 - 8*lambda**45 - 8*lambda**44 + 10*lambda**43 + 6*lambda**42 + 8*lambda**41 - 5*lambda**40 - 12*lambda**39 + 7*lambda**38 - 7*lambda**37 + 7*lambda**36 + lambda**35 - 3*lambda**34 + 10*lambda**33 + lambda**32 - 6*lambda**31 - 2*lambda**30 - 10*lambda**29 - 3*lambda**28 + 2*lambda**27 + 9*lambda**26 - 3*lambda**25 + 14*lambda**24 - 8*lambda**23 - 7*lambda**21 + 9*lambda**20 + 3*lambda**19 - 4*lambda**18 - 10*lambda**17 - 7*lambda**16 + 12*lambda**15 + 7*lambda**14 + 2*lambda**13 - 12*lambda**12 - 4*lambda**11 - 2*lambda**10 + 5*lambda**9 + lambda**7 - 7*lambda**6 + 7*lambda**5 - 4*lambda**4 + 12*lambda**3 - 6*lambda**2 + 3*lambda - 6)

Gray code

The following session shows how to use the module to explore a nonstandard look and say sequence. We use the binary number system known as Gray code to generate the sequence. The corresponding LookAndSay object depends on the say function which converts a positive integer into its Gray code.

>>> from look_and_say import *
>>>
>>> # Define the "say function"
... def gray(num):
...     '''Returns the binary Gray code of an integer from 1 to 7.'''
...     assert num < 8, "This say function can only count to 7."
...     gray_code = {1:'1', 2:'11', 3:'10', 4:'110', 5:'111', 6:'101', 7:'100'}
...     return gray_code[num]
... 
>>> # Create the LookAndSay object and generate a look and say sequence
... gray_ls = LookAndSay(gray)
>>> gray_ls.generate_sequence(seed='0', terms=6)
>>> gray_ls.get_sequence()
['0', '10', '1110', '10110', '111011110', '10110110110']
>>> 
>>> # Use a BinaryChemistry object to determine the chemical properties
... gray_chem = BinaryChemistry(gray_ls)
>>> gray_chem.generate_elements('0')
>>> gray_chem.print_periodic_table()
element   string   abundance    decay
E1        10       0.0          [E3]
E2        110      58.5786438   [E4]
E3        1110     0.0          [E1, E2]
E4        11110    41.4213562   [E2, E2]
>>> 
>>> # The dominant eigenvalue of the decay matrix gives the long term
... # growth rate of look and say sequences.
... gray_chem.get_dom_eigenvalue()
1.4142135623730958
>>> 
>>> # The growth rate is the maximal real root of the characteristic polynomial
... gray_chem.get_char_poly()
(lambda - 1)*(lambda + 1)*(lambda**2 - 2)

Standard ternary

The following session illustrates how to use the module to explore look and say sequences using the standard ternary number system (i.e. using base 3 with digits 0, 1, and 2). The results are similar to those discussed here.

To construct the corresponding LookAndSay object requires a say function which, in this case, is a function that converts an integer to it's standard ternary representation. The construction of the Chemistry object requires both the LookAndSay object as well as a splitting function which determines when the terms of the look and say sequences split. In this case, one can show that the terms will always split after a run of 0's as well as between a 2 (on the left) and either a 10 or a 1110 (on the right). We use a SplitFuncFactory object to create the splitting function.

>>> from look_and_say import *
>>> 
>>> # Define a "say function"
... def ternary(num):
...     '''Returns the ternary representation of a nonnegative integer'''
...     if num < 3:
...         return str(num)
...     return ternary(num // 3) + str(num % 3)
...  
>>> # Use the Split Function Factory to create a split function:
... sff = SplitFuncFactory()
>>> sff.declare_split_after('0')
>>> sff.declare_splitting_pairs(('2', '1110'), ('2', '10'))
>>> split = sff.get_split()
>>> 
>>> # Instantiate the LookAndSay and Chemistry objects:
... ternary_ls = LookAndSay(ternary)
>>> ternary_chem = Chemistry(ternary_ls, split)
>>> 
>>> # Generate elements and order them according to relative abundances:
... ternary_chem.generate_elements('0', '1', '2')
>>> ternary_chem.order_elements('abundance')
>>> 
>>> # Print chemical properties:
... ternary_chem.print_periodic_table()
element   string     abundance    decay
E1        10         18.5037375   [E4]
E2        22110      13.9680582   [E5]
E3        2110       13.9680582   [E6]
E4        1110       13.9680582   [E1, E7]
E5        222110     10.5441752   [E1, E2]
E6        122110     10.5441752   [E8]
E7        110        10.5441752   [E3]
E8        11222110   7.9595623    [E3, E2]
E9        222112     0.0          [E1, E10]
E10       22112      0.0          [E9]
E11       212221     0.0          [E16, E4, E13]
E12       2112       0.0          [E14]
E13       211        0.0          [E15]
E14       122112     0.0          [E17]
E15       1221       0.0          [E18]
E16       12         0.0          [E20]
E17       11222112   0.0          [E3, E10]
E18       112211     0.0          [E11]
E19       112        0.0          [E12]
E20       1112       0.0          [E1, E19]
>>>
>>> # Show the dominant eigenvalue which gives the generic growth rate of the look and say sequences.
... print(ternary_chem.get_dom_eigenvalue())
1.3247179572447458
>>> 
>>> # The characteristic polynomial formatted in latex:
... print(ternary_chem.get_char_poly(latex=True))
\lambda^{6} \left(\lambda - 1\right)^{2} \left(\lambda + 1\right)^{2} \left(\lambda^{2} + 1\right) \left(\lambda^{3} - \lambda - 1\right) \left(\lambda^{5} - \lambda^{3} + 1\right)

Look and say sequences

The fundamental class in this module, LookAndSay, is responsible for holding the logic to create look and say sequences. For example, the following shows how to create a standard decimal look and say sequence with 30 terms starting with the seed 555.

>>> from look_and_say import *
>>> 
>>> # The default LookAndSay object uses the standard decimal number system:
... decimal = LookAndSay()
>>> 
>>> # Generate a look and say sequence
>>> decimal.generate_sequence(seed='555', terms=30)
>>> decimal.get_sequence()
['555', '35', '1315', '11131115', '31133115', '1321232115', '111312111213122115', '311311123112111311222115', '1321133112132112311321322115', '111312212321121113122112132113121113222115', '31131122111213122112311311222112111312211311123113322115', '13211321223112111311222112132113213221123113112221133112132123222115', '11131221131211221321123113213221121113122113121113222112132113213221232112111312111213322115', '311311222113111221221113122112132113121113222112311311222113111231133221121113122113121113221112131221123113111231121123222115', '1321132132211331221122311311222112111312211311123113322112132113213221133112132123222112311311222113111231132231121113112221121321133112132112211213322115', '111312211312111322212311222122132113213221123113112221133112132123222112111312211312111322212321121113121112133221121321132132211331121321132213211231132132211211131221232112111312212221121123222115', '311311222113111231133211121321321122111312211312111322211213211321322123211211131211121332211231131122211311123113321112131221123113111231121123222112111312211312111322212321121113122113221113122112132113121113222112311311221112131221123113112211322112211213322115', '13211321322113311213212312311211131211131221223113112221131112311332211211131221131211132211121312211231131112311211232221121321132132211331121321231231121113112221121321133112132112211213322112311311222113111231133211121312211231131122211322311311222112111312211311123113322112132113212231121113112221121321132122211322212221121123222115', '111312211312111322212321121113121112131112132112311311123113112211221321132132211331121321232221123113112221131112311322311211131122211213211331121321122112133221121113122113121113222123211211131211121311121321123113213221121113122123211211131221222112112322211213211321322113311213212312311211131122211213211321322113221321132132211231131122211331121321232221121113122113121122132112311321322112111312211312113221133211322112211213322115', '3113112221131112311332111213122112311311123112111331121113122112132113311213211321222122111312211312111322212321121113121112133221121321132132211331121321132213211231132132211211131221232112111312212221121123222112311311222113111231133211121312211231131112311211133112111312211213211312111322211231131122111213122112311311221132211221121332211211131221131211132221232112111312111213111213211231132132211211131221131211132221132211131221131211132221121321132132212321121113121112133221123113112221131112212211131221121321131211132221123113112221131112211322212312211322212221121123222115', '132113213221133112132123123112111311222112132113311213211231232112311311222112111312212321121113122113121132112231131122211311123113321112131221123113111231121123222112111312211312111322212321121113122113221113122112132113121113222112311311221112131221123113112211322112211213322112132113213221133112132123123112111311222112132113311213211231232112311311222112111312211311123113322112132113212231121113112221121321132122211322212221121123222112311311222113111231133211121312211231131112311211133112111312211213211312111322211231131122211311123113322113223113112221131112311332211211131221131211132211121312211231131112311211232221121321132132211331221122311311222112111312211311123113322112132113213221133122211332111213112221133211322112211213322115', '1113122113121113222123211211131211121311121321123113213221121113122123211211131221121311121312211213211321322112311311221112131221123113112221131112211312212213211321322113311213212312311211131122211213211331121321122112133221123113112221131112311332111213122112311311222113223113112221121113122113111231133221121321132122311211131122211213211321222113222122211211232221121113122113121113222123211211131211121311121321123113213221121113122123211211131221121311121312211213211321322112311311222113311213212322211211131221131211221321123113213221121113122113121132211332113221122112133221121321132132211331121321231231121113112221121321133112132112312321123113112221121113122113111231133221121321132132211331121321232221132213211321322113311213212322211231131122211311123113223112111311222112132113311213211221121332211211131221131211132221231122212213211321322112311311222113311213212322211211131221131211132221231132212312311211132132212312211322212221121123222115', '3113112221131112311332111213122112311311123112111331121113122112132113121113222112311311221112131221123113112221121113311211131122211211131221131211132221121321132122311211131122211213211321322113312221131122112211131221131211132221232112111312111213111213211231132132211211131221232112111312212221121123222112132113213221133112132123123112111311222112132113213221132213211321322112311311222113311213212322211211131221131211221321123113213221121113122113121132211332113221122112133221123113112221131112311332111213122112311311123112111331121113122112132113121113222112311311221112131221123113112221121113311211131122211211131221131211132221121321132132212321121113121112133221123113112221131112212211131221121321131211132221123113112221131112211322212312211322212221121123222112111312211312111322212321121113121112131112132112311321322112111312212321121113122112131112131221121321132132211231131122211331121321232221121113122113121113222123211211131211121332211322111312211312111322212321121113121112133221121321132132211331121321132213211231132132211211131221232112111312212221121123222112311311222113111231133211121321321122111312211312111322211213211321322123211211131211121332211231131122211311123113321112132113221112131112132112311312111322111213112221133211322112211213322115', '132113213221133112132123123112111311222112132113311213211231232112311311222112111312211311123113322112132113212231121113112221121321132132211231232112311321322112311311222113111231133221121113122113121122132112311321322112111312211312111322212311322113212221223113112221131112311332111213122112311311123112111331121113122112132113121113222112311311221112131221123113112211322112211213322112111312211312111322212321121113121112131112132112311321322112111312211312111322211322111312211312111322211213211321322123211211131211121332211231131122211311122122111312211213211312111322211231131122211311122113222123122113222122211211232221121321132132211331121321231231121113112221121321133112132112312321123113112221121113122113111231133221121321132122311211131122211213211321322112312321123113213221123113112221131112311332211211131221131211132211121312211231131112311211232221121321132132211331221122311311222112111312211311123113322112132113213221133122211332111213112221133211322112211213322112311311222113111231133211121312211231131112311211133112111312211213211312111322211231131122111213122112311311222112111331121113112221121113122113121113222112132113213221232112111312111213322112311311222113111231133211121312211231131112311211232221132231131122211311123113321112131221123113111231121123222112111312211312111322212321121113122113221113122112132113121113222112311311221112131221123113112211322112211213322112132113213221133112132123123112111312111312212231131122211311123113322112111312211312111322111213122112311311123112112322211213211321322113311213212312311211131221132231121113311211131221121321131112311322311211132132212312211322212221121123222115', '1113122113121113222123211211131211121311121321123113213221121113122123211211131221121311121312211213211321322112311311222113311213212322211211131221131211221321123113213221121113122113121113222112131112131221121321131211132221121321132132211331121321232221123113112221131112212211131221121321131211132221123113112221131112311332111213211322211312113211221321132132211331121321231231121113112221121321133112132112312321123113112221121113122113111231133221121321132122311211131122211213211321222113222122211211232221123113112221131112311332111213122112311311123112111331121113122112132113121113222112311311222113111231133221132231131122211311123113322112111312211312111322111213122112311311123112112322211213211321322113312211223113112221121113122113111231133221121321132132211331222113321112131122211332113221122112133221121113122113121113222123211211131211121311121321123113213221121113122123211211131221121311121312211213211321322112311311222113311213212322211211131221131211221321123113213221121113122113121113222112131112131221121321131211132221121321132132211331121321232221123113112221131112311322311211131122211213211331121321122112133221121113122113121113222123112221221321132132211231131122211331121321232221121113122113121113222123113221231231121113213221231221132221222112112322211213211321322113311213212312311211131122211213211331121321123123211231131122211211131221131112311332211213211321223112111311222112132113213221123123211231132132211231131122211311123113322112111312211312111322111213122112311311123112112322211213211321322113311213212312311211131122211213211331121321122112133221132213211321322113311213212312311211131122211213211331121321122112133221123113112221131112311332111213122112311311222113223113112221121113122113111231133221121321132122311211131122211213211321222113222122211211232221121113122113121113222123211211131211121311121321123113111231131122112213211321322113311213212322211231131122211311123113223112111311222112132113311213211221121332211211131221131211132221232112111312111213111213211231131122211322132112312321123113112221121113122113311213211322132112311312111322111213112221133211322112211213322115', '3113112221131112311332111213122112311311123112111331121113122112132113121113222112311311221112131221123113112221121113311211131122211211131221131211132221121321132132212321121113121112133221123113112221131112212211131221121321131211132221123113112221131112311332211211133112111311222112111312211311123113322112111312211312111322212321121113121112133221121321132132211331221122311311222112111312211311123113322112132113213221133112132123123112111312211332211311122113122122111312211312111322212321121113121112131112132112311321322112111312212321121113122112131112131221121321132132211231131122211331121321232221121113122113121122132112311321322112111312211312113221133211322112211213322112132113213221133112132123123112111311222112132113311213211231232112311311222112111312211311123113322112132113213221133112132123222113221321132132211331121321232221123113112221131112311322311211131122211213211331121321122112133221121113122113121113222123112221221321132132211231131122211331121321232221121113122113121113222123113221231231121113213221231221132221222112112322211231131122211311123113321112131221123113111231121113311211131221121321131211132221123113112211121312211231131122211211133112111311222112111312211312111322211213211321322123211211131211121332211231131122211311122122111312211213211312111322211231131122211311123113322112111331121113112221121113122113111231133221121113122113121113222123211211131211121332211213211321322113311213211322132112311321322112111312212321121113122122211211232221123113112221131112311332111213213211221113122113121113222112132113213221232112111312111213322112311311222113111231133211121321132211121311121321123113121113221112131122211332113221122112133221121113122113121113222123211211131211121311121321123113213221121113122123211211131221121311121312211213211321322112311311222113311213212322211211131221131211221321123113213221121113122113121113222112131112131221121321131211132221121321132132211331121321232221123113112221131112311322311211131122211213211331121321122112133221121113122113121113222123211211131211121311121321123113213221121113122123211211131221222112112322211322111312211312111322212321121113121112131112132112311321322112111312212321121113122122211211232221121321132132211331121321231231121113112221121321132132211322132113213221123113112221133112132123222112111312211312112213211231132132211211131221131211322113321132211221121332211231131122211311123113321112131221123113111231121113311211131221121321133112132113212221221113122113121113222123211211131211121332211213211321322113311213211322132112311321322112111312212321121113122122211211232221123113112221131112311332111213122112311311123112111331121113122112132113213221132211131221121311121312211213211321322112311311222123211211131221132211131221121321131112311322311211132132212312211322212221121123222115', '1321132132211331121321231231121113112221121321133112132112312321123113112221121113122113111231133221121321132122311211131122211213211321322112312321123113213221123113112221131112311332211211131221131211132211121312211231131112311211232221121321132132211331221122311311222112111312211311123113322112132113213221133112132123222112312321123113213221123113112221133112132123222112311311222113111231133211121312211231131112311211232221121113122113121113222123112221221321132132211231131122211331121321232221121113122113121113222123211211131211121311121321123113112221232221133122211311221122311311222113111231133211121312211231131112311211133112111312211213211312111322211231131122111213122112311311222112111331121113112221121113122113121113222112132113213221232112111312111213322112311311222113111221221113122112132113121113222112311311222113111221132221231221132221222112112322211211131221131211132221232112111312111213111213211231132132211211131221232112111312211213111213122112132113213221123113112221133112132123222112111312211312111322212321121113121112133221132211131221131211132221232112111312111213322112132113213221133112132113221321123113213221121113122123211211131221222112112322211231131122211311123113321112132132112211131221131211132221121321132132212321121113121112133221123113112221131112311332111213211322111213111213211231131211132211121311222113321132211221121332211213211321322113311213212312311211131122211213211331121321123123211231131122211211131221131112311332211213211321223112111311222112132113213221123123211231132132211231131122211311123113322112111312211312111322111213122112311311123112112322211213211321322113312211223113112221121113122113111231133221121321132132211331121321232221123123211231132132211231131122211331121321232221123113112221131112311332111213122112311311123112112322211211131221131211132221232112111312211322111312211213211312111322211231131122111213122112311311221132211221121332211213211321322113311213212312311211131211131221223113112221131112311332211211131221131211132211121312211231131112311211232221121321132132211331121321231231121113122113223112111331121113122112132113111231132231121113213221231221132221222112112322211231131122211311123113321112131221123113111231121113311211131221121321131211132221123113112211121312211231131122211211133112111311222112111312211312111322211213211321322123211211131211121332211231131122211311122122111312211213211312111322211231131122211311123113322112111331121113112221121113122113111231133221121113122113121113222123211211131211121332211213211321322113311213211322132112311321322112111312212321121113122122211211232221123113112221131112311332111213122112311311123112111331121113122112132113121113222112311311221112131221123113112211322112211213322113223113112221131112311332111213122112311311123112111331121113122112132113121113222112311311221112131221123113112211322112211213322112111312211312111322212321121113121112131112132112311321322112111312211312111322211322111312211312111322211213211321322123211211131211121332211231131122211311122122111312211213211312111322211231131122211311122113222123122113222122211211232221121321132132211331121321231231121113112221121321133112132112312321123113112221121113122123211211131221131211321122311311222113111231133211121312211231131112311211232221121113122113121113222123211211131221132211131221121321131211132221123113112211121312211231131122113221122112133221121321132132211331121321231231121113112221121321133112132112312321123113112221121113122113121113222113223113112221121113311211131122211211131221131211132221121321132132111213122112311311222113223113112221121113122113311213211322132112311312111322111213112221133211322112211213322115', '111312211312111322212321121113121112131112132112311321322112111312212321121113122112131112131221121321132132211231131122211331121321232221121113122113121122132112311321322112111312211312111322211213111213122112132113121113222112132113213221133112132123222112311311222113111231132231121113112221121321133112132112211213322112111312211312111322212311222122132113213221123113112221133112132123222112111312211312111322212321121113121112133221121311121312211213211312111322211213211321322123211211131211121332211213211321322113311213212312311211131122211213211331121321122112133221123113112221131112311332111213213211221113122113121113222112132113213221232112111312111213322112311311222113111231133211121312211231131112311211133112111312211213211321321112133221231132211321222122132113213221133112132123123112111311222112132113311213211231232112311311222112111312211311123113322112132113212231121113112221121321132132211231232112311321322112311311222113111231133221121113122113121113221112131221123113111231121123222112132113213221133122112231131122211211131221131112311332211213211321322113312221133211121311222113321132211221121332211231131122211311123113321112131221123113111231121113311211131221121321131211132221123113112211121312211231131122211211133112111311222112111312211312111322211213211321322123211211131211121332211231131122211311123113321112131221123113111231121123222113223113112221131112311332111213122112311311123112112322211211131221131211132221232112111312211322111312211213211312111322211231131122111213122112311311221132211221121332211213211321322113311213212312311211131211131221223113112221131112311332211211131221131211132211121312211231131112311211232221121321132132211331121321231231121113122113223112111331121113122112132113111231132231121113213221231221132221222112112322211211131221131211132221232112111312111213111213211231132132211211131221232112111312211213111213122112132113213221123113112221133112132123222112111312211312112213211231132132211211131221131211132221121311121312211213211312111322211213211321322113311213212322211231131122211311123113223112111311222112132113311213211221121332211211131221131211132221231122212213211321322112311311222113311213212322211211131221131211132221232112111312111213322112131112131221121321131211132221121321132132212321121113121112133221121321132132211331121321231231121113112221121321133112132112211213322112311311222113111231133211121312211231131122211322311311222112111312211311123113322112132113212231121113112221121321132122211322212221121123222112111312211312111322212321121113121112131112132112311311123113112211221321132132211331121321232221123113112221131112311322311211131122211213211331121321122112133221121113122113121113222123211211131211121311121321123113112221132213211231232112311311222112111312211331121321132213211231131211132211121311222113321132211221121332211213211321322113311213212312311211131122211213211331121321123123211231131122211211131221131112311332211213211321223112111311222112132113213221123123211231132132211231131122211311123113322112111312211312111322111213122112311311123112112322211213211321322113312211223113112221121113122113111231133221121321132132211331121321232221123123211231132132211231131122211331121321232221123113112221131112311332111213122112311311123112112322211211131221131211132221232112111312211322111312211213211312111322211231131122111213122112311311221132211221121332211213211321322113311213212312311211131122211213211331121321123123211231131122211211131221131112311332211213211321223112111311222112132113212221132221222112112322211322132113213221133112132123123112111311222112132113311213211231232112311311222112111312211311123113322112132113212231121113112221121321132122211322212221121123222112311311222113111231133211121312211231131112311211133112111312211213211312111322211231131122211311123113322113223113112221131112311332211211131221131211132211121312211231131112311211232221121321132132211331221122311311222112111312211311123113322112132113213221133122211332111213112221133211322112211213322112111312211312111322212321121113121112131112132112311321322112111312212321121113122112131112131221121321132132211231131122111213122112311311222113111221131221221321132132211331121321231231121113112221121321133112132112211213322112311311222113111231133211121312211231131122211322311311222112111312211311123113322112132113212231121113112221121321132122211322212221121123222112111312211312111322212321121113121112131112132112311321322112111312212321121113122112131112131221121321132132211231131122211311123113322113221321132132211231232112311321322112311311222113111231133221121113122113121113123112111311222112132113213221132213211321322112311311222123211211131221132211131221121321131112311322311211132132212312211322212221121123222115', '311311222113111231133211121312211231131112311211133112111312211213211312111322211231131122111213122112311311222112111331121113112221121113122113121113222112132113213221232112111312111213322112311311222113111221221113122112132113121113222112311311222113111231133221121113311211131122211211131221131112311332211211131221131211132221232112111312111213322112132113213221133112132113221321123113213221121113122123211211131221222112112322211231131122211311123113321112132132112211131221131211132221121321132132212321121113121112133221123113112221131112311332111213122112311311123112112322211211133112111311222112111312211311123113322112111312211312111322111213122112311311123112112322211211131221131211132221232112111312111213111213211231132132211211131221232112111312212221121123222112132113213221133112132123123112111312111312212231131122211311123113322112111312211312111322111213122112311311123112112322211213211321322113311213212312311211131122211213211331121321123123211231131122211211131221131211131231121123221112132113222113121132112211131221131211132221232112111312111213111213211231132132211211131221232112111312211213111213122112132113213221123113112221133112132123222112111312211312112213211231132132211211131221131211132221121311121312211213211312111322211213211321322113311213212322211231131122211311123113223112111311222112132113311213211221121332211211131221131211132221231122212213211321322112311311222113311213212322211211131221131211132221231132212312311211132132212312211322212221121123222112132113213221133112132123123112111311222112132113311213211231232112311311222112111312211311123113322112132113212231121113112221121321132132211231232112311321322112311311222113111231133221121113122113121113221112131221123113111231121123222112132113213221133112132123123112111311222112132113311213211221121332211322132113213221133112132123123112111311222112132113311213211221121332211231131122211311123113321112131221123113112221132231131122211211131221131112311332211213211321223112111311222112132113212221132221222112112322211211131221131211132221232112111312111213111213211231131112311311221122132113213221133112132123222112311311222113111231132231121113112221121321133112132112211213322112111312211312111322212321121113121112131112132112311311222113221321123123211231131122211211131221133112132113221321123113121113221112131122211332113221122112133221123113112221131112311332111213122112311311123112111331121113122112132113121113222112311311221112131221123113112221121113311211131122211211131221131211132221121321132132212321121113121112133221123113112221131112212211131221121321131211132221123113112221131112311332211211133112111311222112111312211311123113322112111312211312111322212321121113121112133221121321132132211331121321132213211231132132211211131221232112111312212221121123222112311311222113111231133211121321321122111312211312111322211213211321322123211211131211121332211231131122211311123113321112131221123113111231121123222112111331121113112221121113122113111231133221121113122113121113221112131221123113111231121123222112111312211312111322212321121113121112131112132112311321322112111312212321121113122122211211232221121321132132211331121321231231121113112221121321132132211322132113213221123113112221133112132123222112111312211312112213211231132132211211131221131211322113321132211221121332211231131122211311123113321112131221123113111231121113311211131221121321133112132113212221221113122113121113222123211211131211121332211213211321322113311213211322132112311321322112111312212321121113122122211211232221123113112221131112311332111213122112311311123112111331121113122112132113213221132211131221121311121312211213211321322112311311222123211211131221132211131221121321131112311322311211132132212312211322212221121123222112111312211312111322212321121113121112131112132112311321322112111312212321121113122112131112131221121321132132211231131122211331121321232221121113122113121122132112311321322112111312211312111322211213111213122112132113121113222112132113213221133112132123222112311311222113111231132231121113112221121321133112132112211213322112111312211312111322212311222122132113213221123113112221133112132123222112111312211312111322212321121113121112133221121311121312211213211312111322211213211321322123211211131211121332211213211321322113311213212312311211131122211213211331121321122112133221123113112221131112311332111213122112311311222113223113112221121113122113111231133221121321132122311211131122211213211321222113222122211211232221121113122113121113222123211211131211121311121321123113213221121113122123211211131221121311121312211213211321322112311311222113311213212322211211131221131211221321123113213221121113122113121132211332113221122112133221132211131221131211132221232112111312111213111213211231132132211211131221232112111312211213111213122112132113213221123113112221133112132123222112111312211312112213211231132132211211131221131211322113321132211221121332211213211321322113311213212312311211131122211213211331121321123123211231131122211211131221131112311332211213211321322113311213212322211322132113213221133112132123222112311311222113111231132231121113112221121321133112132112211213322112111312211312111322212311222122132113213221123113112221133112132123222112111312211312111322212311322123123112111321322123122113222122211211232221123113112221131112311332111213122112311311123112111331121113122112132113121113222112311311221112131221123113112221121113311211131122211211131221131211132221121321132122311211131122211213211321322113312221131122112211131221131211132221232112111312111213111213211231132132211211131221232112111312212221121123222112132113213221133112132123123112111311222112132113213221132213211321322112311311222113311213212322211211131221131211221321123113213221121113122113121132211332113221122112133221123113112221131112311332111213122112311311123112111331121113122112132113121113222112311311221112131221123113112221121113311211131122211211131221131211132221121321132132211331121321232221132211131221131211132221121311121312211213211312111322211213211321322113311213212322211231131122211311123113111213211231132132211211131221131211132221132211131221131211132221121321132132111213122112311311222113223113112221121113122113311213211322132112311312111322111213112221133211322112211213322115', '1321132132211331121321231231121113112221121321133112132112312321123113112221121113122113111231133221121321132122311211131122211213211321322112312321123113213221123113112221131112311332211211131221131211132211121312211231131112311211232221121321132132211331221122311311222112111312211311123113322112132113213221133112132123222112312321123113213221123113112221133112132123222112311311222113111231133211121312211231131112311211232221121113122113121113222123211211131221132211131221121321131211132221123113112211121312211231131122113221122112133221121321132132211331121321231231121113121113122122311311222113111231133221121113122113121113221112131221123113111231121123222112132113213221133112132123123112111311222112132113311213211221121332211231232112311321322112311311222113311213212322211231131122211311123113223112111311222112132113311213211221121332211231131122211311123113321112131221123113111231121113311211131221121321131211132221123113112211121312211231131122113221122112133221121113122113121113222123211211131211121311121321123113111231131122112213211321322113311213212322211231131122211311123113223112111311222112132113311213211221121332211211131221131211132221232112111312111213111213211231132132211211131221232112111312211213111213122112132113213221123113112221131112311311121321122112132231121113122113322113111221131221223113112221131112311332111213122112311311123112111331121113122112132113121113222112311311221112131221123113112221121113311211131122211211131221131211132221121321132132212321121113121112133221123113112221131112212211131221121321131211132221123113112221131112311332211211133112111311222112111312211311123113322112111312211312111322212321121113121112133221121321132132211331121321132213211231132132211211131221232112111312212221121123222112311311222113111231133211121321321122111312211312111322211213211321322123211211131211121332211231131122211311123113321112132113221112131112132112311312111322111213112221133211322112211213322112111312211312111322212321121113121112131112132112311321322112111312212321121113122112131112131221121321132132211231131122211331121321232221121113122113121122132112311321322112111312211312111322211213111213122112132113121113222112132113213221133112132123222112311311222113111231132231121113112221121321133112132112211213322112111312211312111322212321121113121112131112132112311321322112111312212321121113122122211211232221132211131221131211132221232112111312111213111213211231132132211211131221232112111312212221121123222112132113213221133112132123123112111311222112132113213221132213211321322112311311222113311213212322211211131221131211221321123113213221121113122113121132211332113221122112133221123113112221131112311332111213122112311311123112111331121113122112132113311213211321222122111312211312111322212321121113121112133221121321132132211331121321132213211231132132211211131221232112111312212221121123222112311311222113111231133211121312211231131112311211133112111312211213211321322113221113122112131112131221121321132132211231131122212321121113122113221113122112132113111231132231121113213221231221132221222112112322211213211321322113311213212312311211131122211213211331121321123123211231131122211211131221131112311332211213211321223112111311222112132113213221123123211231132132211231131122211311123113322112111312211312111322111213122112311311123112112322211213211321322113312211223113112221121113122113111231133221121321132132211331121321232221123123211231132132211231131122211331121321232221123113112221131112311332111213122112311311123112112322211211131221131211132221232112111312211322111312211213211312111322211231131122111213122112311311221132211221121332211213211321322113311213212312311211131211131221223113112221131112311332211211131221131211132211121312211231131112311211232221121321132132211331121321231231121113112221121321133112132112211213322112312321123113213221123113112221133112132123222112311311222113111231132231121113112221121321133112132112211213322112311311222113111231133211121312211231131112311211133112111312211213211312111322211231131122111213122112311311221132211221121332211211131221131211132221232112111312111213111213211231132132211211131221131211132221132211131221131211132221121321132132212321121113121112133221123113112221131112212211131221121321131211132221123113112221131112211322212312211322212221121123222112132113213221133112132123123112111311222112132113311213211231232112311311222112111312212321121113122113121132112231131122211311123113321112131221123113111231121123222112111312211312111322212321121113122113221113122112132113121113222112311311221112131221123113112211322112211213322112132113213221133112132123123112111311222112132113311213211231232112311311222112111312211312111322211322311311222112111331121113112221121113122113121113222112132113213211121312211231131122211322311311222112111312211331121321132213211231131211132211121311222113321132211221121332211231131122211311123113321112131221123113111231121113311211131221121321131211132221123113112211121312211231131122211211133112111311222112111312211312111322211213211321322123211211131211121332211231131122211311122122111312211213211312111322211231131122211311123113322112111331121113112221121113122113111231133221121113122113121113222123211211131211121332211213211321322113311213211322132112311321322112111312212321121113122122211211232221123113112221131112311332111213213211221113122113121113222112132113213221232112111312111213322112311311222113111231133211121312211231131112311211232221121113311211131122211211131221131112311332211211131221131211132211121312211231131112311211232221121113122113121113222123211211131211121311121321123113213221121113122123211211131221222112112322211213211321322113311213212312311211131122211213211321322113221321132132211231131122211331121321232221121113122113121122132112311321322112111312211312113221133211322112211213322112311311222113111231133211121312211231131112311211133112111312211213211312111322211231131122111213122112311311222112111331121113112221121113122113121113222112132113213221232112111312111213322112311311222113111221221113122112132113121113222112311311222113111221132221231221132221222112112322211322311311222113111231133211121312211231131112311211133112111312211213211312111322211231131122111213122112311311222112111331121113112221121113122113121113222112132113213221232112111312111213322112311311222113111221221113122112132113121113222112311311222113111221132221231221132221222112112322211211131221131211132221232112111312111213111213211231132132211211131221232112111312211213111213122112132113213221123113112221133112132123222112111312211312111322212321121113121112133221132211131221131211132221232112111312111213322112132113213221133112132113221321123113213221121113122123211211131221222112112322211231131122211311123113321112132132112211131221131211132221121321132132212321121113121112133221123113112221131112311332111213211322111213111213211231131211132211121311222113321132211221121332211213211321322113311213212312311211131122211213211331121321123123211231131122211211131221131112311332211213211321223112111311222112132113213221123123211231132132211231131122211311123113322112111312211312112213211231132132211211131221131211132221231132211321222122311311222113111231133211121312211231131112311211133112111312211213211312111322211231131122111213122112311311221132211221121332211211131221131211132221232112111312111213111213211231132132211211131221131211132221132211131221131211132221121321132132212321121113121112133221123113112221131112212211131221121321131211132221123113112221131112211322212312211322212221121123222112132113213221133112132123123112111311222112132113311213211231232112311311222112111312211311123113322112132113212231121113112221121321132132211231232112311321322112311311222113111231133221121113122113121113222123211211131211121332211322311311222113111231133221121113311211131122211211131221131112311332211211131221131211132221232112111312111213322112132113213221133112132113311211131221121321131211132221123113112221131112311332211322311311222113111231133221121113122113121113123112111311222112132113213221132213211321322112311311222123211211131221132211131221121321131112311322311211132132212312211322212221121123222115']

We can extract the ratios of lengths of the terms in the sequence or just the last length ratio:

>>> decimal.get_length_ratios()
[0.6666666666666666, 2.0, 2.0, 1.0, 1.25, 1.8, 1.3333333333333333, 1.1666666666666667, 1.5, 1.3333333333333333, 1.2142857142857142, 1.3529411764705883, 1.3695652173913044, 1.2222222222222223, 1.2857142857142858, 1.3333333333333333, 1.2803030303030303, 1.2958579881656804, 1.3378995433789955, 1.2798634812286689, 1.2853333333333334, 1.329875518672199, 1.2917316692667706, 1.2958937198067633, 1.3131407269338304, 1.304471256210078, 1.294341675734494, 1.3127364438839848, 1.3080371437720142]
>>> decimal.get_last_length_ratio()
1.3080371437720142

The fundamental operation in the LookAndSay class used to compute each term in a look and say sequence is the say_what_you_see() method:

>>> decimal.say_what_you_see('2222')
'42'
>>> decimal.say_what_you_see('2334445555')
'12233445'

By default, when we see a run of four 2's, we say '42'. If we alter the way we say what we see, we get into nonstandard look and say sequences.

Setting up nonstandard look and say sequences with a say function

To start exploring with a nonstandard look and say sequence, the first thing to do is define a say function. The say function will determine how the say-what-you-see operation acts on a run of $n$ consecutive digits $d$, written with Conway's exponent notation as $d^n$. The say function can take one or two parameters:

  • If the say function accepts one parameter, the look and say sequence will correspond to the operation $d^n\to say(n)d$.

  • If the say function accepts two parameters, the look and say sequence will correspond to the operation $d^n\to say(n, d)$.

For example, the following say function can be used to create a Roman numeral look and say sequence.

>>> def roman_say(n):
...     '''Returns the Roman numeral for n assuming 0<n<10'''
...     assert n < 10, "This Roman can only count to 9."
...     roman = {1:'I', 2:'II', 3:'III', 4:'IV', 5:'V', 6:'VI', 7:'VII', 8:'VIII', 9:'IX'}
...     return roman[n]

To get a Roman look and say sequence, pass the say function into the constructor of a LookAndSay object:

>>> roman_ls = LookAndSay(roman_say)
>>> 
>>> roman_ls.generate_sequence('I', 11)
>>> roman_ls.get_sequence()
['I', 'II', 'III', 'IIII', 'IVI', 'IIIVII', 'IIIIIVIII', 'VIIVIIII', 'IVIIIIVIVI', 'IIIVIVIIVIIIVII', 'IIIIIVIIIVIIIIVIIIIIVIII']
>>> 
>>> roman_ls.generate_sequence('V', 11)
>>> roman_ls.get_sequence()
['V', 'IV', 'IIIV', 'IIIIIV', 'VIIV', 'IVIIIIV', 'IIIVIVIIV', 'IIIIIVIIIVIIIIV', 'VIIVIIIIIVIVIIV', 'IVIIIIVVIIVIIIVIIIIV', 'IIIVIVIIIVIIIIVIIIIIVIVIIV']

Standard binary

Here is a standard binary look and say:

>>> def binary_say(num):
...     return "{0:b}".format(num)
... 
>>> binary_ls = LookAndSay(binary_say)
>>> 
>>> binary_ls.generate_sequence('0', 10)
>>> binary_ls.get_sequence()
['0', '10', '1110', '11110', '100110', '1110010110', '111100111010110', '100110011110111010110', '1110010110010011011110111010110', '1111001110101100111001011010011011110111010110']
>>> 
>>> binary_ls.generate_sequence('1', 10)
>>> binary_ls.get_sequence()
['1', '11', '101', '111011', '11110101', '100110111011', '111001011011110101', '111100111010110100110111011', '100110011110111010110111001011011110101', '1110010110010011011110111010110111100111010110100110111011']

The chemistry of the standard binary look and say sequences is determined below.

Two parameter say functions

Here is a look-and-say-again from the paper Stuttering Conway Sequences Are Still Conway Sequences by Brier et al. The say function for this example corresponds to the decay $d^n\to nndd$.

>>> def say_again(n, d):
...     return 2 * str(n) + 2 * d
... 
>>> look_and_say_again = LookAndSay(say_again)
>>> 
>>> look_and_say_again.generate_sequence('1', 11)
>>> look_and_say_again.get_sequence()
['1', '1111', '4411', '22442211', '2222224422222211', '6622224466222211', '226644222244226644222211', '2222226622444422224422222266224444222211', '662222662222444444222244662222662222444444222211', '22664422226644226644442222442266442222664422664444222211', '2222226622444422226622442222226644444422224422222266224444222266224422222266444444222211']
>>> 
>>> look_and_say_again.generate_sequence('2', 11)
>>> look_and_say_again.get_sequence()
['2', '1122', '22112222', '222222114422', '6622221122442222', '226644222211222222444422', '22222266224444222211662244442222', '6622226622224444442222112266222244444422', '226644222266442266444422221122222266442266442222', '222222662244442222662244222222664444442222116622226622442222226622444422', '66222266222244444422226622222244662222666644442222112266442222662222224466222266222244442222']

Here is Morrill's Look Knave:

>>> def knave_say(bit_count, bit):
...     flip = {'0':'1', '1':'0'}
...     return "{0:b}".format(bit_count) + flip[bit]
... 
>>> look_knave = LookAndSay(knave_say)
>>> 
>>> look_knave.generate_sequence('0', 13)
>>> look_knave.get_sequence()
['0', '11', '100', '10101', '1011101110', '10111101111011', '1011100011100011100', '1011110111110111110101', '1011100011101011101011101110', '10111101111101110111101110111101111011', '10111000111010111101110001111011100011100011100', '1011110111110111011100011110111100011110111110111110101', '1011100011101011110111101111000111000111100011101011101011101110']
>>> 
>>> look_knave.generate_sequence('1', 13)
>>> look_knave.get_sequence()
['1', '10', '1011', '1011100', '1011110101', '1011100011101110', '10111101111101111011', '1011100011101011100011100', '1011110111110111011110111110101', '101110001110101111011100011101011101110', '10111101111101110111000111101111101110111101111011', '10111000111010111101111011110001110101111011100011100011100', '10111101111101110111000111000111000111110111011100011110111110111110101']

Splitting functions

A splitting function for a look and say sequence is a function split(s) which returns a list [s0, s1, ...] of substrings of the string s such that s equals the concatenation s1 + s2 + ... and the say-what-you-see operation applied to s is equal to the concatenation of the say-what-you-see operation applied to each s1, s2, ....

Conway's splitting function

The terms of a standard decimal look and say sequences split into elements according to Conway's Splitting Theorem. Conway's conditions for splitting a string are implemented into the splitting function split_Conway() as illustrated below:

>>> from look_and_say import split_Conway
>>>
>>> split_Conway('1211132213')
['12', '1113', '22', '13']

Split function factories

One can use a SplitFuncFactory object to create a splitting function. Conditions for splitting are specified with the following methods:

method splitting condition
declare_split_after((L,R)) Specify pair(s) of chunks in the form (L, R) such that LR always splits as L.R.
declare_split_after(L) Specify chunk(s) L such that LR splits for every possible R (assuming the last character of L and the first character of R are distinct).
declare_split_before(R) Specify chunk(s) R such that LR splits for every possible L (assuming the last character of L and the first character of R are distinct).

After the splitting conditions are specified, the get_split() method will return the splitting function.

For example, the following shows how to create a splitting function which splits a string at the end of every run of 0's.

>>> sff = SplitFuncFactory()
>>> sff.declare_split_after('0')
>>> split = sff.get_split()
>>> split('1101230022200012301325023')
['110', '12300', '222000', '1230', '13250', '23']

You can specify multiple chunks to split after:

>>> sff = SplitFuncFactory()
>>> sff.declare_split_after('1', '20')
>>> split = sff.get_split()
>>> split('12311223323112011200011110234234')
['1', '2311', '22332311', '20', '11', '20001111', '0234234']

Here is an example specifying strings to split before:

>>> sff = SplitFuncFactory()
>>> sff.declare_split_before('0', '31')
>>> split = sff.get_split()
>>> split('12311223323112011200011110234234')
['12', '31122332', '3112', '0112', '0001111', '0234234']

Here is an example where multiple splitting pairs are declared:

>>> sff = SplitFuncFactory()
>>> sff.declare_splitting_pairs(('311', '223'), ('0', '1'))
>>> split = sff.get_split()
>>> split('12311223323112011200011110234234')
['12311', '2233231120', '112000', '11110234234']

Here is another example, where multiple methods are used to declare the splitting conditions:

>>> sff = SplitFuncFactory()
>>> sff.declare_split_before('111')
>>> sff.declare_split_after('2', '30')
>>> sff.declare_splitting_pairs(('11', '333'))
>>> split = sff.get_split()
>>> split('1234411154211333234530411113333344')
['12', '344', '111542', '11', '3332', '34530', '4', '1111', '3333344']

Chemistry

Generating elements

Given a look and say sequence equipped with a splitting function, a Chemistry object can search for all the elements (i.e. substrings resulting from completely splitting the terms) of the look and say sequence. The default Chemistry object corresponds to standard decimal look and say sequences uses Conway's splitting function. For example, the following session preforms a search for all elements in the decimal look and say sequence (from above) generated by seed 1. The search yields 92 elements, which agrees with Conway's results:

>>> chem = Chemistry(decimal)
>>> chem.generate_elements('1')
>>> elements = chem.get_elements()
>>> len(elements)
92

Element properties and periodic tables

Each Element is equipped with a name, string, and a decay into other elements. The following examples illustrate how to work with elements using Conway's chemistry for standard decimal look and say sequences. First, you can get any particular element using its name. You can extract any of the attributes of an element with the appropriate getters as in the following example:

>>> e = chem.get_element('Rh')
>>> e.get_name()
'Rh'
>>> e.get_string()
'311311222113111221131221'
>>> e.get_decay()
[Ho, Ru]

The collection of all the elements and their properties (including their limiting relative abundance in the look and say sequence) is held in the periodic table. You can get the periodic table as a dictionary:

>>> chem.get_periodic_table()
{'H': {'string': '22', 'abundance': 9.1790383, 'decay': [H]}, 'He': {'string': '13112221133211322112211213322112', 'abundance': 0.3237297, 'decay': [Hf, Pa, H, Ca, Li]}, 'Li': {'string': '312211322212221121123222112', 'abundance': 0.4220067, 'decay': [He]}, 'Be': {'string': '111312211312113221133211322112211213322112', 'abundance': 0.2263886, 'decay': [Ge, Ca, Li]}, 'B': {'string': '1321132122211322212221121123222112', 'abundance': 0.295115, 'decay': [Be]}, 'C': {'string': '3113112211322112211213322112', 'abundance': 0.3847053, 'decay': [B]}, 'N': {'string': '111312212221121123222112', 'abundance': 0.501493, 'decay': [C]}, 'O': {'string': '132112211213322112', 'abundance': 0.6537349, 'decay': [N]}, 'F': {'string': '31121123222112', 'abundance': 0.852194, 'decay': [O]}, 'Ne': {'string': '111213322112', 'abundance': 1.1109007, 'decay': [F]}, 'Na': {'string': '123222112', 'abundance': 1.4481449, 'decay': [Ne]}, 'Mg': {'string': '3113322112', 'abundance': 1.8850441, 'decay': [Pm, Na]}, 'Al': {'string': '1113222112', 'abundance': 2.4573007, 'decay': [Mg]}, 'Si': {'string': '1322112', 'abundance': 3.2032813, 'decay': [Al]}, 'P': {'string': '311311222112', 'abundance': 1.4895887, 'decay': [Ho, Si]}, 'S': {'string': '1113122112', 'abundance': 1.9417939, 'decay': [P]}, 'Cl': {'string': '132112', 'abundance': 2.5312784, 'decay': [S]}, 'Ar': {'string': '3112', 'abundance': 3.299717, 'decay': [Cl]}, 'K': {'string': '1112', 'abundance': 4.3014361, 'decay': [Ar]}, 'Ca': {'string': '12', 'abundance': 5.6072543, 'decay': [K]}, 'Sc': {'string': '3113112221133112', 'abundance': 0.9302097, 'decay': [Ho, Pa, H, Ca, Co]}, 'Ti': {'string': '11131221131112', 'abundance': 1.2126003, 'decay': [Sc]}, 'V': {'string': '13211312', 'abundance': 1.5807182, 'decay': [Ti]}, 'Cr': {'string': '31132', 'abundance': 2.0605883, 'decay': [V]}, 'Mn': {'string': '111311222112', 'abundance': 2.686136, 'decay': [Cr, Si]}, 'Fe': {'string': '13122112', 'abundance': 3.5015859, 'decay': [Mn]}, 'Co': {'string': '32112', 'abundance': 4.5645877, 'decay': [Fe]}, 'Ni': {'string': '11133112', 'abundance': 1.3871124, 'decay': [Zn, Co]}, 'Cu': {'string': '131112', 'abundance': 1.8082082, 'decay': [Ni]}, 'Zn': {'string': '312', 'abundance': 2.3571391, 'decay': [Cu]}, 'Ga': {'string': '13221133122211332', 'abundance': 0.1447891, 'decay': [Eu, Ca, Ac, H, Ca, Zn]}, 'Ge': {'string': '31131122211311122113222', 'abundance': 0.1887437, 'decay': [Ho, Ga]}, 'As': {'string': '11131221131211322113322112', 'abundance': 0.0027246, 'decay': [Ge, Na]}, 'Se': {'string': '13211321222113222112', 'abundance': 0.0035518, 'decay': [As]}, 'Br': {'string': '3113112211322112', 'abundance': 0.00463, 'decay': [Se]}, 'Kr': {'string': '11131221222112', 'abundance': 0.0060355, 'decay': [Br]}, 'Rb': {'string': '1321122112', 'abundance': 0.0078678, 'decay': [Kr]}, 'Sr': {'string': '3112112', 'abundance': 0.0102563, 'decay': [Rb]}, 'Y': {'string': '1112133', 'abundance': 0.0133699, 'decay': [Sr, U]}, 'Zr': {'string': '12322211331222113112211', 'abundance': 0.0174286, 'decay': [Y, H, Ca, Tc]}, 'Nb': {'string': '1113122113322113111221131221', 'abundance': 0.0227196, 'decay': [Er, Zr]}, 'Mo': {'string': '13211322211312113211', 'abundance': 0.0296167, 'decay': [Nb]}, 'Tc': {'string': '311322113212221', 'abundance': 0.0386077, 'decay': [Mo]}, 'Ru': {'string': '132211331222113112211', 'abundance': 0.0328995, 'decay': [Eu, Ca, Tc]}, 'Rh': {'string': '311311222113111221131221', 'abundance': 0.042887, 'decay': [Ho, Ru]}, 'Pd': {'string': '111312211312113211', 'abundance': 0.0559065, 'decay': [Rh]}, 'Ag': {'string': '132113212221', 'abundance': 0.0728785, 'decay': [Pd]}, 'Cd': {'string': '3113112211', 'abundance': 0.0950027, 'decay': [Ag]}, 'In': {'string': '11131221', 'abundance': 0.1238434, 'decay': [Cd]}, 'Sn': {'string': '13211', 'abundance': 0.1614395, 'decay': [In]}, 'Sb': {'string': '3112221', 'abundance': 0.2104488, 'decay': [Pm, Sn]}, 'Te': {'string': '1322113312211', 'abundance': 0.2743363, 'decay': [Eu, Ca, Sb]}, 'I': {'string': '311311222113111221', 'abundance': 0.3576186, 'decay': [Ho, Te]}, 'Xe': {'string': '11131221131211', 'abundance': 0.4661834, 'decay': [I]}, 'Cs': {'string': '13211321', 'abundance': 0.6077061, 'decay': [Xe]}, 'Ba': {'string': '311311', 'abundance': 0.7921919, 'decay': [Cs]}, 'La': {'string': '11131', 'abundance': 1.0326833, 'decay': [Ba]}, 'Ce': {'string': '1321133112', 'abundance': 1.3461825, 'decay': [La, H, Ca, Co]}, 'Pr': {'string': '31131112', 'abundance': 1.7548529, 'decay': [Ce]}, 'Nd': {'string': '111312', 'abundance': 2.2875864, 'decay': [Pr]}, 'Pm': {'string': '132', 'abundance': 2.9820456, 'decay': [Nd]}, 'Sm': {'string': '311332', 'abundance': 1.5408115, 'decay': [Pm, Ca, Zn]}, 'Eu': {'string': '1113222', 'abundance': 2.0085669, 'decay': [Sm]}, 'Gd': {'string': '13221133112', 'abundance': 2.1662973, 'decay': [Eu, Ca, Co]}, 'Tb': {'string': '3113112221131112', 'abundance': 2.8239359, 'decay': [Ho, Gd]}, 'Dy': {'string': '111312211312', 'abundance': 3.6812186, 'decay': [Tb]}, 'Ho': {'string': '1321132', 'abundance': 4.7987529, 'decay': [Dy]}, 'Er': {'string': '311311222', 'abundance': 0.1098596, 'decay': [Ho, Pm]}, 'Tm': {'string': '11131221133112', 'abundance': 0.1204908, 'decay': [Er, Ca, Co]}, 'Yb': {'string': '1321131112', 'abundance': 0.1570691, 'decay': [Tm]}, 'Lu': {'string': '311312', 'abundance': 0.2047517, 'decay': [Yb]}, 'Hf': {'string': '11132', 'abundance': 0.2669097, 'decay': [Lu]}, 'Ta': {'string': '13112221133211322112211213322113', 'abundance': 0.0242077, 'decay': [Hf, Pa, H, Ca, W]}, 'W': {'string': '312211322212221121123222113', 'abundance': 0.0315567, 'decay': [Ta]}, 'Re': {'string': '111312211312113221133211322112211213322113', 'abundance': 0.0169288, 'decay': [Ge, Ca, W]}, 'Os': {'string': '1321132122211322212221121123222113', 'abundance': 0.022068, 'decay': [Re]}, 'Ir': {'string': '3113112211322112211213322113', 'abundance': 0.0287673, 'decay': [Os]}, 'Pt': {'string': '111312212221121123222113', 'abundance': 0.0375005, 'decay': [Ir]}, 'Au': {'string': '132112211213322113', 'abundance': 0.0488847, 'decay': [Pt]}, 'Hg': {'string': '31121123222113', 'abundance': 0.063725, 'decay': [Au]}, 'Tl': {'string': '111213322113', 'abundance': 0.0830705, 'decay': [Hg]}, 'Pb': {'string': '123222113', 'abundance': 0.1082888, 'decay': [Tl]}, 'Bi': {'string': '3113322113', 'abundance': 0.1411629, 'decay': [Pm, Pb]}, 'Po': {'string': '1113222113', 'abundance': 0.1840167, 'decay': [Bi]}, 'At': {'string': '1322113', 'abundance': 0.23988, 'decay': [Po]}, 'Rn': {'string': '311311222113', 'abundance': 0.3127021, 'decay': [Ho, At]}, 'Fr': {'string': '1113122113', 'abundance': 0.4076313, 'decay': [Rn]}, 'Ra': {'string': '132113', 'abundance': 0.5313789, 'decay': [Fr]}, 'Ac': {'string': '3113', 'abundance': 0.6926935, 'decay': [Ra]}, 'Th': {'string': '1113', 'abundance': 0.7581905, 'decay': [Ac]}, 'Pa': {'string': '13', 'abundance': 0.9883599, 'decay': [Th]}, 'U': {'string': '3', 'abundance': 0.0102563, 'decay': [Pa]}}

By default, the relative abundances are given in percentages. You can scale the abundances by setting the total sum of all the abundances. You can also set the decimal place precision of the abundances as in the following:

>>> chem.get_periodic_table(abundance_sum=10**6, dec_places=2)
{'H': {'string': '22', 'abundance': 91790.38, 'decay': [H]}, 'He': {'string': '13112221133211322112211213322112', 'abundance': 3237.3, 'decay': [Hf, Pa, H, Ca, Li]}, 'Li': {'string': '312211322212221121123222112', 'abundance': 4220.07, 'decay': [He]}, 'Be': {'string': '111312211312113221133211322112211213322112', 'abundance': 2263.89, 'decay': [Ge, Ca, Li]}, 'B': {'string': '1321132122211322212221121123222112', 'abundance': 2951.15, 'decay': [Be]}, 'C': {'string': '3113112211322112211213322112', 'abundance': 3847.05, 'decay': [B]}, 'N': {'string': '111312212221121123222112', 'abundance': 5014.93, 'decay': [C]}, 'O': {'string': '132112211213322112', 'abundance': 6537.35, 'decay': [N]}, 'F': {'string': '31121123222112', 'abundance': 8521.94, 'decay': [O]}, 'Ne': {'string': '111213322112', 'abundance': 11109.01, 'decay': [F]}, 'Na': {'string': '123222112', 'abundance': 14481.45, 'decay': [Ne]}, 'Mg': {'string': '3113322112', 'abundance': 18850.44, 'decay': [Pm, Na]}, 'Al': {'string': '1113222112', 'abundance': 24573.01, 'decay': [Mg]}, 'Si': {'string': '1322112', 'abundance': 32032.81, 'decay': [Al]}, 'P': {'string': '311311222112', 'abundance': 14895.89, 'decay': [Ho, Si]}, 'S': {'string': '1113122112', 'abundance': 19417.94, 'decay': [P]}, 'Cl': {'string': '132112', 'abundance': 25312.78, 'decay': [S]}, 'Ar': {'string': '3112', 'abundance': 32997.17, 'decay': [Cl]}, 'K': {'string': '1112', 'abundance': 43014.36, 'decay': [Ar]}, 'Ca': {'string': '12', 'abundance': 56072.54, 'decay': [K]}, 'Sc': {'string': '3113112221133112', 'abundance': 9302.1, 'decay': [Ho, Pa, H, Ca, Co]}, 'Ti': {'string': '11131221131112', 'abundance': 12126.0, 'decay': [Sc]}, 'V': {'string': '13211312', 'abundance': 15807.18, 'decay': [Ti]}, 'Cr': {'string': '31132', 'abundance': 20605.88, 'decay': [V]}, 'Mn': {'string': '111311222112', 'abundance': 26861.36, 'decay': [Cr, Si]}, 'Fe': {'string': '13122112', 'abundance': 35015.86, 'decay': [Mn]}, 'Co': {'string': '32112', 'abundance': 45645.88, 'decay': [Fe]}, 'Ni': {'string': '11133112', 'abundance': 13871.12, 'decay': [Zn, Co]}, 'Cu': {'string': '131112', 'abundance': 18082.08, 'decay': [Ni]}, 'Zn': {'string': '312', 'abundance': 23571.39, 'decay': [Cu]}, 'Ga': {'string': '13221133122211332', 'abundance': 1447.89, 'decay': [Eu, Ca, Ac, H, Ca, Zn]}, 'Ge': {'string': '31131122211311122113222', 'abundance': 1887.44, 'decay': [Ho, Ga]}, 'As': {'string': '11131221131211322113322112', 'abundance': 27.25, 'decay': [Ge, Na]}, 'Se': {'string': '13211321222113222112', 'abundance': 35.52, 'decay': [As]}, 'Br': {'string': '3113112211322112', 'abundance': 46.3, 'decay': [Se]}, 'Kr': {'string': '11131221222112', 'abundance': 60.36, 'decay': [Br]}, 'Rb': {'string': '1321122112', 'abundance': 78.68, 'decay': [Kr]}, 'Sr': {'string': '3112112', 'abundance': 102.56, 'decay': [Rb]}, 'Y': {'string': '1112133', 'abundance': 133.7, 'decay': [Sr, U]}, 'Zr': {'string': '12322211331222113112211', 'abundance': 174.29, 'decay': [Y, H, Ca, Tc]}, 'Nb': {'string': '1113122113322113111221131221', 'abundance': 227.2, 'decay': [Er, Zr]}, 'Mo': {'string': '13211322211312113211', 'abundance': 296.17, 'decay': [Nb]}, 'Tc': {'string': '311322113212221', 'abundance': 386.08, 'decay': [Mo]}, 'Ru': {'string': '132211331222113112211', 'abundance': 328.99, 'decay': [Eu, Ca, Tc]}, 'Rh': {'string': '311311222113111221131221', 'abundance': 428.87, 'decay': [Ho, Ru]}, 'Pd': {'string': '111312211312113211', 'abundance': 559.07, 'decay': [Rh]}, 'Ag': {'string': '132113212221', 'abundance': 728.78, 'decay': [Pd]}, 'Cd': {'string': '3113112211', 'abundance': 950.03, 'decay': [Ag]}, 'In': {'string': '11131221', 'abundance': 1238.43, 'decay': [Cd]}, 'Sn': {'string': '13211', 'abundance': 1614.39, 'decay': [In]}, 'Sb': {'string': '3112221', 'abundance': 2104.49, 'decay': [Pm, Sn]}, 'Te': {'string': '1322113312211', 'abundance': 2743.36, 'decay': [Eu, Ca, Sb]}, 'I': {'string': '311311222113111221', 'abundance': 3576.19, 'decay': [Ho, Te]}, 'Xe': {'string': '11131221131211', 'abundance': 4661.83, 'decay': [I]}, 'Cs': {'string': '13211321', 'abundance': 6077.06, 'decay': [Xe]}, 'Ba': {'string': '311311', 'abundance': 7921.92, 'decay': [Cs]}, 'La': {'string': '11131', 'abundance': 10326.83, 'decay': [Ba]}, 'Ce': {'string': '1321133112', 'abundance': 13461.83, 'decay': [La, H, Ca, Co]}, 'Pr': {'string': '31131112', 'abundance': 17548.53, 'decay': [Ce]}, 'Nd': {'string': '111312', 'abundance': 22875.86, 'decay': [Pr]}, 'Pm': {'string': '132', 'abundance': 29820.46, 'decay': [Nd]}, 'Sm': {'string': '311332', 'abundance': 15408.12, 'decay': [Pm, Ca, Zn]}, 'Eu': {'string': '1113222', 'abundance': 20085.67, 'decay': [Sm]}, 'Gd': {'string': '13221133112', 'abundance': 21662.97, 'decay': [Eu, Ca, Co]}, 'Tb': {'string': '3113112221131112', 'abundance': 28239.36, 'decay': [Ho, Gd]}, 'Dy': {'string': '111312211312', 'abundance': 36812.19, 'decay': [Tb]}, 'Ho': {'string': '1321132', 'abundance': 47987.53, 'decay': [Dy]}, 'Er': {'string': '311311222', 'abundance': 1098.6, 'decay': [Ho, Pm]}, 'Tm': {'string': '11131221133112', 'abundance': 1204.91, 'decay': [Er, Ca, Co]}, 'Yb': {'string': '1321131112', 'abundance': 1570.69, 'decay': [Tm]}, 'Lu': {'string': '311312', 'abundance': 2047.52, 'decay': [Yb]}, 'Hf': {'string': '11132', 'abundance': 2669.1, 'decay': [Lu]}, 'Ta': {'string': '13112221133211322112211213322113', 'abundance': 242.08, 'decay': [Hf, Pa, H, Ca, W]}, 'W': {'string': '312211322212221121123222113', 'abundance': 315.57, 'decay': [Ta]}, 'Re': {'string': '111312211312113221133211322112211213322113', 'abundance': 169.29, 'decay': [Ge, Ca, W]}, 'Os': {'string': '1321132122211322212221121123222113', 'abundance': 220.68, 'decay': [Re]}, 'Ir': {'string': '3113112211322112211213322113', 'abundance': 287.67, 'decay': [Os]}, 'Pt': {'string': '111312212221121123222113', 'abundance': 375.0, 'decay': [Ir]}, 'Au': {'string': '132112211213322113', 'abundance': 488.85, 'decay': [Pt]}, 'Hg': {'string': '31121123222113', 'abundance': 637.25, 'decay': [Au]}, 'Tl': {'string': '111213322113', 'abundance': 830.71, 'decay': [Hg]}, 'Pb': {'string': '123222113', 'abundance': 1082.89, 'decay': [Tl]}, 'Bi': {'string': '3113322113', 'abundance': 1411.63, 'decay': [Pm, Pb]}, 'Po': {'string': '1113222113', 'abundance': 1840.17, 'decay': [Bi]}, 'At': {'string': '1322113', 'abundance': 2398.8, 'decay': [Po]}, 'Rn': {'string': '311311222113', 'abundance': 3127.02, 'decay': [Ho, At]}, 'Fr': {'string': '1113122113', 'abundance': 4076.31, 'decay': [Rn]}, 'Ra': {'string': '132113', 'abundance': 5313.79, 'decay': [Fr]}, 'Ac': {'string': '3113', 'abundance': 6926.94, 'decay': [Ra]}, 'Th': {'string': '1113', 'abundance': 7581.9, 'decay': [Ac]}, 'Pa': {'string': '13', 'abundance': 9883.6, 'decay': [Th]}, 'U': {'string': '3', 'abundance': 102.56, 'decay': [Pa]}}

You can also print the periodic table:

>>> chem.print_periodic_table(abundance_sum=10**6)
element   string                                       abundance       decay
H         22                                           91790.383216    [H]
He        13112221133211322112211213322112             3237.2968587    [Hf, Pa, H, Ca, Li]
Li        312211322212221121123222112                  4220.0665982    [He]
Be        111312211312113221133211322112211213322112   2263.8860324    [Ge, Ca, Li]
B         1321132122211322212221121123222112           2951.1503716    [Be]
C         3113112211322112211213322112                 3847.0525419    [B]
N         111312212221121123222112                     5014.9302464    [C]
O         132112211213322112                           6537.349075     [N]
F         31121123222112                               8521.9396539    [O]
Ne        111213322112                                 11109.0068209   [F]
Na        123222112                                    14481.4487733   [Ne]
Mg        3113322112                                   18850.4412275   [Pm, Na]
Al        1113222112                                   24573.0066954   [Mg]
Si        1322112                                      32032.81296     [Al]
P         311311222112                                 14895.8866582   [Ho, Si]
S         1113122112                                   19417.9392497   [P]
Cl        132112                                       25312.7842174   [S]
Ar        3112                                         32997.1701218   [Cl]
K         1112                                         43014.3609132   [Ar]
Ca        12                                           56072.5431285   [K]
Sc        3113112221133112                             9302.0974443    [Ho, Pa, H, Ca, Co]
Ti        11131221131112                               12126.0027828   [Sc]
V         13211312                                     15807.1815919   [Ti]
Cr        31132                                        20605.8826107   [V]
Mn        111311222112                                 26861.3601797   [Cr, Si]
Fe        13122112                                     35015.8585455   [Mn]
Co        32112                                        45645.8772557   [Fe]
Ni        11133112                                     13871.1241997   [Zn, Co]
Cu        131112                                       18082.0822027   [Ni]
Zn        312                                          23571.3913363   [Cu]
Ga        13221133122211332                            1447.8905642    [Eu, Ca, Ac, H, Ca, Zn]
Ge        31131122211311122113222                      1887.4372276    [Ho, Ga]
As        11131221131211322113322112                   27.2462161      [Ge, Na]
Se        13211321222113222112                         35.5175479      [As]
Br        3113112211322112                             46.2998682      [Se]
Kr        11131221222112                               60.3554557      [Br]
Rb        1321122112                                   78.6780001      [Kr]
Sr        3112112                                      102.5628525     [Rb]
Y         1112133                                      133.6986032     [Sr, U]
Zr        12322211331222113112211                      174.28646       [Y, H, Ca, Tc]
Nb        1113122113322113111221131221                 227.1958675     [Er, Zr]
Mo        13211322211312113211                         296.1673685     [Nb]
Tc        311322113212221                              386.0770494     [Mo]
Ru        132211331222113112211                        328.9948058     [Eu, Ca, Tc]
Rh        311311222113111221131221                     428.8701504     [Ho, Ru]
Pd        111312211312113211                           559.0653795     [Rh]
Ag        132113212221                                 728.7849206     [Pd]
Cd        3113112211                                   950.0274565     [Ag]
In        11131221                                     1238.4341972    [Cd]
Sn        13211                                        1614.3946687    [In]
Sb        3112221                                      2104.4881933    [Pm, Sn]
Te        1322113312211                                2743.3629717    [Eu, Ca, Sb]
I         311311222113111221                           3576.1856107    [Ho, Te]
Xe        11131221131211                               4661.8342719    [I]
Cs        13211321                                     6077.0611889    [Xe]
Ba        311311                                       7921.9188284    [Cs]
La        11131                                        10326.8333118   [Ba]
Ce        1321133112                                   13461.8251664   [La, H, Ca, Co]
Pr        31131112                                     17548.5292866   [Ce]
Nd        111312                                       22875.863883    [Pr]
Pm        132                                          29820.4561674   [Nd]
Sm        311332                                       15408.1151815   [Pm, Ca, Zn]
Eu        1113222                                      20085.6687093   [Sm]
Gd        13221133112                                  21662.9728211   [Eu, Ca, Co]
Tb        3113112221131112                             28239.3589492   [Ho, Gd]
Dy        111312211312                                 36812.1864183   [Tb]
Ho        1321132                                      47987.5294384   [Dy]
Er        311311222                                    1098.5955997    [Ho, Pm]
Tm        11131221133112                               1204.9083841    [Er, Ca, Co]
Yb        1321131112                                   1570.6911808    [Tm]
Lu        311312                                       2047.51732      [Yb]
Hf        11132                                        2669.0970363    [Lu]
Ta        13112221133211322112211213322113             242.0773667     [Hf, Pa, H, Ca, W]
W         312211322212221121123222113                  315.5665525     [Ta]
Re        111312211312113221133211322112211213322113   169.2880181     [Ge, Ca, W]
Os        1321132122211322212221121123222113           220.6800123     [Re]
Ir        3113112211322112211213322113                 287.6734477     [Os]
Pt        111312212221121123222113                     375.0045674     [Ir]
Au        132112211213322113                           488.8474298     [Pt]
Hg        31121123222113                               637.2503975     [Au]
Tl        111213322113                                 830.7051329     [Hg]
Pb        123222113                                    1082.8883286    [Tl]
Bi        3113322113                                   1411.62861      [Pm, Pb]
Po        1113222113                                   1840.1669683    [Bi]
At        1322113                                      2398.7998311    [Po]
Rn        311311222113                                 3127.0209328    [Ho, At]
Fr        1113122113                                   4076.3134078    [Rn]
Ra        132113                                       5313.7894999    [Fr]
Ac        3113                                         6926.9352045    [Ra]
Th        1113                                         7581.9047124    [Ac]
Pa        13                                           9883.5986391    [Th]
U         3                                            102.5628525     [Pa]

Binary chemistry

To determine the chemistry of standard binary look and say sequences (see above) we use a BinaryChemistry object:

>>> binary_chem = BinaryChemistry(binary_ls)
>>> binary_chem.generate_elements('1')
>>> binary_chem.print_periodic_table()
element   string   abundance    decay
E1        1        0.0          [E4]
E2        10       21.6756572   [E7]
E3        100      10.0915624   [E8]
E4        11       0.0          [E2, E1]
E5        110      21.6756572   [E2, E5]
E6        1100     10.0915624   [E2, E6]
E7        1110     14.7899036   [E9]
E8        11100    6.8857536    [E10]
E9        11110    10.0915624   [E3, E5]
E10       111100   4.6983411    [E3, E6]

With a BinaryChemistry object the terms of the binary look and say sequence are split so that the elements are of the form $1^m0^n$ with $m> 0$ and $n\geq 0$. For example, the string '110100101' splits into the list of elements ['110', '100', '10', '1']. A BinaryChemistry is valid whenever the say function say(n,d) always returns a string starting with the character 1 and only returns a string ending with 1 when d is 1.

The decay matrix, characteristic polynomial, and dominant eigenvalue for this binary chemistry will be computed below.

Balanced quinary

In general, given a LookAndSay object it is the user's responsibility to provide a compatible splitting function before exploring the corresponding Chemistry. In many cases, one can use a Split Function Factory object to help create the split function. To illustrate the use of a SplitFuncFactory object, consider look and say sequences arising from the balanced quinary number system. This is the base 5 number system using digits Q, T, 0, 1, 2 where Q=-2 and T=-1.

>>> from look_and_say import *
>>> 
>>> def balanced_quinary(n):
...     '''Return the balanced quinary representation of n assuming 0<n<11'''
...     assert n < 11, "Can't say past 10."
...     bq = {1:'1', 2:'2', 3:'1Q', 4:'1T', 5:'10', 6:'11', 7:'12', 8:'2Q', 9:'2T', 10:'20'}
...     return bq[n]
... 
>>> bq_las = LookAndSay(balanced_quinary)
>>> 
>>> # Use the fact that balance quinary representations of positive integers 
>>> # never start with 0, T, or Q to determine appropriate splitting conditions.
>>> sff = SplitFuncFactory()
>>> sff.declare_split_after('Q', 'T', '0')
>>> split = sff.get_split()
>>> 
>>> bq_chem = Chemistry(bq_las, split)
>>> 
>>> # Generate all elements using any digit as a seed
>>> bq_chem.generate_elements(*'QT012')
>>> 
>>> bq_chem.print_periodic_table()
element   string       abundance    decay
E1        11121Q       0.0          [E12, E3]
E2        111Q         13.9680582   [E12, E7]
E3        112111Q      0.0          [E14, E7]
E4        112211       0.0          [E16]
E5        112221121Q   0.0          [E15, E19]
E6        1122211Q     7.9595623    [E15, E20]
E7        11Q          10.5441752   [E15]
E8        12111Q       0.0          [E1, E7]
E9        1221         0.0          [E4]
E10       122112111Q   0.0          [E5, E7]
E11       12211Q       10.5441752   [E6]
E12       1Q           18.5037375   [E2]
E13       211          0.0          [E9]
E14       21121Q       0.0          [E10]
E15       211Q         13.9680582   [E11]
E16       212221       0.0          [E8, E13]
E17       22110        0.0          [E22]
E18       22112        0.0          [E23]
E19       22112111Q    0.0          [E24, E7]
E20       2211Q        13.9680582   [E25]
E21       2211T        0.0          [E26]
E22       222110       0.0          [E12, E17]
E23       222112       0.0          [E12, E18]
E24       2221121Q     0.0          [E12, E19]
E25       22211Q       10.5441752   [E12, E20]
E26       22211T       0.0          [E12, E21]

Decay matrices, characteristic polynomials, and dominant eigenvalues

The decay matrix of a chemistry records the number of times every element appears in the decay of each element. The Chemistry method get_decay_matrix() returns the decay matrix as an array. For example, the following shows the decay matrix for the standard binary chemistry from above.

>>> decay_matrix = binary_chem.get_decay_matrix()
>>> 
>>> for row in decay_matrix:
...     print(row)
... 
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 1, 0, 0, 0, 1]
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0]

The characteristic polynomial of the decay matrix (found using sympy) is obtained with the get_char_poly() method.

>>> binary_chem.get_char_poly()
lambda**4*(lambda - 1)**2*(lambda + 1)*(lambda**3 - lambda**2 - 1)

By default, the polynomial is returned as a factored expression, but there is an option to obtain the non-factored polynomial:

>>> binary_chem.get_char_poly(factor=False)
lambda**10 - 2*lambda**9 + lambda**7 + lambda**5 - lambda**4

You can also get the characteristic polynomial formatted in latex:

>>> print(binary_chem.get_char_poly(latex=True))
\lambda^{4} \left(\lambda - 1\right)^{2} \left(\lambda + 1\right) \left(\lambda^{3} - \lambda^{2} - 1\right)
>>>
>>> print(binary_chem.get_char_poly(factor=False, latex=True))
\lambda^{10} - 2 \lambda^{9} + \lambda^{7} + \lambda^{5} - \lambda^{4}

The method get_dom_eigenvalue() returns the dominant (i.e. maximum real) eigenvalue of the decay matrix. This give the growth rate of the generic look and say sequence.

>>> binary_chem.get_dom_eigenvalue()
1.4655712318767662

This method applied to the standard decimal case from above gives us Conway's constant:

>>> chem.get_dom_eigenvalue()
1.3035772690342982

Reordering the elements

The elements in a chemistry can be reordered using the order_elements() method. For example, we can reorder the elements in Conway's chemistry according to the elements' string lengths:

>>> chem.order_elements(order_on='string length')
>>> chem.print_periodic_table()
element   string                                       abundance   decay
U         3                                            0.0102563   [Pa]
H         22                                           9.1790383   [H]
Ca        12                                           5.6072543   [K]
Pa        13                                           0.9883599   [Th]
Zn        312                                          2.3571391   [Cu]
Pm        132                                          2.9820456   [Nd]
Ar        3112                                         3.299717    [Cl]
K         1112                                         4.3014361   [Ar]
Ac        3113                                         0.6926935   [Ra]
Th        1113                                         0.7581905   [Ac]
Cr        31132                                        2.0605883   [V]
Co        32112                                        4.5645877   [Fe]
Sn        13211                                        0.1614395   [In]
La        11131                                        1.0326833   [Ba]
Hf        11132                                        0.2669097   [Lu]
Cl        132112                                       2.5312784   [S]
Cu        131112                                       1.8082082   [Ni]
Ba        311311                                       0.7921919   [Cs]
Nd        111312                                       2.2875864   [Pr]
Sm        311332                                       1.5408115   [Pm, Ca, Zn]
Lu        311312                                       0.2047517   [Yb]
Ra        132113                                       0.5313789   [Fr]
Si        1322112                                      3.2032813   [Al]
Sr        3112112                                      0.0102563   [Rb]
Y         1112133                                      0.0133699   [Sr, U]
Sb        3112221                                      0.2104488   [Pm, Sn]
Eu        1113222                                      2.0085669   [Sm]
Ho        1321132                                      4.7987529   [Dy]
At        1322113                                      0.23988     [Po]
V         13211312                                     1.5807182   [Ti]
Fe        13122112                                     3.5015859   [Mn]
Ni        11133112                                     1.3871124   [Zn, Co]
In        11131221                                     0.1238434   [Cd]
Cs        13211321                                     0.6077061   [Xe]
Pr        31131112                                     1.7548529   [Ce]
Na        123222112                                    1.4481449   [Ne]
Er        311311222                                    0.1098596   [Ho, Pm]
Pb        123222113                                    0.1082888   [Tl]
Mg        3113322112                                   1.8850441   [Pm, Na]
Al        1113222112                                   2.4573007   [Mg]
S         1113122112                                   1.9417939   [P]
Rb        1321122112                                   0.0078678   [Kr]
Cd        3113112211                                   0.0950027   [Ag]
Ce        1321133112                                   1.3461825   [La, H, Ca, Co]
Yb        1321131112                                   0.1570691   [Tm]
Bi        3113322113                                   0.1411629   [Pm, Pb]
Po        1113222113                                   0.1840167   [Bi]
Fr        1113122113                                   0.4076313   [Rn]
Gd        13221133112                                  2.1662973   [Eu, Ca, Co]
Ne        111213322112                                 1.1109007   [F]
P         311311222112                                 1.4895887   [Ho, Si]
Mn        111311222112                                 2.686136    [Cr, Si]
Ag        132113212221                                 0.0728785   [Pd]
Dy        111312211312                                 3.6812186   [Tb]
Tl        111213322113                                 0.0830705   [Hg]
Rn        311311222113                                 0.3127021   [Ho, At]
Te        1322113312211                                0.2743363   [Eu, Ca, Sb]
F         31121123222112                               0.852194    [O]
Ti        11131221131112                               1.2126003   [Sc]
Kr        11131221222112                               0.0060355   [Br]
Xe        11131221131211                               0.4661834   [I]
Tm        11131221133112                               0.1204908   [Er, Ca, Co]
Hg        31121123222113                               0.063725    [Au]
Tc        311322113212221                              0.0386077   [Mo]
Sc        3113112221133112                             0.9302097   [Ho, Pa, H, Ca, Co]
Br        3113112211322112                             0.00463     [Se]
Tb        3113112221131112                             2.8239359   [Ho, Gd]
Ga        13221133122211332                            0.1447891   [Eu, Ca, Ac, H, Ca, Zn]
O         132112211213322112                           0.6537349   [N]
Pd        111312211312113211                           0.0559065   [Rh]
I         311311222113111221                           0.3576186   [Ho, Te]
Au        132112211213322113                           0.0488847   [Pt]
Se        13211321222113222112                         0.0035518   [As]
Mo        13211322211312113211                         0.0296167   [Nb]
Ru        132211331222113112211                        0.0328995   [Eu, Ca, Tc]
Ge        31131122211311122113222                      0.1887437   [Ho, Ga]
Zr        12322211331222113112211                      0.0174286   [Y, H, Ca, Tc]
N         111312212221121123222112                     0.501493    [C]
Rh        311311222113111221131221                     0.042887    [Ho, Ru]
Pt        111312212221121123222113                     0.0375005   [Ir]
As        11131221131211322113322112                   0.0027246   [Ge, Na]
Li        312211322212221121123222112                  0.4220067   [He]
W         312211322212221121123222113                  0.0315567   [Ta]
C         3113112211322112211213322112                 0.3847053   [B]
Nb        1113122113322113111221131221                 0.0227196   [Er, Zr]
Ir        3113112211322112211213322113                 0.0287673   [Os]
He        13112221133211322112211213322112             0.3237297   [Hf, Pa, H, Ca, Li]
Ta        13112221133211322112211213322113             0.0242077   [Hf, Pa, H, Ca, W]
B         1321132122211322212221121123222112           0.295115    [Be]
Os        1321132122211322212221121123222113           0.022068    [Re]
Be        111312211312113221133211322112211213322112   0.2263886   [Ge, Ca, Li]
Re        111312211312113221133211322112211213322113   0.0169288   [Ge, Ca, W]

In the following example, we reorder the elements in the standard binary chemistry from above according to the abundances:

>>> binary_chem.order_elements('abundance')
>>> binary_chem.print_periodic_table()
element   string   abundance    decay
E1        110      21.6756572   [E2, E1]
E2        10       21.6756572   [E3]
E3        1110     14.7899036   [E4]
E4        11110    10.0915624   [E6, E1]
E5        1100     10.0915624   [E2, E5]
E6        100      10.0915624   [E7]
E7        11100    6.8857536    [E8]
E8        111100   4.6983411    [E6, E5]
E9        11       0.0          [E2, E10]
E10       1        0.0          [E9]

The options for the parameter order_on are the following:

order_on description
'abundance' Orders elements from highest abundance to lowest.
'string' Orders elements according to the lexicographic order of their strings.
'string length' Orders elements according to the lengths of their strings from shortest to longest.
'name' Orders elements alphabetically according to their names.
'key' Orders elements according to the function specified by the parameter key as in the python function sorted().

You can reverse the ordering above by passing the extra parameter reverse = True.

By default this method will automatically rename the elements according to their new order. This will not happen if the elements are named via Conway or if the parameter rename = False is passed.

Cosmology

Currently the Cosmology class is only implemented for the standard decimal look and say sequences where digits in the terms of the look and say sequences are restricted to 1, 2, and 3.

Computing the age of an exotic element

The Cosmology method days_exotic() will return the number of days it takes for a string to decay into a compound of common elements. For example, the following computes the number of days it takes for Methuselum to evolve into a compound of common elements:

>>> cosmo = Cosmology()
>>> methuselum = '22333222112'
>>> cosmo.days_exotic(methuselum)
24

The decay tree of a string

The Cosmology method decay_tree(string) starts with the passed string, then the say_what_you_see operation is applied repeatedly until the result splits as a compound of common elements. The method returns a nested dictionary corresponding to the resulting decay tree: The root of the tree is the passed string, the children of a node are the atoms obtained by applying the say_what_you_see operation to the node and splitting the result, the leaves are the nodes corresponding to common elements. For example, the following session computes the decay trees of '1' and Methuselum:

>>> cosmo.decay_tree('1')
{'1': [{'11': [{'21': [{'1211': [{'111221': [{'312211': [{'13112221': ['11132', '13211']}]}]}]}]}]}]}
>>>
>>> cosmo.decay_tree(methuselum)
{'22333222112': [{'2233322112': [{'2233222112': [{'2223322112': [{'3223222112': [{'132213322112': [{'1113221123222112': [{'311322211213322112': [{'1321133221121123222112': ['11131', '22', {'123222112211213322112': [{'11121332212221121123222112': ['3112112', {'32211322112211213322112': [{'1322211322212221121123222112': [{'11133221133211322112211213322112': [{'3123222': [{'1311121332': [{'11133112112': ['312', {'321122112': [{'131221222112': [{'1113112211322112': [{'311321222113222112': [{'1321131211322113322112': [{'111312211311122113222': [{'3113112221133122211332': ['1321132', '13', '22', '12', '3113', '22', '12', '312']}]}, '123222112']}]}]}]}]}]}, '312']}]}, '12', '312211322212221121123222112']}]}]}]}]}]}]}]}]}]}]}]}]}]}

Proof of Conway's Cosmology Theorem

The proof() method in the Cosmology class uses a backtracking algorithm to prove Conway's Cosmological Theorem. Currently the proof is only valid for the standard decimal look and say sequences where every term consists of strings of some of the digits 1, 2, and 3.

If we run the method proof(day = N) the algorithm searches for all strings that might appear as chunks of an N-day old element. The search starts with strings of length 1 (i.e. the digits) and then searches for strings of length 2, then length 3, etc. For each string found in the search, the algorithm repeatedly applies the say-what-you-see operation until the result is a compound of common elements. The algorithm terminates when for some positive integer L, there are no strings of length L that can appear as chunks of an N-day old element, and all strings of length less than L which might appear as a chunk of an N-day old element are shown to eventually decay into a compound of common elements.

Running the program prints a few details about the search. In particular, an upper bound for the age of an exotic (i.e. not common) element is displayed.

The default parameter is day = 9, which results in a proof of the Cosmological Theorem that gives an upper bound of 27 for the age of an exotic element.

The proof is essentially the same as that of Zeilberger. The implementation is similar to that of Litherland's proof1.

More examples

Conway's chemistry with transuranic elements

In the following example, the seeds used to generate elements guarantee that a few transuranic elements will appear. Also, we print the periodic table using Conway's convention that abundances are given in atoms per million.

>>> ls = LookAndSay()
>>> chem = Chemistry(ls)
>>> chem.generate_elements('11111', '78')
>>> chem.print_periodic_table(abundance_sum = 10**6)
element   string                                       abundance       decay
H         22                                           91790.383216    [H]
He        13112221133211322112211213322112             3237.2968587    [Hf, Pa, H, Ca, Li]
Li        312211322212221121123222112                  4220.0665982    [He]
Be        111312211312113221133211322112211213322112   2263.8860324    [Ge, Ca, Li]
B         1321132122211322212221121123222112           2951.1503716    [Be]
C         3113112211322112211213322112                 3847.0525419    [B]
N         111312212221121123222112                     5014.9302464    [C]
O         132112211213322112                           6537.349075     [N]
F         31121123222112                               8521.9396539    [O]
Ne        111213322112                                 11109.0068209   [F]
Na        123222112                                    14481.4487733   [Ne]
Mg        3113322112                                   18850.4412275   [Pm, Na]
Al        1113222112                                   24573.0066954   [Mg]
Si        1322112                                      32032.81296     [Al]
P         311311222112                                 14895.8866582   [Ho, Si]
S         1113122112                                   19417.9392497   [P]
Cl        132112                                       25312.7842174   [S]
Ar        3112                                         32997.1701218   [Cl]
K         1112                                         43014.3609132   [Ar]
Ca        12                                           56072.5431285   [K]
Sc        3113112221133112                             9302.0974443    [Ho, Pa, H, Ca, Co]
Ti        11131221131112                               12126.0027828   [Sc]
V         13211312                                     15807.1815919   [Ti]
Cr        31132                                        20605.8826107   [V]
Mn        111311222112                                 26861.3601797   [Cr, Si]
Fe        13122112                                     35015.8585455   [Mn]
Co        32112                                        45645.8772557   [Fe]
Ni        11133112                                     13871.1241997   [Zn, Co]
Cu        131112                                       18082.0822027   [Ni]
Zn        312                                          23571.3913363   [Cu]
Ga        13221133122211332                            1447.8905642    [Eu, Ca, Ac, H, Ca, Zn]
Ge        31131122211311122113222                      1887.4372276    [Ho, Ga]
As        11131221131211322113322112                   27.2462161      [Ge, Na]
Se        13211321222113222112                         35.5175479      [As]
Br        3113112211322112                             46.2998682      [Se]
Kr        11131221222112                               60.3554557      [Br]
Rb        1321122112                                   78.6780001      [Kr]
Sr        3112112                                      102.5628525     [Rb]
Y         1112133                                      133.6986032     [Sr, U]
Zr        12322211331222113112211                      174.28646       [Y, H, Ca, Tc]
Nb        1113122113322113111221131221                 227.1958675     [Er, Zr]
Mo        13211322211312113211                         296.1673685     [Nb]
Tc        311322113212221                              386.0770494     [Mo]
Ru        132211331222113112211                        328.9948058     [Eu, Ca, Tc]
Rh        311311222113111221131221                     428.8701504     [Ho, Ru]
Pd        111312211312113211                           559.0653795     [Rh]
Ag        132113212221                                 728.7849206     [Pd]
Cd        3113112211                                   950.0274565     [Ag]
In        11131221                                     1238.4341972    [Cd]
Sn        13211                                        1614.3946687    [In]
Sb        3112221                                      2104.4881933    [Pm, Sn]
Te        1322113312211                                2743.3629717    [Eu, Ca, Sb]
I         311311222113111221                           3576.1856107    [Ho, Te]
Xe        11131221131211                               4661.8342719    [I]
Cs        13211321                                     6077.0611889    [Xe]
Ba        311311                                       7921.9188284    [Cs]
La        11131                                        10326.8333118   [Ba]
Ce        1321133112                                   13461.8251664   [La, H, Ca, Co]
Pr        31131112                                     17548.5292866   [Ce]
Nd        111312                                       22875.863883    [Pr]
Pm        132                                          29820.4561674   [Nd]
Sm        311332                                       15408.1151815   [Pm, Ca, Zn]
Eu        1113222                                      20085.6687093   [Sm]
Gd        13221133112                                  21662.9728211   [Eu, Ca, Co]
Tb        3113112221131112                             28239.3589492   [Ho, Gd]
Dy        111312211312                                 36812.1864183   [Tb]
Ho        1321132                                      47987.5294384   [Dy]
Er        311311222                                    1098.5955997    [Ho, Pm]
Tm        11131221133112                               1204.9083841    [Er, Ca, Co]
Yb        1321131112                                   1570.6911808    [Tm]
Lu        311312                                       2047.51732      [Yb]
Hf        11132                                        2669.0970363    [Lu]
Ta        13112221133211322112211213322113             242.0773667     [Hf, Pa, H, Ca, W]
W         312211322212221121123222113                  315.5665525     [Ta]
Re        111312211312113221133211322112211213322113   169.2880181     [Ge, Ca, W]
Os        1321132122211322212221121123222113           220.6800123     [Re]
Ir        3113112211322112211213322113                 287.6734477     [Os]
Pt        111312212221121123222113                     375.0045674     [Ir]
Au        132112211213322113                           488.8474298     [Pt]
Hg        31121123222113                               637.2503975     [Au]
Tl        111213322113                                 830.7051329     [Hg]
Pb        123222113                                    1082.8883286    [Tl]
Bi        3113322113                                   1411.62861      [Pm, Pb]
Po        1113222113                                   1840.1669683    [Bi]
At        1322113                                      2398.7998311    [Po]
Rn        311311222113                                 3127.0209328    [Ho, At]
Fr        1113122113                                   4076.3134078    [Rn]
Ra        132113                                       5313.7894999    [Fr]
Ac        3113                                         6926.9352045    [Ra]
Th        1113                                         7581.9047124    [Ac]
Pa        13                                           9883.5986391    [Th]
U         3                                            102.5628525     [Pa]
Np5       13112221133211322112211213322115             0.0             [Hf, Pa, H, Ca, Pu5]
Np7       13112221133211322112211213322117             0.0             [Hf, Pa, H, Ca, Pu7]
Np8       13112221133211322112211213322118             0.0             [Hf, Pa, H, Ca, Pu8]
Pu5       312211322212221121123222115                  0.0             [Np5]
Pu7       312211322212221121123222117                  0.0             [Np7]
Pu8       312211322212221121123222118                  0.0             [Np8]

Twindragon binary

The following chemistry corresponds to the binary number system using the complex base $-1+i.$ This binary number system is known as twindragon binary.

>>> def twindragon_say(num):
...     assert num < 8, "This twindragon can only count to 7."
...     twindragon = {1:'1', 2:'1100', 3:'1101', 4:'111010000', 5:'111010001', 6:'111011100', 7:'111011101'}
...     return twindragon[num]
... 
>>> twindragon_ls = LookAndSay(twindragon_say)
>>> twindragon_chem = BinaryChemistry(twindragon_ls)
>>> twindragon_chem.generate_elements('1')
>>> twindragon_chem.print_periodic_table()
element   string   abundance    decay
E1        1        0.0          [E6]
E2        10       6.746022     [E9]
E3        1000     1.5358344    [E11, E2]
E4        10000    2.8580442    [E12, E5]
E5        100000   1.3339664    [E12, E3, E2]
E6        11       0.0          [E8, E1]
E7        110      28.3551578   [E8, E7]
E8        1100     24.8181731   [E8, E10]
E9        1110     14.6891551   [E7, E9]
E10       111000   11.5836587   [E7, E11, E2]
E11       11110    6.1234052    [E9, E4, E7]
E12       111110   1.9565832    [E9, E3, E9]
>>>
>>> twindragon_chem.get_char_poly()
lambda*(lambda - 1)*(lambda + 1)*(lambda**9 - 3*lambda**8 + 3*lambda**7 - 2*lambda**6 - 2*lambda**5 + 4*lambda**4 - 3*lambda**3 - lambda**2 - 3*lambda - 1)
>>>
>>> twindragon_chem.get_dom_eigenvalue()
2.142515914678024

Balanced ternary

In this example we find the chemical properties of a balanced ternary look and say sequence.

>>> # Define a (partial) say function to convert integers to balanced ternary:
... def bal_tern(num):
...     assert num < 11, "bal_tern will only convert integers from 1 to 10."
...     repn = {1:'1', 2:'1T', 3:'10', 4:'11', 5:'1TT', 6:'1T0', 7:'1T1', 8:'10T', 9:'100', 10:'101'}
...     return repn[num]
... 
>>> # Use the split function factory to generate an appropriate split function: 
... sff = SplitFuncFactory()
>>> sff.declare_split_after('0', 'T')
>>> split = sff.get_split()
>>> 
>>> # Instantiate the look and say and chemistry objects:
... bal_tern_ls = LookAndSay(bal_tern)
>>> bal_tern_chem = Chemistry(bal_tern_ls, split)
>>> 
>>> # Generate persistent elements from the seed '0', and order them according to their relative abundance:
... bal_tern_chem.generate_elements('0')
>>> bal_tern_chem.order_elements('abundance')
>>> 
>>> # Print the chemical properties
... bal_tern_chem.print_periodic_table()
element   string   abundance    decay
E1        1T       23.6067977   [E3]
E2        11T      23.6067977   [E1, E2]
E3        111T     14.5898034   [E5, E2]
E4        110      14.5898034   [E1, E4]
E5        10       14.5898034   [E6]
E6        1110     9.0169944    [E5, E4]
>>> 
>>> print(bal_tern_chem.get_char_poly())
lambda**3*(lambda - 1)*(lambda**2 - lambda - 1)
>>> 
>>> print(bal_tern_chem.get_dom_eigenvalue()) # golden!
1.618033988749895

Projects that used the look_and_say module

Acknowledgments

  • The initial implementation of the proof() method in the Cosmology class was written with Ethan Bassingthwaite and Monika de los Rios in the Spring of 2022 at The College of Idaho. We followed the strategy of Zeilberger's proof with implementation similar to that of Litherland.
  1"""
  2.. include:: ./docs/SUMMARY.md
  3
  4.. include:: ./docs/INSTALL.md
  5
  6.. include:: ./docs/EXAMPLESESSIONS.md
  7
  8.. include:: ./docs/MAINDOCS.md
  9
 10.. include:: ./docs/PROJECTS.md
 11
 12.. include:: ./docs/ACKNOWLEDGMENTS.md
 13"""
 14
 15import numpy
 16import sympy
 17
 18################## Conway's Conventions ############################
 19
 20def split_Conway(string):
 21    """
 22    Splits a string into a list of substrings according to Conway's Splitting Theorem.
 23    Assumes the string is not empty. 
 24    For example, split_Conway('1211132213') returns ['12', '1113', '22', '13'].
 25    """
 26    chunks = []
 27    start = 0
 28    for i in range(1, len(string)):
 29        if _is_split_pair_Conway(string[start:i], string[i:]):
 30            chunks.append(string[start:i])
 31            start = i
 32    chunks.append(string[start:])
 33    return chunks
 34
 35def _is_split_pair_Conway(L, R):
 36    """Implementation of Conway's Splitting Theorem"""
 37    if L == '' or R == '':
 38            return True # pragma: no cover
 39    if L[-1] not in ['1', '2', '3'] and R[0] in ['1', '2', '3']: # n] and [m
 40            return True
 41    if L[-1] == '2': # 2]
 42            if len(R) > 1 and R[0] == '1' and R[1] != '1' and (len(R) == 2 or R[2] != R[1]): #[1^1X^1
 43                    return True
 44            if len(R) > 2 and R[0] == '1' and R[1] == '1' and R[2] == '1' and (len(R) == 3 or R[3] != '1'): #[1^3
 45                    return True
 46            if R[0] == '3' and (len(R) == 1 or (R[1] != '3' and (len(R) < 4 or R[2] != R[1] or R[3] != R[1]))): #[3^1X^\not=3
 47                    return True
 48            if R[0] not in ['1', '2', '3'] and (len(R) == 1 or R[1] != R[0]): #[n^1
 49                    return True
 50    if L[-1] != '2' and len(R) > 1 and R[0] == '2' and R[1] == '2': # \not=2]
 51            if len(R) > 3 and R[2] == '1' and R[3] != '1' and (len(R) == 4 or R[4] != R[3]): #[2^21^1X^1
 52                    return True
 53            if len(R) > 4 and R[2] == '1' and R[3] == '1' and R[4] == '1' and (len(R) == 5 or R[5] != '1'): #[2^21^3
 54                    return True
 55            if len(R) > 2 and R[2] == '3' and (len(R) == 3 or (R[3] != '3' and (len(R) < 6 or R[4] != R[3] or R[5] != R[3]))): #[2^23^1X^\not=3
 56                    return True 
 57            if len(R) == 2 or (len(R) > 2 and R[2] not in ['1', '2', '3'] and (len(R) == 3 or R[3] != R[2])): #[2^2n^(0 or 1)
 58                    return True
 59    return False
 60
 61_CONWAY_ELEMENTS = {'3': {'name': 'U', 'number': 92}, '13': {'name': 'Pa', 'number': 91}, '1113': {'name': 'Th', 'number': 90}, '3113': {'name': 'Ac', 'number': 89}, '132113': {'name': 'Ra', 'number': 88}, '1113122113': {'name': 'Fr', 'number': 87}, '311311222113': {'name': 'Rn', 'number': 86}, '1322113': {'name': 'At', 'number': 85}, '1113222113': {'name': 'Po', 'number': 84}, '3113322113': {'name': 'Bi', 'number': 83}, '123222113': {'name': 'Pb', 'number': 82}, '111213322113': {'name': 'Tl', 'number': 81}, '31121123222113': {'name': 'Hg', 'number': 80}, '132112211213322113': {'name': 'Au', 'number': 79}, '111312212221121123222113': {'name': 'Pt', 'number': 78}, '3113112211322112211213322113': {'name': 'Ir', 'number': 77}, '1321132122211322212221121123222113': {'name': 'Os', 'number': 76}, '111312211312113221133211322112211213322113': {'name': 'Re', 'number': 75}, '312211322212221121123222113': {'name': 'W', 'number': 74}, '13112221133211322112211213322113': {'name': 'Ta', 'number': 73}, '11132': {'name': 'Hf', 'number': 72}, '311312': {'name': 'Lu', 'number': 71}, '1321131112': {'name': 'Yb', 'number': 70}, '11131221133112': {'name': 'Tm', 'number': 69}, '311311222': {'name': 'Er', 'number': 68}, '1321132': {'name': 'Ho', 'number': 67}, '111312211312': {'name': 'Dy', 'number': 66}, '3113112221131112': {'name': 'Tb', 'number': 65}, '13221133112': {'name': 'Gd', 'number': 64}, '1113222': {'name': 'Eu', 'number': 63}, '311332': {'name': 'Sm', 'number': 62}, '132': {'name': 'Pm', 'number': 61}, '111312': {'name': 'Nd', 'number': 60}, '31131112': {'name': 'Pr', 'number': 59}, '1321133112': {'name': 'Ce', 'number': 58}, '11131': {'name': 'La', 'number': 57}, '311311': {'name': 'Ba', 'number': 56}, '13211321': {'name': 'Cs', 'number': 55}, '11131221131211': {'name': 'Xe', 'number': 54}, '311311222113111221': {'name': 'I', 'number': 53}, '1322113312211': {'name': 'Te', 'number': 52}, '3112221': {'name': 'Sb', 'number': 51}, '13211': {'name': 'Sn', 'number': 50}, '11131221': {'name': 'In', 'number': 49}, '3113112211': {'name': 'Cd', 'number': 48}, '132113212221': {'name': 'Ag', 'number': 47}, '111312211312113211': {'name': 'Pd', 'number': 46}, '311311222113111221131221': {'name': 'Rh', 'number': 45}, '132211331222113112211': {'name': 'Ru', 'number': 44}, '311322113212221': {'name': 'Tc', 'number': 43}, '13211322211312113211': {'name': 'Mo', 'number': 42}, '1113122113322113111221131221': {'name': 'Nb', 'number': 41}, '12322211331222113112211': {'name': 'Zr', 'number': 40}, '1112133': {'name': 'Y', 'number': 39}, '3112112': {'name': 'Sr', 'number': 38}, '1321122112': {'name': 'Rb', 'number': 37}, '11131221222112': {'name': 'Kr', 'number': 36}, '3113112211322112': {'name': 'Br', 'number': 35}, '13211321222113222112': {'name': 'Se', 'number': 34}, '11131221131211322113322112': {'name': 'As', 'number': 33}, '31131122211311122113222': {'name': 'Ge', 'number': 32}, '13221133122211332': {'name': 'Ga', 'number': 31}, '312': {'name': 'Zn', 'number': 30}, '131112': {'name': 'Cu', 'number': 29}, '11133112': {'name': 'Ni', 'number': 28}, '32112': {'name': 'Co', 'number': 27}, '13122112': {'name': 'Fe', 'number': 26}, '111311222112': {'name': 'Mn', 'number': 25}, '31132': {'name': 'Cr', 'number': 24}, '13211312': {'name': 'V', 'number': 23}, '11131221131112': {'name': 'Ti', 'number': 22}, '3113112221133112': {'name': 'Sc', 'number': 21}, '12': {'name': 'Ca', 'number': 20}, '1112': {'name': 'K', 'number': 19}, '3112': {'name': 'Ar', 'number': 18}, '132112': {'name': 'Cl', 'number': 17}, '1113122112': {'name': 'S', 'number': 16}, '311311222112': {'name': 'P', 'number': 15}, '1322112': {'name': 'Si', 'number': 14}, '1113222112': {'name': 'Al', 'number': 13}, '3113322112': {'name': 'Mg', 'number': 12}, '123222112': {'name': 'Na', 'number': 11}, '111213322112': {'name': 'Ne', 'number': 10}, '31121123222112': {'name': 'F', 'number': 9}, '132112211213322112': {'name': 'O', 'number': 8}, '111312212221121123222112': {'name': 'N', 'number': 7}, '3113112211322112211213322112': {'name': 'C', 'number': 6}, '1321132122211322212221121123222112': {'name': 'B', 'number': 5}, '111312211312113221133211322112211213322112': {'name': 'Be', 'number': 4}, '312211322212221121123222112': {'name': 'Li', 'number': 3}, '13112221133211322112211213322112': {'name': 'He', 'number': 2}, '22': {'name': 'H', 'number': 1}}
 62
 63def _conway_name(element):
 64    string = element.get_string()
 65    if string in _CONWAY_ELEMENTS:
 66        return _CONWAY_ELEMENTS[string]['name']
 67    Pu = '31221132221222112112322211n'
 68    Np = '1311222113321132211221121332211n'
 69    if len(string) == len(Pu) and string[:-1] == Pu[:-1]:
 70        return 'Pu' + string[-1]
 71    if len(string) == len(Np) and string[:-1] == Np[:-1]:
 72        return 'Np' + string[-1]
 73    return element.get_name()
 74
 75def _conway_number(element):
 76    string = element.get_string()
 77    if string == '':
 78        return 0
 79    elif string in _CONWAY_ELEMENTS:
 80        return _CONWAY_ELEMENTS[string]['number']
 81    else: # Handling transuranic elements
 82        return 92 * int(string[:9]) + ord(string[-1])
 83
 84################# LOOK AND SAY #################################
 85
 86class LookAndSay():
 87    """
 88    A class responsible for the fundamental say-what-you-see operation
 89    that generates a look and say sequence. The parameter ``say`` in the
 90    constructor is a function that determines the decay of a chunk of the form
 91    $d^n$. The say function can have one or two parameters:
 92
 93    * If the say function accepts one parameter, the LookAndSay object will correspond to the decay $d^n\\to say(n)d$.
 94    * If the say function accepts two parameters, the LookAndSay object will correspond to the decay $d^n\\to say(n, d)$.
 95
 96    When no parameter is passed to the constructor, the LookAndSay
 97    object will correspond to standard decimal look and say sequences.
 98    """
 99    def __init__(self, say = None):
100        super(LookAndSay, self).__init__()
101        self._is_Conway = False
102        if say == None:
103            say = (lambda n : str(n))
104            self._is_Conway = True
105        self.say = say
106        self.sequence = []
107
108    def _chunk_op(self, char_count, char):
109        """
110        Conversion of the say function to a two parameter function 
111        if the say function takes only one parameter; 
112        just a copy of the say function otherwise.
113        """
114        try:
115            return self.say(char_count, char)
116        except:
117            return self.say(char_count) + char
118
119    def say_what_you_see(self, string):
120        """
121        The fundamental look and say operation that generates each 
122        term of a look and say sequence from its predecessor. For example, 
123        using the standard (default) LookAndSay object, 
124        ``say_what_you_see('1112222333')`` returns ``'314233'``.
125        """
126        if not string: return '' # handles empty string, which is falsy
127        letter = string[0]
128        result = ''
129        count = 0
130        for ch in string:
131            if ch == letter: 
132                count += 1
133            else:
134                result += self._chunk_op(count, letter)
135                count = 1
136                letter = ch 
137        result += self._chunk_op(count, letter)
138        return result
139     
140    def generate_sequence(self, seed, terms):
141        """
142        Generates the look and say sequence. The parameter ``seed`` is 
143        the initial term in the sequence, and ``terms`` is the 
144        number of terms generated.
145        """
146        if not seed: return None # handles empty seed, which is falsy
147        result = [seed]
148        for _ in range(terms-1):
149            result.append(self.say_what_you_see(result[-1]))
150        self.sequence = result
151
152    def get_sequence(self):
153        """Returns the look and say sequence as a list of strings"""
154        return self.sequence
155
156    def get_length_ratios(self):
157        """
158        Returns a list of the ratios of lengths of 
159        successive terms in the look and say sequence.
160        """
161        terms = len(self.sequence)
162        assert terms > 1, 'Look and say sequence does not have enough terms to compute the ratio of lengths.'
163        return [len(self.sequence[i+1]) / len(self.sequence[i]) for i in range(terms - 1)]
164
165    def get_last_length_ratio(self):
166        """
167        Returns the ratio of the lengths of the last 
168        two terms of the look and say sequence
169        """
170        return self.get_length_ratios()[-1]
171
172
173
174########### CHEMISTRY #####################
175
176class Chemistry():
177    """
178    A class responsible for generating all the persistent elements 
179    appearing in look and say sequences, along with 
180    the chemical properties of those elements.
181
182    Parameters in the constructor are a LookAndSay object ``las`` and
183    a splitting function ``split``. The user is responsible
184    for verifying that the provided splitting function is valid for the given
185    LookAndSay object. The default splitting function corresponds to 
186    Conway's original Splitting Theorem.
187    """
188    def __init__(self, las, split = split_Conway, elements = None):
189        super(Chemistry, self).__init__()
190        self.las = las
191        self.split = split
192        if elements == None:
193            elements = []
194        self.elements = elements
195        self._eigenvector = None
196
197    def _split_to_elements(self, string): 
198        return [Element(chunk, self.las) for chunk in self.split(string)]
199
200    def _generate_all_elements(self, strings):
201        for string in strings:
202            for elt in self._split_to_elements(string):
203                if elt not in self.elements:
204                    # add elt to the chemistry:
205                    self.elements.append(elt)
206                    # recursively set the decay for elt:
207                    decay_elts = self._split_to_elements(self.las.say_what_you_see(elt.get_string()))
208                    self._generate_all_elements(map(lambda e : e.get_string(), decay_elts))
209                    elt._set_decay(decay_elts)
210        # clean up decay for all elements:
211        for elt in self.elements:
212            dec = []
213            for d in elt.get_decay():
214                dec += [e for e in self.elements if e == d]
215            elt._set_decay(dec)
216
217    def _remove_intermittent_elements(self): 
218        while True:
219            common_elements = []
220            for elt in self.elements:
221                for d in elt.get_decay():
222                    if d not in common_elements:
223                        common_elements.append(d)
224            if len(common_elements) < len(self.elements):
225                self.elements = list(common_elements)
226            else: 
227                break # pragma: no cover
228
229    def generate_elements(self, *seeds):
230        """
231        Collects all the persistent elements from all the look and
232        say sequences generated by the given seeds. The string(s) entered as the 
233        parameter(s) will be used as the seed(s) for generating the elements. 
234        This method will clear any elements in the chemistry that exist 
235        before this method is called (i.e. prior to collecting from the new seeds). 
236        """
237
238        self.clear_elements()
239        strings = [self.las.say_what_you_see(seed) for seed in seeds] #only look at 2-day-old strings
240        self._generate_all_elements(strings)
241        self._remove_intermittent_elements()
242        self.order_elements('string')
243        self._name_elements()
244
245    def _name_elements(self):
246        if self.las._is_Conway:
247            for e in self.get_elements():
248                e.set_name(_conway_name(e))
249            self.elements = sorted(self.get_elements(), key = _conway_number)
250        else:
251            for i, e in enumerate(self.get_elements()):
252                e.set_name('E' + str(i + 1))
253
254    def get_elements(self):
255        """Returns the elements as a list."""
256        return self.elements
257
258    def get_element(self, name):
259        """Returns the element with the given name. Returns None there is no element with the given name."""
260        for e in self.elements:
261            if e.get_name() == name:
262                return e
263
264    def clear_elements(self):
265        """Resets the list of elements back to the empty list."""
266        self.elements = []
267        self._eigenvector = None
268
269    def get_periodic_table(self, dec_places = 7, abundance_sum = 100):
270        """
271        Creates a periodic table including each element's name, string, relative abundance, and decay.
272        Returns the periodic table as a nested dictionary.
273
274        """
275
276        return {e.get_name() : {'string' : e.get_string(), 
277                                'abundance' : self._get_abundances(dec_places, abundance_sum)[i],
278                                'decay' : e.get_decay()}
279                                for i, e in enumerate(self.get_elements())}
280
281    def print_periodic_table(self, dec_places = 7, abundance_sum = 100):
282        """
283        Prints the periodic table. Note the abundances are given as percentages, 
284        so they will differ from Conway's abundances by a factor of $10^4$.
285        The parameter ``dec_places`` refers to the accuracy of the abundances.
286        """
287        pt = self.get_periodic_table(dec_places, abundance_sum)
288        elt_width = 2 + max(len('element'), max([len(e.get_name()) for e in self.get_elements()]))
289        str_width = 2 + max(len('string'), max([len(e.get_string()) for e in self.get_elements()]))
290        ab_width  = 2 + max(len('abundance'), max([len(str(prop['abundance'])) for elt, prop in pt.items()]))
291        print("{:<{elt_width}} {:<{str_width}} {:<{ab_width}} {}".format('element', 'string', 'abundance', 'decay', elt_width=elt_width, str_width=str_width, ab_width=ab_width))
292        for elt, prop in pt.items():
293            print("{:<{elt_width}} {:<{str_width}} {:<{ab_width}} {}".format(elt, prop['string'], prop['abundance'], str(prop['decay']), elt_width=elt_width, str_width=str_width, ab_width=ab_width))
294
295    def get_decay_matrix(self):
296        """
297        Returns the decay matrix as a nested list of integers 
298        (i.e. a list of the rows).
299        The order of the columns and rows correspond to the order
300        in the list of elements.
301        """
302        mat = []
303        e = self.elements
304        for i in range(len(e)):
305            row = []
306            for j in range(len(e)):
307                row.append(e[j].get_decay().count(e[i]))
308            mat.append(row)
309        return mat
310
311    def get_dom_eigenvalue(self):
312        """
313        Returns the maximal real eigenvalue of the decay matrix.
314        In the standard case, this will give Conway's constant. 
315        In general, this will give the growth rate of the look and say sequence. 
316        This method assumes the existence of a real eigenvalue which
317        is larger than (the absolute value) of every other eigenvalue.
318        This assumption is usually guaranteed by the Perron-Frobenius Theorem.
319        """
320        assert len(self.elements) > 0, "The get_dom_eigenvalue method requires a nonempty list of elements.\n\tTo fix: Use the generate_elements method prior to calling get_dom_eigenvalue."
321        eigenstuff = numpy.linalg.eig(numpy.array(self.get_decay_matrix()))
322        eigenvalues = eigenstuff[0]
323        return max(eigenvalues).real
324
325    def get_char_poly(self, factor = True, latex = False):
326        """
327        Returns the characteristic polynomial of the decay matrix using sympy.
328        By default the returned polynomial will be factored. 
329        Use ``factor = False`` to get the expanded (i.e. unfactored) polynomial. 
330        Use ``latex = True`` to return the polynomial formatted in latex.
331        """
332        chi = sympy.Matrix(self.get_decay_matrix()).charpoly()
333        if factor:
334            chi = sympy.factor(chi.as_expr())
335        else:
336            chi = chi.as_expr()
337        if latex:
338            return sympy.latex(chi)
339        else:
340            return chi
341
342    def _get_abundances(self, dec_places = 7, abundance_sum = 100):
343        """
344        Returns a list of relative abundances of each element.
345        By default the abundances are given as percentages, 
346        so they will differ from Conway's abundances by a factor of $10^4$.
347        The abundances can be renormalized by setting the parameter ``abundance_sum``.
348        The order of the list corresponds to the order of the list of elements.
349        """
350        # Computing the eigenvector is time intensive for large chemistries, so 
351        # we keep it as an attribute of the object after computing it once.
352        if self._eigenvector is None:
353            eigenstuff = numpy.linalg.eig(numpy.array(self.get_decay_matrix()))
354            eigenvalues = eigenstuff[0]
355            eigenvectors = eigenstuff[1]
356            index = numpy.where(eigenvalues == max(eigenvalues))
357            limiting_eigenvector_nparray = eigenvectors[:,index].real
358            # The next two lines are converting the numpy array to a list
359            limiting_eigenvector = limiting_eigenvector_nparray.tolist()
360            limiting_eigenvector = [elt[0][0] for elt in limiting_eigenvector]
361            # Now store the eigenvector as a dictionary to avoid issues with reordering elements
362            self._eigenvector = {e:limiting_eigenvector[i] for i, e in enumerate(self.get_elements())}
363        eigenvector_lst = [self._eigenvector[e] for e in self.get_elements()]
364        abundance = [abs(round(abundance_sum * num / sum(eigenvector_lst), dec_places)) for num in eigenvector_lst]
365        return abundance
366
367    def order_elements(self, order_on, key = None, reverse = False, rename = True):
368        """
369        Reorders the list of elements depending on the parameter ``order_on`` as follows:
370
371        * ``order_on='abundance'``: Orders elements from highest abundance to lowest.
372        * ``order_on='string'``: Orders elements according to the lexicographic order of their strings.
373        * ``order_on='string length'``: Orders elements according to the lengths of their strings from shortest to longest.
374        * ``order_on='name'``: Orders elements alphabetically according to their names.
375        * ``order_on='key'``: Orders elements according to the function specified by the parameter ``key``.
376
377        You can reverse the ordering above by passing the extra parameter ``reverse = True``.
378        
379        By default this method will automatically rename the elements according to their new order.
380        This will not happen if the elements are named via Conway or if the parameter ``rename = False`` is passed.
381        """
382        assert order_on in ['abundance', 'name', 'string', 'string length', 'key'], "Invalid parameter passed to order_elements. Valid parameter are 'abundance', 'name', 'string', 'string length', and 'key'."
383        pt = self.get_periodic_table()
384        sorted_key = {
385            'abundance': lambda e : pt[e.get_name()]['abundance'],
386            'name': lambda e : e.get_name(),
387            'string': lambda e : e.get_string(),
388            'string length': lambda e : len(e.get_string()),
389            'key': key
390        }
391        self.elements = sorted(self.get_elements(), key = sorted_key[order_on])
392        if order_on == 'abundance':
393            self.elements.reverse()
394        if reverse:
395            self.elements.reverse()
396        if not self.las._is_Conway and rename:
397            self._name_elements()
398
399class BinaryChemistry(Chemistry):
400    """
401    A chemistry for binary look and say sequences that split as 1.0 
402    (i.e. whenever a 1 is left of a 0). This chemistry is valid whenever
403    the say-what-you-see operation maps 
404    $d^n$ to $[n]d$ where $[n]$ is a binary representation of n
405    that always starts with a 1. For example, this chemistry is 
406    valid for standard base two binary look and say sequences. 
407    """
408    def __init__(self, las, elements = None):
409        sff = SplitFuncFactory()
410        sff.declare_split_before('1')
411        binary_split = sff.get_split()
412        super().__init__(las, binary_split, elements)
413
414########### ELEMENT #######################
415
416class Element():
417    """
418    An element consists of a string (usually a chunk of digits) and 
419    a name. For example, in Conway's chemistry there is an element
420    named H (short for Hydrogen) consisting of the string '22'. 
421    Each element decays into a list of other elements. 
422    The only methods for this class are getters and a setter.
423    """
424    def __init__(self, string, las, decay = []):
425        super(Element, self).__init__()
426        self.string = string
427        self.las = las
428        self.name = string
429        self.decay = decay
430
431    def __str__(self):
432        return self.name
433
434    def __repr__(self):
435        return self.name
436
437    def __eq__(self, other):
438        """Overrides the default implementation"""
439        if isinstance(other, Element):
440                return self.string == other.string and self.las == other.las
441        return NotImplemented
442
443    def __hash__(self):
444        """Overrides the default implementation"""
445        return hash((self.string, self.las))
446
447    def _set_decay(self, elements):
448        self.decay = elements
449
450    def get_decay(self):
451        return self.decay
452
453    def set_name(self, name):
454        self.name = name
455
456    def get_name(self):
457        return self.name
458
459    def get_string(self):
460        return self.string
461
462########### SPLIT FUNCTION FACTORY #####################
463
464class SplitFuncFactory():
465    """
466    A class to help create a split function. The split function factory
467    can produce a split function via any combination of the following:
468
469    * Specifying specific chunks L and R such that LR splits as L.R.
470    * Specifying specific characters or chunks to always split before or after.
471    """
472    def __init__(self):
473        self._splitting_pairs = []
474        self._chunks_before_split = []
475        self._chunks_after_split = []
476
477    def get_split(self):
478        """Returns the split function."""
479        return lambda string : self._split(string)
480
481    def _split(self, string):
482        chunks = []
483        start = 0
484        for i in range(1, len(string)):
485            if self._is_split(string[start:i], string[i:]):
486                chunks.append(string[start:i])
487                start = i
488        chunks.append(string[start:])
489        return chunks
490
491    def _is_split(self, L, R):
492        if L == '' or R == '':
493            return True # pragma: no cover
494        if L[-1] == R[0]:
495            return False
496        for l in self._chunks_before_split:
497            if len(l) <= len(L) and l == L[-len(l):]:
498                return True
499        for r in self._chunks_after_split:
500            if len(r) <= len(R) and r == R[:len(r)]:
501                return True
502        for l, r in self._splitting_pairs:
503            if len(l) <= len(L) and len(r) <= len(R) and l == L[-len(l):] and r == R[:len(r)]:
504                return True
505        return False
506
507    def declare_splitting_pairs(self, *pairs):
508        """
509        Specify pairs of chunks in the form (L, R) 
510        such that LR always splits as L.R.
511        """
512        for pair in pairs:
513            self._splitting_pairs.append(pair)
514
515    def declare_split_after(self, *chunks):
516        """
517        Specify chunks L such that LR splits for every possible R (assuming the last character of L and the first character of R are distinct).
518        """
519        for chunk in chunks:
520            self._chunks_before_split.append(chunk)
521
522    def declare_split_before(self, *chunks):
523        """
524        Specify chunks R such that LR splits for every possible L (assuming the last character of L and the first character of R are distinct).
525        """
526        for chunk in chunks:
527            self._chunks_after_split.append(chunk)
528
529########### COSMOLOGY #####################
530
531class Cosmology():
532    '''
533    A class for proving Conway's Cosmological Theorem.
534    Currently this will only prove The Cosmological Theorem 
535    for the standard decimal look and say sequences where every term
536    consists of strings of some of the digits 1, 2, and 3.  
537    '''
538    def __init__(self, digits = '123'):
539        self.las = LookAndSay()
540        self.split = split_Conway
541        self.digits = digits
542        self._compendium_sets = []
543        self.common_strings = {elt for elt in _CONWAY_ELEMENTS}
544        # add transuranic elements to the list of common strings:
545        for digit in self.digits:
546            if digit not in '123':
547                self.common_strings.add('31221132221222112112322211' + digit)
548                self.common_strings.add('1311222113321132211221121332211' + digit)
549
550    def days_exotic(self, string):
551        '''
552        Returns the number of days until the string splits into a compound of common elements. 
553        '''
554        atoms = [atom for atom in self.split(string) if atom not in self.common_strings]
555        days = 0
556        while atoms != []:
557            next_atoms = []
558            for atom in atoms:
559                new_atoms = self.split(self.las.say_what_you_see(atom))
560                next_atoms += [a for a in new_atoms if a not in self.common_strings]
561            atoms = next_atoms
562            days += 1
563        return days
564
565    def decay_tree(self, string):
566        '''
567        Starting with the passed string, the say_what_you_see operation is applied
568        repeatedly until the result splits as a compound of common elements. 
569        Returns a nested dictionary corresponding to the resulting *decay tree*: 
570        The root of the tree is the passed string, the children of a node are the 
571        atoms obtained by applying the say_what_you_see operation to the node and 
572        splitting the result, the leaves are the nodes corresponding to common elements.
573        '''
574        if string in self.common_strings:
575            return string
576        return {string: [self.decay_tree(atom) for atom in self.split(self.las.say_what_you_see(string))]}
577
578
579    def proof(self, day = 9):
580        '''
581        Uses a backtracking algorithm to prove Conway's Cosmological Theorem. If we pass the parameter
582        ``day = N`` the algorithm searches for all strings that might appear as chunks of an N-day
583        old element. The search starts with strings of length 1 (i.e. the digits) and then 
584        searches for strings of length 2, then length 3, etc. For each string found in the search, 
585        the algorithm repeatedly applies the say-what-you-see operation until the result is a 
586        compound of common elements. The algorithm terminates when for some positive integer L, 
587        there are no strings of length L that can appear as chunks of an N-day old element, and 
588        all strings of length less than L which might appear as a chunk of an N-day old element 
589        are shown to eventually decay into a compound of common elements.
590
591        Running the program prints a few details about the search. In particular, an upper bound
592        for the age of an exotic (i.e. not common) element is displayed.  
593
594        The default parameter is ``day = 9``, which results in a proof of the Cosmological Theorem
595        that gives an upper bound of 27 for the age of an exotic element. 
596
597        The proof is essentially the same as that of Zeilberger. 
598        The implementation is similar to that of Litherland.
599        '''
600        chunks = self.digits # start with length 1 chunks
601        max_days_exotic = 0
602
603        print(f'To prove the Cosmological Theorem we search for all strings\nwhich could appear as chunks of {day} day old elements.\nSearching...')
604        length = 0
605        while True:
606            length += 1
607            compendium = []
608            #Gather all chunks that have a grandparent to the compendium
609            for chunk in chunks:
610                if self._has_grandparent(chunk, day):
611                    compendium.append(chunk)
612
613            if compendium == []:
614                print(f'There are no strings of length {length} that can appear as chunks\nof {day} day old elements. All strings of length less than {length}\nthat could appear after {day} days decay into compounds of common\nelements after an additional {max_days_exotic-day} days. This gives an upper\nbound of {max_days_exotic} days for the age of an exotic element.\nQ.E.D.')
615                return max_days_exotic
616
617            self._compendium_sets.append(set(compendium))
618
619            #Compute an upper bound on the maximum longevity of an exotic element:
620            for chunk in compendium:
621                lifespan = day + self.days_exotic(chunk)
622                if lifespan > max_days_exotic:
623                    max_days_exotic = lifespan
624
625            chunks = []
626            #For each found above, add all possible digits to the left and check if splits
627            for digit in self.digits:
628                chunks += [digit+chunk for chunk in compendium if len(self.split(digit+chunk)) == 1]
629
630    def _parents(self, kid):
631        '''
632        We call a string a *parent* of the kid if applying the say-what-you-see operation
633        results in a string which contains the kid as a substring. This function returns
634        a list of all the minimal parents of kid (here minimal means that every parent of
635        the kid will contain one of the elements of the list as a substring).
636        '''
637        parents = []
638        if self._is_day_one_even(kid):
639            parents += self._even_parents(kid)
640        if self._is_day_one_odd(kid):
641            parents += self._odd_parents(kid)
642        return parents
643
644    def _is_day_one_odd(self, string):
645        if len(string) == 1:
646            return False
647        if len(string) % 2 == 0:
648            i = 0
649        else:
650            i = 1
651        while i < len(string) - 2:
652            if string[i] == string[i+2]:
653                return False
654            i += 2
655        return True
656
657    def _is_day_one_even(self, string):
658        if len(string) == 1:
659            return True
660        if len(string) % 2 == 0:
661            i = 1
662        else:
663            i = 0
664        while i < len(string) - 2:
665            if string[i] == string[i+2]:
666                return False
667            i += 2
668        return True
669
670    def _even_parents(self, string):
671        new_string = ''
672        if len(string) % 2 == 0:
673            start = 0
674        else:
675            start = 1
676            new_string += string[0]
677        while start < len(string) - 1:
678            new_string += int(string[start]) * string[start+1]
679            start += 2
680        return [new_string]
681
682    def _odd_parents(self, string):
683        new_string = ''
684        if len(string) % 2 == 0:
685            start = 1
686            new_string += string[0]
687        else:
688            start = 0
689        while start < len(string) - 2:
690            new_string += int(string[start]) * string[start+1]
691            start += 2
692        return [new_string + int(string[-1]) * digit for digit in self.digits if digit != string[-2]]
693
694
695    def _has_grandparent(self, kid, day):
696        '''
697        Returns True if the string kid *might* be contained in the result of applying
698        the say-what-you-see operation day-times to some string. Note that this method
699        will only return False when kid cannot be part of a day-old descendant of 
700        any string, but may return True even if kid is not contained in any day-old descendant.
701        '''
702        # Apply the parents function day-times and see what we get:
703        ancestors = [kid]
704        for _ in range(day):
705            next_ancestors = []
706            for a in ancestors:
707                next_ancestors += self._parents(a)
708            ancestors = next_ancestors
709
710        return len(ancestors) != 0
def split_Conway(string)
21def split_Conway(string):
22    """
23    Splits a string into a list of substrings according to Conway's Splitting Theorem.
24    Assumes the string is not empty. 
25    For example, split_Conway('1211132213') returns ['12', '1113', '22', '13'].
26    """
27    chunks = []
28    start = 0
29    for i in range(1, len(string)):
30        if _is_split_pair_Conway(string[start:i], string[i:]):
31            chunks.append(string[start:i])
32            start = i
33    chunks.append(string[start:])
34    return chunks

Splits a string into a list of substrings according to Conway's Splitting Theorem. Assumes the string is not empty. For example, split_Conway('1211132213') returns ['12', '1113', '22', '13'].

class LookAndSay:
 87class LookAndSay():
 88    """
 89    A class responsible for the fundamental say-what-you-see operation
 90    that generates a look and say sequence. The parameter ``say`` in the
 91    constructor is a function that determines the decay of a chunk of the form
 92    $d^n$. The say function can have one or two parameters:
 93
 94    * If the say function accepts one parameter, the LookAndSay object will correspond to the decay $d^n\\to say(n)d$.
 95    * If the say function accepts two parameters, the LookAndSay object will correspond to the decay $d^n\\to say(n, d)$.
 96
 97    When no parameter is passed to the constructor, the LookAndSay
 98    object will correspond to standard decimal look and say sequences.
 99    """
100    def __init__(self, say = None):
101        super(LookAndSay, self).__init__()
102        self._is_Conway = False
103        if say == None:
104            say = (lambda n : str(n))
105            self._is_Conway = True
106        self.say = say
107        self.sequence = []
108
109    def _chunk_op(self, char_count, char):
110        """
111        Conversion of the say function to a two parameter function 
112        if the say function takes only one parameter; 
113        just a copy of the say function otherwise.
114        """
115        try:
116            return self.say(char_count, char)
117        except:
118            return self.say(char_count) + char
119
120    def say_what_you_see(self, string):
121        """
122        The fundamental look and say operation that generates each 
123        term of a look and say sequence from its predecessor. For example, 
124        using the standard (default) LookAndSay object, 
125        ``say_what_you_see('1112222333')`` returns ``'314233'``.
126        """
127        if not string: return '' # handles empty string, which is falsy
128        letter = string[0]
129        result = ''
130        count = 0
131        for ch in string:
132            if ch == letter: 
133                count += 1
134            else:
135                result += self._chunk_op(count, letter)
136                count = 1
137                letter = ch 
138        result += self._chunk_op(count, letter)
139        return result
140     
141    def generate_sequence(self, seed, terms):
142        """
143        Generates the look and say sequence. The parameter ``seed`` is 
144        the initial term in the sequence, and ``terms`` is the 
145        number of terms generated.
146        """
147        if not seed: return None # handles empty seed, which is falsy
148        result = [seed]
149        for _ in range(terms-1):
150            result.append(self.say_what_you_see(result[-1]))
151        self.sequence = result
152
153    def get_sequence(self):
154        """Returns the look and say sequence as a list of strings"""
155        return self.sequence
156
157    def get_length_ratios(self):
158        """
159        Returns a list of the ratios of lengths of 
160        successive terms in the look and say sequence.
161        """
162        terms = len(self.sequence)
163        assert terms > 1, 'Look and say sequence does not have enough terms to compute the ratio of lengths.'
164        return [len(self.sequence[i+1]) / len(self.sequence[i]) for i in range(terms - 1)]
165
166    def get_last_length_ratio(self):
167        """
168        Returns the ratio of the lengths of the last 
169        two terms of the look and say sequence
170        """
171        return self.get_length_ratios()[-1]

A class responsible for the fundamental say-what-you-see operation that generates a look and say sequence. The parameter say in the constructor is a function that determines the decay of a chunk of the form $d^n$. The say function can have one or two parameters:

  • If the say function accepts one parameter, the LookAndSay object will correspond to the decay $d^n\to say(n)d$.
  • If the say function accepts two parameters, the LookAndSay object will correspond to the decay $d^n\to say(n, d)$.

When no parameter is passed to the constructor, the LookAndSay object will correspond to standard decimal look and say sequences.

LookAndSay(say=None)
100    def __init__(self, say = None):
101        super(LookAndSay, self).__init__()
102        self._is_Conway = False
103        if say == None:
104            say = (lambda n : str(n))
105            self._is_Conway = True
106        self.say = say
107        self.sequence = []
def say_what_you_see(self, string)
120    def say_what_you_see(self, string):
121        """
122        The fundamental look and say operation that generates each 
123        term of a look and say sequence from its predecessor. For example, 
124        using the standard (default) LookAndSay object, 
125        ``say_what_you_see('1112222333')`` returns ``'314233'``.
126        """
127        if not string: return '' # handles empty string, which is falsy
128        letter = string[0]
129        result = ''
130        count = 0
131        for ch in string:
132            if ch == letter: 
133                count += 1
134            else:
135                result += self._chunk_op(count, letter)
136                count = 1
137                letter = ch 
138        result += self._chunk_op(count, letter)
139        return result

The fundamental look and say operation that generates each term of a look and say sequence from its predecessor. For example, using the standard (default) LookAndSay object, say_what_you_see('1112222333') returns '314233'.

def generate_sequence(self, seed, terms)
141    def generate_sequence(self, seed, terms):
142        """
143        Generates the look and say sequence. The parameter ``seed`` is 
144        the initial term in the sequence, and ``terms`` is the 
145        number of terms generated.
146        """
147        if not seed: return None # handles empty seed, which is falsy
148        result = [seed]
149        for _ in range(terms-1):
150            result.append(self.say_what_you_see(result[-1]))
151        self.sequence = result

Generates the look and say sequence. The parameter seed is the initial term in the sequence, and terms is the number of terms generated.

def get_sequence(self)
153    def get_sequence(self):
154        """Returns the look and say sequence as a list of strings"""
155        return self.sequence

Returns the look and say sequence as a list of strings

def get_length_ratios(self)
157    def get_length_ratios(self):
158        """
159        Returns a list of the ratios of lengths of 
160        successive terms in the look and say sequence.
161        """
162        terms = len(self.sequence)
163        assert terms > 1, 'Look and say sequence does not have enough terms to compute the ratio of lengths.'
164        return [len(self.sequence[i+1]) / len(self.sequence[i]) for i in range(terms - 1)]

Returns a list of the ratios of lengths of successive terms in the look and say sequence.

def get_last_length_ratio(self)
166    def get_last_length_ratio(self):
167        """
168        Returns the ratio of the lengths of the last 
169        two terms of the look and say sequence
170        """
171        return self.get_length_ratios()[-1]

Returns the ratio of the lengths of the last two terms of the look and say sequence

class Chemistry:
177class Chemistry():
178    """
179    A class responsible for generating all the persistent elements 
180    appearing in look and say sequences, along with 
181    the chemical properties of those elements.
182
183    Parameters in the constructor are a LookAndSay object ``las`` and
184    a splitting function ``split``. The user is responsible
185    for verifying that the provided splitting function is valid for the given
186    LookAndSay object. The default splitting function corresponds to 
187    Conway's original Splitting Theorem.
188    """
189    def __init__(self, las, split = split_Conway, elements = None):
190        super(Chemistry, self).__init__()
191        self.las = las
192        self.split = split
193        if elements == None:
194            elements = []
195        self.elements = elements
196        self._eigenvector = None
197
198    def _split_to_elements(self, string): 
199        return [Element(chunk, self.las) for chunk in self.split(string)]
200
201    def _generate_all_elements(self, strings):
202        for string in strings:
203            for elt in self._split_to_elements(string):
204                if elt not in self.elements:
205                    # add elt to the chemistry:
206                    self.elements.append(elt)
207                    # recursively set the decay for elt:
208                    decay_elts = self._split_to_elements(self.las.say_what_you_see(elt.get_string()))
209                    self._generate_all_elements(map(lambda e : e.get_string(), decay_elts))
210                    elt._set_decay(decay_elts)
211        # clean up decay for all elements:
212        for elt in self.elements:
213            dec = []
214            for d in elt.get_decay():
215                dec += [e for e in self.elements if e == d]
216            elt._set_decay(dec)
217
218    def _remove_intermittent_elements(self): 
219        while True:
220            common_elements = []
221            for elt in self.elements:
222                for d in elt.get_decay():
223                    if d not in common_elements:
224                        common_elements.append(d)
225            if len(common_elements) < len(self.elements):
226                self.elements = list(common_elements)
227            else: 
228                break # pragma: no cover
229
230    def generate_elements(self, *seeds):
231        """
232        Collects all the persistent elements from all the look and
233        say sequences generated by the given seeds. The string(s) entered as the 
234        parameter(s) will be used as the seed(s) for generating the elements. 
235        This method will clear any elements in the chemistry that exist 
236        before this method is called (i.e. prior to collecting from the new seeds). 
237        """
238
239        self.clear_elements()
240        strings = [self.las.say_what_you_see(seed) for seed in seeds] #only look at 2-day-old strings
241        self._generate_all_elements(strings)
242        self._remove_intermittent_elements()
243        self.order_elements('string')
244        self._name_elements()
245
246    def _name_elements(self):
247        if self.las._is_Conway:
248            for e in self.get_elements():
249                e.set_name(_conway_name(e))
250            self.elements = sorted(self.get_elements(), key = _conway_number)
251        else:
252            for i, e in enumerate(self.get_elements()):
253                e.set_name('E' + str(i + 1))
254
255    def get_elements(self):
256        """Returns the elements as a list."""
257        return self.elements
258
259    def get_element(self, name):
260        """Returns the element with the given name. Returns None there is no element with the given name."""
261        for e in self.elements:
262            if e.get_name() == name:
263                return e
264
265    def clear_elements(self):
266        """Resets the list of elements back to the empty list."""
267        self.elements = []
268        self._eigenvector = None
269
270    def get_periodic_table(self, dec_places = 7, abundance_sum = 100):
271        """
272        Creates a periodic table including each element's name, string, relative abundance, and decay.
273        Returns the periodic table as a nested dictionary.
274
275        """
276
277        return {e.get_name() : {'string' : e.get_string(), 
278                                'abundance' : self._get_abundances(dec_places, abundance_sum)[i],
279                                'decay' : e.get_decay()}
280                                for i, e in enumerate(self.get_elements())}
281
282    def print_periodic_table(self, dec_places = 7, abundance_sum = 100):
283        """
284        Prints the periodic table. Note the abundances are given as percentages, 
285        so they will differ from Conway's abundances by a factor of $10^4$.
286        The parameter ``dec_places`` refers to the accuracy of the abundances.
287        """
288        pt = self.get_periodic_table(dec_places, abundance_sum)
289        elt_width = 2 + max(len('element'), max([len(e.get_name()) for e in self.get_elements()]))
290        str_width = 2 + max(len('string'), max([len(e.get_string()) for e in self.get_elements()]))
291        ab_width  = 2 + max(len('abundance'), max([len(str(prop['abundance'])) for elt, prop in pt.items()]))
292        print("{:<{elt_width}} {:<{str_width}} {:<{ab_width}} {}".format('element', 'string', 'abundance', 'decay', elt_width=elt_width, str_width=str_width, ab_width=ab_width))
293        for elt, prop in pt.items():
294            print("{:<{elt_width}} {:<{str_width}} {:<{ab_width}} {}".format(elt, prop['string'], prop['abundance'], str(prop['decay']), elt_width=elt_width, str_width=str_width, ab_width=ab_width))
295
296    def get_decay_matrix(self):
297        """
298        Returns the decay matrix as a nested list of integers 
299        (i.e. a list of the rows).
300        The order of the columns and rows correspond to the order
301        in the list of elements.
302        """
303        mat = []
304        e = self.elements
305        for i in range(len(e)):
306            row = []
307            for j in range(len(e)):
308                row.append(e[j].get_decay().count(e[i]))
309            mat.append(row)
310        return mat
311
312    def get_dom_eigenvalue(self):
313        """
314        Returns the maximal real eigenvalue of the decay matrix.
315        In the standard case, this will give Conway's constant. 
316        In general, this will give the growth rate of the look and say sequence. 
317        This method assumes the existence of a real eigenvalue which
318        is larger than (the absolute value) of every other eigenvalue.
319        This assumption is usually guaranteed by the Perron-Frobenius Theorem.
320        """
321        assert len(self.elements) > 0, "The get_dom_eigenvalue method requires a nonempty list of elements.\n\tTo fix: Use the generate_elements method prior to calling get_dom_eigenvalue."
322        eigenstuff = numpy.linalg.eig(numpy.array(self.get_decay_matrix()))
323        eigenvalues = eigenstuff[0]
324        return max(eigenvalues).real
325
326    def get_char_poly(self, factor = True, latex = False):
327        """
328        Returns the characteristic polynomial of the decay matrix using sympy.
329        By default the returned polynomial will be factored. 
330        Use ``factor = False`` to get the expanded (i.e. unfactored) polynomial. 
331        Use ``latex = True`` to return the polynomial formatted in latex.
332        """
333        chi = sympy.Matrix(self.get_decay_matrix()).charpoly()
334        if factor:
335            chi = sympy.factor(chi.as_expr())
336        else:
337            chi = chi.as_expr()
338        if latex:
339            return sympy.latex(chi)
340        else:
341            return chi
342
343    def _get_abundances(self, dec_places = 7, abundance_sum = 100):
344        """
345        Returns a list of relative abundances of each element.
346        By default the abundances are given as percentages, 
347        so they will differ from Conway's abundances by a factor of $10^4$.
348        The abundances can be renormalized by setting the parameter ``abundance_sum``.
349        The order of the list corresponds to the order of the list of elements.
350        """
351        # Computing the eigenvector is time intensive for large chemistries, so 
352        # we keep it as an attribute of the object after computing it once.
353        if self._eigenvector is None:
354            eigenstuff = numpy.linalg.eig(numpy.array(self.get_decay_matrix()))
355            eigenvalues = eigenstuff[0]
356            eigenvectors = eigenstuff[1]
357            index = numpy.where(eigenvalues == max(eigenvalues))
358            limiting_eigenvector_nparray = eigenvectors[:,index].real
359            # The next two lines are converting the numpy array to a list
360            limiting_eigenvector = limiting_eigenvector_nparray.tolist()
361            limiting_eigenvector = [elt[0][0] for elt in limiting_eigenvector]
362            # Now store the eigenvector as a dictionary to avoid issues with reordering elements
363            self._eigenvector = {e:limiting_eigenvector[i] for i, e in enumerate(self.get_elements())}
364        eigenvector_lst = [self._eigenvector[e] for e in self.get_elements()]
365        abundance = [abs(round(abundance_sum * num / sum(eigenvector_lst), dec_places)) for num in eigenvector_lst]
366        return abundance
367
368    def order_elements(self, order_on, key = None, reverse = False, rename = True):
369        """
370        Reorders the list of elements depending on the parameter ``order_on`` as follows:
371
372        * ``order_on='abundance'``: Orders elements from highest abundance to lowest.
373        * ``order_on='string'``: Orders elements according to the lexicographic order of their strings.
374        * ``order_on='string length'``: Orders elements according to the lengths of their strings from shortest to longest.
375        * ``order_on='name'``: Orders elements alphabetically according to their names.
376        * ``order_on='key'``: Orders elements according to the function specified by the parameter ``key``.
377
378        You can reverse the ordering above by passing the extra parameter ``reverse = True``.
379        
380        By default this method will automatically rename the elements according to their new order.
381        This will not happen if the elements are named via Conway or if the parameter ``rename = False`` is passed.
382        """
383        assert order_on in ['abundance', 'name', 'string', 'string length', 'key'], "Invalid parameter passed to order_elements. Valid parameter are 'abundance', 'name', 'string', 'string length', and 'key'."
384        pt = self.get_periodic_table()
385        sorted_key = {
386            'abundance': lambda e : pt[e.get_name()]['abundance'],
387            'name': lambda e : e.get_name(),
388            'string': lambda e : e.get_string(),
389            'string length': lambda e : len(e.get_string()),
390            'key': key
391        }
392        self.elements = sorted(self.get_elements(), key = sorted_key[order_on])
393        if order_on == 'abundance':
394            self.elements.reverse()
395        if reverse:
396            self.elements.reverse()
397        if not self.las._is_Conway and rename:
398            self._name_elements()

A class responsible for generating all the persistent elements appearing in look and say sequences, along with the chemical properties of those elements.

Parameters in the constructor are a LookAndSay object las and a splitting function split. The user is responsible for verifying that the provided splitting function is valid for the given LookAndSay object. The default splitting function corresponds to Conway's original Splitting Theorem.

Chemistry(las, split=<function split_Conway>, elements=None)
189    def __init__(self, las, split = split_Conway, elements = None):
190        super(Chemistry, self).__init__()
191        self.las = las
192        self.split = split
193        if elements == None:
194            elements = []
195        self.elements = elements
196        self._eigenvector = None
def generate_elements(self, *seeds)
230    def generate_elements(self, *seeds):
231        """
232        Collects all the persistent elements from all the look and
233        say sequences generated by the given seeds. The string(s) entered as the 
234        parameter(s) will be used as the seed(s) for generating the elements. 
235        This method will clear any elements in the chemistry that exist 
236        before this method is called (i.e. prior to collecting from the new seeds). 
237        """
238
239        self.clear_elements()
240        strings = [self.las.say_what_you_see(seed) for seed in seeds] #only look at 2-day-old strings
241        self._generate_all_elements(strings)
242        self._remove_intermittent_elements()
243        self.order_elements('string')
244        self._name_elements()

Collects all the persistent elements from all the look and say sequences generated by the given seeds. The string(s) entered as the parameter(s) will be used as the seed(s) for generating the elements. This method will clear any elements in the chemistry that exist before this method is called (i.e. prior to collecting from the new seeds).

def get_elements(self)
255    def get_elements(self):
256        """Returns the elements as a list."""
257        return self.elements

Returns the elements as a list.

def get_element(self, name)
259    def get_element(self, name):
260        """Returns the element with the given name. Returns None there is no element with the given name."""
261        for e in self.elements:
262            if e.get_name() == name:
263                return e

Returns the element with the given name. Returns None there is no element with the given name.

def clear_elements(self)
265    def clear_elements(self):
266        """Resets the list of elements back to the empty list."""
267        self.elements = []
268        self._eigenvector = None

Resets the list of elements back to the empty list.

def get_periodic_table(self, dec_places=7, abundance_sum=100)
270    def get_periodic_table(self, dec_places = 7, abundance_sum = 100):
271        """
272        Creates a periodic table including each element's name, string, relative abundance, and decay.
273        Returns the periodic table as a nested dictionary.
274
275        """
276
277        return {e.get_name() : {'string' : e.get_string(), 
278                                'abundance' : self._get_abundances(dec_places, abundance_sum)[i],
279                                'decay' : e.get_decay()}
280                                for i, e in enumerate(self.get_elements())}

Creates a periodic table including each element's name, string, relative abundance, and decay. Returns the periodic table as a nested dictionary.

def print_periodic_table(self, dec_places=7, abundance_sum=100)
282    def print_periodic_table(self, dec_places = 7, abundance_sum = 100):
283        """
284        Prints the periodic table. Note the abundances are given as percentages, 
285        so they will differ from Conway's abundances by a factor of $10^4$.
286        The parameter ``dec_places`` refers to the accuracy of the abundances.
287        """
288        pt = self.get_periodic_table(dec_places, abundance_sum)
289        elt_width = 2 + max(len('element'), max([len(e.get_name()) for e in self.get_elements()]))
290        str_width = 2 + max(len('string'), max([len(e.get_string()) for e in self.get_elements()]))
291        ab_width  = 2 + max(len('abundance'), max([len(str(prop['abundance'])) for elt, prop in pt.items()]))
292        print("{:<{elt_width}} {:<{str_width}} {:<{ab_width}} {}".format('element', 'string', 'abundance', 'decay', elt_width=elt_width, str_width=str_width, ab_width=ab_width))
293        for elt, prop in pt.items():
294            print("{:<{elt_width}} {:<{str_width}} {:<{ab_width}} {}".format(elt, prop['string'], prop['abundance'], str(prop['decay']), elt_width=elt_width, str_width=str_width, ab_width=ab_width))

Prints the periodic table. Note the abundances are given as percentages, so they will differ from Conway's abundances by a factor of $10^4$. The parameter dec_places refers to the accuracy of the abundances.

def get_decay_matrix(self)
296    def get_decay_matrix(self):
297        """
298        Returns the decay matrix as a nested list of integers 
299        (i.e. a list of the rows).
300        The order of the columns and rows correspond to the order
301        in the list of elements.
302        """
303        mat = []
304        e = self.elements
305        for i in range(len(e)):
306            row = []
307            for j in range(len(e)):
308                row.append(e[j].get_decay().count(e[i]))
309            mat.append(row)
310        return mat

Returns the decay matrix as a nested list of integers (i.e. a list of the rows). The order of the columns and rows correspond to the order in the list of elements.

def get_dom_eigenvalue(self)
312    def get_dom_eigenvalue(self):
313        """
314        Returns the maximal real eigenvalue of the decay matrix.
315        In the standard case, this will give Conway's constant. 
316        In general, this will give the growth rate of the look and say sequence. 
317        This method assumes the existence of a real eigenvalue which
318        is larger than (the absolute value) of every other eigenvalue.
319        This assumption is usually guaranteed by the Perron-Frobenius Theorem.
320        """
321        assert len(self.elements) > 0, "The get_dom_eigenvalue method requires a nonempty list of elements.\n\tTo fix: Use the generate_elements method prior to calling get_dom_eigenvalue."
322        eigenstuff = numpy.linalg.eig(numpy.array(self.get_decay_matrix()))
323        eigenvalues = eigenstuff[0]
324        return max(eigenvalues).real

Returns the maximal real eigenvalue of the decay matrix. In the standard case, this will give Conway's constant. In general, this will give the growth rate of the look and say sequence. This method assumes the existence of a real eigenvalue which is larger than (the absolute value) of every other eigenvalue. This assumption is usually guaranteed by the Perron-Frobenius Theorem.

def get_char_poly(self, factor=True, latex=False)
326    def get_char_poly(self, factor = True, latex = False):
327        """
328        Returns the characteristic polynomial of the decay matrix using sympy.
329        By default the returned polynomial will be factored. 
330        Use ``factor = False`` to get the expanded (i.e. unfactored) polynomial. 
331        Use ``latex = True`` to return the polynomial formatted in latex.
332        """
333        chi = sympy.Matrix(self.get_decay_matrix()).charpoly()
334        if factor:
335            chi = sympy.factor(chi.as_expr())
336        else:
337            chi = chi.as_expr()
338        if latex:
339            return sympy.latex(chi)
340        else:
341            return chi

Returns the characteristic polynomial of the decay matrix using sympy. By default the returned polynomial will be factored. Use factor = False to get the expanded (i.e. unfactored) polynomial. Use latex = True to return the polynomial formatted in latex.

def order_elements(self, order_on, key=None, reverse=False, rename=True)
368    def order_elements(self, order_on, key = None, reverse = False, rename = True):
369        """
370        Reorders the list of elements depending on the parameter ``order_on`` as follows:
371
372        * ``order_on='abundance'``: Orders elements from highest abundance to lowest.
373        * ``order_on='string'``: Orders elements according to the lexicographic order of their strings.
374        * ``order_on='string length'``: Orders elements according to the lengths of their strings from shortest to longest.
375        * ``order_on='name'``: Orders elements alphabetically according to their names.
376        * ``order_on='key'``: Orders elements according to the function specified by the parameter ``key``.
377
378        You can reverse the ordering above by passing the extra parameter ``reverse = True``.
379        
380        By default this method will automatically rename the elements according to their new order.
381        This will not happen if the elements are named via Conway or if the parameter ``rename = False`` is passed.
382        """
383        assert order_on in ['abundance', 'name', 'string', 'string length', 'key'], "Invalid parameter passed to order_elements. Valid parameter are 'abundance', 'name', 'string', 'string length', and 'key'."
384        pt = self.get_periodic_table()
385        sorted_key = {
386            'abundance': lambda e : pt[e.get_name()]['abundance'],
387            'name': lambda e : e.get_name(),
388            'string': lambda e : e.get_string(),
389            'string length': lambda e : len(e.get_string()),
390            'key': key
391        }
392        self.elements = sorted(self.get_elements(), key = sorted_key[order_on])
393        if order_on == 'abundance':
394            self.elements.reverse()
395        if reverse:
396            self.elements.reverse()
397        if not self.las._is_Conway and rename:
398            self._name_elements()

Reorders the list of elements depending on the parameter order_on as follows:

  • order_on='abundance': Orders elements from highest abundance to lowest.
  • order_on='string': Orders elements according to the lexicographic order of their strings.
  • order_on='string length': Orders elements according to the lengths of their strings from shortest to longest.
  • order_on='name': Orders elements alphabetically according to their names.
  • order_on='key': Orders elements according to the function specified by the parameter key.

You can reverse the ordering above by passing the extra parameter reverse = True.

By default this method will automatically rename the elements according to their new order. This will not happen if the elements are named via Conway or if the parameter rename = False is passed.

class BinaryChemistry(Chemistry):
400class BinaryChemistry(Chemistry):
401    """
402    A chemistry for binary look and say sequences that split as 1.0 
403    (i.e. whenever a 1 is left of a 0). This chemistry is valid whenever
404    the say-what-you-see operation maps 
405    $d^n$ to $[n]d$ where $[n]$ is a binary representation of n
406    that always starts with a 1. For example, this chemistry is 
407    valid for standard base two binary look and say sequences. 
408    """
409    def __init__(self, las, elements = None):
410        sff = SplitFuncFactory()
411        sff.declare_split_before('1')
412        binary_split = sff.get_split()
413        super().__init__(las, binary_split, elements)

A chemistry for binary look and say sequences that split as 1.0 (i.e. whenever a 1 is left of a 0). This chemistry is valid whenever the say-what-you-see operation maps $d^n$ to $[n]d$ where $[n]$ is a binary representation of n that always starts with a 1. For example, this chemistry is valid for standard base two binary look and say sequences.

BinaryChemistry(las, elements=None)
409    def __init__(self, las, elements = None):
410        sff = SplitFuncFactory()
411        sff.declare_split_before('1')
412        binary_split = sff.get_split()
413        super().__init__(las, binary_split, elements)
class Element:
417class Element():
418    """
419    An element consists of a string (usually a chunk of digits) and 
420    a name. For example, in Conway's chemistry there is an element
421    named H (short for Hydrogen) consisting of the string '22'. 
422    Each element decays into a list of other elements. 
423    The only methods for this class are getters and a setter.
424    """
425    def __init__(self, string, las, decay = []):
426        super(Element, self).__init__()
427        self.string = string
428        self.las = las
429        self.name = string
430        self.decay = decay
431
432    def __str__(self):
433        return self.name
434
435    def __repr__(self):
436        return self.name
437
438    def __eq__(self, other):
439        """Overrides the default implementation"""
440        if isinstance(other, Element):
441                return self.string == other.string and self.las == other.las
442        return NotImplemented
443
444    def __hash__(self):
445        """Overrides the default implementation"""
446        return hash((self.string, self.las))
447
448    def _set_decay(self, elements):
449        self.decay = elements
450
451    def get_decay(self):
452        return self.decay
453
454    def set_name(self, name):
455        self.name = name
456
457    def get_name(self):
458        return self.name
459
460    def get_string(self):
461        return self.string

An element consists of a string (usually a chunk of digits) and a name. For example, in Conway's chemistry there is an element named H (short for Hydrogen) consisting of the string '22'. Each element decays into a list of other elements. The only methods for this class are getters and a setter.

Element(string, las, decay=[])
425    def __init__(self, string, las, decay = []):
426        super(Element, self).__init__()
427        self.string = string
428        self.las = las
429        self.name = string
430        self.decay = decay
def get_decay(self)
451    def get_decay(self):
452        return self.decay
def set_name(self, name)
454    def set_name(self, name):
455        self.name = name
def get_name(self)
457    def get_name(self):
458        return self.name
def get_string(self)
460    def get_string(self):
461        return self.string
class SplitFuncFactory:
465class SplitFuncFactory():
466    """
467    A class to help create a split function. The split function factory
468    can produce a split function via any combination of the following:
469
470    * Specifying specific chunks L and R such that LR splits as L.R.
471    * Specifying specific characters or chunks to always split before or after.
472    """
473    def __init__(self):
474        self._splitting_pairs = []
475        self._chunks_before_split = []
476        self._chunks_after_split = []
477
478    def get_split(self):
479        """Returns the split function."""
480        return lambda string : self._split(string)
481
482    def _split(self, string):
483        chunks = []
484        start = 0
485        for i in range(1, len(string)):
486            if self._is_split(string[start:i], string[i:]):
487                chunks.append(string[start:i])
488                start = i
489        chunks.append(string[start:])
490        return chunks
491
492    def _is_split(self, L, R):
493        if L == '' or R == '':
494            return True # pragma: no cover
495        if L[-1] == R[0]:
496            return False
497        for l in self._chunks_before_split:
498            if len(l) <= len(L) and l == L[-len(l):]:
499                return True
500        for r in self._chunks_after_split:
501            if len(r) <= len(R) and r == R[:len(r)]:
502                return True
503        for l, r in self._splitting_pairs:
504            if len(l) <= len(L) and len(r) <= len(R) and l == L[-len(l):] and r == R[:len(r)]:
505                return True
506        return False
507
508    def declare_splitting_pairs(self, *pairs):
509        """
510        Specify pairs of chunks in the form (L, R) 
511        such that LR always splits as L.R.
512        """
513        for pair in pairs:
514            self._splitting_pairs.append(pair)
515
516    def declare_split_after(self, *chunks):
517        """
518        Specify chunks L such that LR splits for every possible R (assuming the last character of L and the first character of R are distinct).
519        """
520        for chunk in chunks:
521            self._chunks_before_split.append(chunk)
522
523    def declare_split_before(self, *chunks):
524        """
525        Specify chunks R such that LR splits for every possible L (assuming the last character of L and the first character of R are distinct).
526        """
527        for chunk in chunks:
528            self._chunks_after_split.append(chunk)

A class to help create a split function. The split function factory can produce a split function via any combination of the following:

  • Specifying specific chunks L and R such that LR splits as L.R.
  • Specifying specific characters or chunks to always split before or after.
SplitFuncFactory()
473    def __init__(self):
474        self._splitting_pairs = []
475        self._chunks_before_split = []
476        self._chunks_after_split = []
def get_split(self)
478    def get_split(self):
479        """Returns the split function."""
480        return lambda string : self._split(string)

Returns the split function.

def declare_splitting_pairs(self, *pairs)
508    def declare_splitting_pairs(self, *pairs):
509        """
510        Specify pairs of chunks in the form (L, R) 
511        such that LR always splits as L.R.
512        """
513        for pair in pairs:
514            self._splitting_pairs.append(pair)

Specify pairs of chunks in the form (L, R) such that LR always splits as L.R.

def declare_split_after(self, *chunks)
516    def declare_split_after(self, *chunks):
517        """
518        Specify chunks L such that LR splits for every possible R (assuming the last character of L and the first character of R are distinct).
519        """
520        for chunk in chunks:
521            self._chunks_before_split.append(chunk)

Specify chunks L such that LR splits for every possible R (assuming the last character of L and the first character of R are distinct).

def declare_split_before(self, *chunks)
523    def declare_split_before(self, *chunks):
524        """
525        Specify chunks R such that LR splits for every possible L (assuming the last character of L and the first character of R are distinct).
526        """
527        for chunk in chunks:
528            self._chunks_after_split.append(chunk)

Specify chunks R such that LR splits for every possible L (assuming the last character of L and the first character of R are distinct).

class Cosmology:
532class Cosmology():
533    '''
534    A class for proving Conway's Cosmological Theorem.
535    Currently this will only prove The Cosmological Theorem 
536    for the standard decimal look and say sequences where every term
537    consists of strings of some of the digits 1, 2, and 3.  
538    '''
539    def __init__(self, digits = '123'):
540        self.las = LookAndSay()
541        self.split = split_Conway
542        self.digits = digits
543        self._compendium_sets = []
544        self.common_strings = {elt for elt in _CONWAY_ELEMENTS}
545        # add transuranic elements to the list of common strings:
546        for digit in self.digits:
547            if digit not in '123':
548                self.common_strings.add('31221132221222112112322211' + digit)
549                self.common_strings.add('1311222113321132211221121332211' + digit)
550
551    def days_exotic(self, string):
552        '''
553        Returns the number of days until the string splits into a compound of common elements. 
554        '''
555        atoms = [atom for atom in self.split(string) if atom not in self.common_strings]
556        days = 0
557        while atoms != []:
558            next_atoms = []
559            for atom in atoms:
560                new_atoms = self.split(self.las.say_what_you_see(atom))
561                next_atoms += [a for a in new_atoms if a not in self.common_strings]
562            atoms = next_atoms
563            days += 1
564        return days
565
566    def decay_tree(self, string):
567        '''
568        Starting with the passed string, the say_what_you_see operation is applied
569        repeatedly until the result splits as a compound of common elements. 
570        Returns a nested dictionary corresponding to the resulting *decay tree*: 
571        The root of the tree is the passed string, the children of a node are the 
572        atoms obtained by applying the say_what_you_see operation to the node and 
573        splitting the result, the leaves are the nodes corresponding to common elements.
574        '''
575        if string in self.common_strings:
576            return string
577        return {string: [self.decay_tree(atom) for atom in self.split(self.las.say_what_you_see(string))]}
578
579
580    def proof(self, day = 9):
581        '''
582        Uses a backtracking algorithm to prove Conway's Cosmological Theorem. If we pass the parameter
583        ``day = N`` the algorithm searches for all strings that might appear as chunks of an N-day
584        old element. The search starts with strings of length 1 (i.e. the digits) and then 
585        searches for strings of length 2, then length 3, etc. For each string found in the search, 
586        the algorithm repeatedly applies the say-what-you-see operation until the result is a 
587        compound of common elements. The algorithm terminates when for some positive integer L, 
588        there are no strings of length L that can appear as chunks of an N-day old element, and 
589        all strings of length less than L which might appear as a chunk of an N-day old element 
590        are shown to eventually decay into a compound of common elements.
591
592        Running the program prints a few details about the search. In particular, an upper bound
593        for the age of an exotic (i.e. not common) element is displayed.  
594
595        The default parameter is ``day = 9``, which results in a proof of the Cosmological Theorem
596        that gives an upper bound of 27 for the age of an exotic element. 
597
598        The proof is essentially the same as that of Zeilberger. 
599        The implementation is similar to that of Litherland.
600        '''
601        chunks = self.digits # start with length 1 chunks
602        max_days_exotic = 0
603
604        print(f'To prove the Cosmological Theorem we search for all strings\nwhich could appear as chunks of {day} day old elements.\nSearching...')
605        length = 0
606        while True:
607            length += 1
608            compendium = []
609            #Gather all chunks that have a grandparent to the compendium
610            for chunk in chunks:
611                if self._has_grandparent(chunk, day):
612                    compendium.append(chunk)
613
614            if compendium == []:
615                print(f'There are no strings of length {length} that can appear as chunks\nof {day} day old elements. All strings of length less than {length}\nthat could appear after {day} days decay into compounds of common\nelements after an additional {max_days_exotic-day} days. This gives an upper\nbound of {max_days_exotic} days for the age of an exotic element.\nQ.E.D.')
616                return max_days_exotic
617
618            self._compendium_sets.append(set(compendium))
619
620            #Compute an upper bound on the maximum longevity of an exotic element:
621            for chunk in compendium:
622                lifespan = day + self.days_exotic(chunk)
623                if lifespan > max_days_exotic:
624                    max_days_exotic = lifespan
625
626            chunks = []
627            #For each found above, add all possible digits to the left and check if splits
628            for digit in self.digits:
629                chunks += [digit+chunk for chunk in compendium if len(self.split(digit+chunk)) == 1]
630
631    def _parents(self, kid):
632        '''
633        We call a string a *parent* of the kid if applying the say-what-you-see operation
634        results in a string which contains the kid as a substring. This function returns
635        a list of all the minimal parents of kid (here minimal means that every parent of
636        the kid will contain one of the elements of the list as a substring).
637        '''
638        parents = []
639        if self._is_day_one_even(kid):
640            parents += self._even_parents(kid)
641        if self._is_day_one_odd(kid):
642            parents += self._odd_parents(kid)
643        return parents
644
645    def _is_day_one_odd(self, string):
646        if len(string) == 1:
647            return False
648        if len(string) % 2 == 0:
649            i = 0
650        else:
651            i = 1
652        while i < len(string) - 2:
653            if string[i] == string[i+2]:
654                return False
655            i += 2
656        return True
657
658    def _is_day_one_even(self, string):
659        if len(string) == 1:
660            return True
661        if len(string) % 2 == 0:
662            i = 1
663        else:
664            i = 0
665        while i < len(string) - 2:
666            if string[i] == string[i+2]:
667                return False
668            i += 2
669        return True
670
671    def _even_parents(self, string):
672        new_string = ''
673        if len(string) % 2 == 0:
674            start = 0
675        else:
676            start = 1
677            new_string += string[0]
678        while start < len(string) - 1:
679            new_string += int(string[start]) * string[start+1]
680            start += 2
681        return [new_string]
682
683    def _odd_parents(self, string):
684        new_string = ''
685        if len(string) % 2 == 0:
686            start = 1
687            new_string += string[0]
688        else:
689            start = 0
690        while start < len(string) - 2:
691            new_string += int(string[start]) * string[start+1]
692            start += 2
693        return [new_string + int(string[-1]) * digit for digit in self.digits if digit != string[-2]]
694
695
696    def _has_grandparent(self, kid, day):
697        '''
698        Returns True if the string kid *might* be contained in the result of applying
699        the say-what-you-see operation day-times to some string. Note that this method
700        will only return False when kid cannot be part of a day-old descendant of 
701        any string, but may return True even if kid is not contained in any day-old descendant.
702        '''
703        # Apply the parents function day-times and see what we get:
704        ancestors = [kid]
705        for _ in range(day):
706            next_ancestors = []
707            for a in ancestors:
708                next_ancestors += self._parents(a)
709            ancestors = next_ancestors
710
711        return len(ancestors) != 0

A class for proving Conway's Cosmological Theorem. Currently this will only prove The Cosmological Theorem for the standard decimal look and say sequences where every term consists of strings of some of the digits 1, 2, and 3.

Cosmology(digits='123')
539    def __init__(self, digits = '123'):
540        self.las = LookAndSay()
541        self.split = split_Conway
542        self.digits = digits
543        self._compendium_sets = []
544        self.common_strings = {elt for elt in _CONWAY_ELEMENTS}
545        # add transuranic elements to the list of common strings:
546        for digit in self.digits:
547            if digit not in '123':
548                self.common_strings.add('31221132221222112112322211' + digit)
549                self.common_strings.add('1311222113321132211221121332211' + digit)
def days_exotic(self, string)
551    def days_exotic(self, string):
552        '''
553        Returns the number of days until the string splits into a compound of common elements. 
554        '''
555        atoms = [atom for atom in self.split(string) if atom not in self.common_strings]
556        days = 0
557        while atoms != []:
558            next_atoms = []
559            for atom in atoms:
560                new_atoms = self.split(self.las.say_what_you_see(atom))
561                next_atoms += [a for a in new_atoms if a not in self.common_strings]
562            atoms = next_atoms
563            days += 1
564        return days

Returns the number of days until the string splits into a compound of common elements.

def decay_tree(self, string)
566    def decay_tree(self, string):
567        '''
568        Starting with the passed string, the say_what_you_see operation is applied
569        repeatedly until the result splits as a compound of common elements. 
570        Returns a nested dictionary corresponding to the resulting *decay tree*: 
571        The root of the tree is the passed string, the children of a node are the 
572        atoms obtained by applying the say_what_you_see operation to the node and 
573        splitting the result, the leaves are the nodes corresponding to common elements.
574        '''
575        if string in self.common_strings:
576            return string
577        return {string: [self.decay_tree(atom) for atom in self.split(self.las.say_what_you_see(string))]}

Starting with the passed string, the say_what_you_see operation is applied repeatedly until the result splits as a compound of common elements. Returns a nested dictionary corresponding to the resulting decay tree: The root of the tree is the passed string, the children of a node are the atoms obtained by applying the say_what_you_see operation to the node and splitting the result, the leaves are the nodes corresponding to common elements.

def proof(self, day=9)
580    def proof(self, day = 9):
581        '''
582        Uses a backtracking algorithm to prove Conway's Cosmological Theorem. If we pass the parameter
583        ``day = N`` the algorithm searches for all strings that might appear as chunks of an N-day
584        old element. The search starts with strings of length 1 (i.e. the digits) and then 
585        searches for strings of length 2, then length 3, etc. For each string found in the search, 
586        the algorithm repeatedly applies the say-what-you-see operation until the result is a 
587        compound of common elements. The algorithm terminates when for some positive integer L, 
588        there are no strings of length L that can appear as chunks of an N-day old element, and 
589        all strings of length less than L which might appear as a chunk of an N-day old element 
590        are shown to eventually decay into a compound of common elements.
591
592        Running the program prints a few details about the search. In particular, an upper bound
593        for the age of an exotic (i.e. not common) element is displayed.  
594
595        The default parameter is ``day = 9``, which results in a proof of the Cosmological Theorem
596        that gives an upper bound of 27 for the age of an exotic element. 
597
598        The proof is essentially the same as that of Zeilberger. 
599        The implementation is similar to that of Litherland.
600        '''
601        chunks = self.digits # start with length 1 chunks
602        max_days_exotic = 0
603
604        print(f'To prove the Cosmological Theorem we search for all strings\nwhich could appear as chunks of {day} day old elements.\nSearching...')
605        length = 0
606        while True:
607            length += 1
608            compendium = []
609            #Gather all chunks that have a grandparent to the compendium
610            for chunk in chunks:
611                if self._has_grandparent(chunk, day):
612                    compendium.append(chunk)
613
614            if compendium == []:
615                print(f'There are no strings of length {length} that can appear as chunks\nof {day} day old elements. All strings of length less than {length}\nthat could appear after {day} days decay into compounds of common\nelements after an additional {max_days_exotic-day} days. This gives an upper\nbound of {max_days_exotic} days for the age of an exotic element.\nQ.E.D.')
616                return max_days_exotic
617
618            self._compendium_sets.append(set(compendium))
619
620            #Compute an upper bound on the maximum longevity of an exotic element:
621            for chunk in compendium:
622                lifespan = day + self.days_exotic(chunk)
623                if lifespan > max_days_exotic:
624                    max_days_exotic = lifespan
625
626            chunks = []
627            #For each found above, add all possible digits to the left and check if splits
628            for digit in self.digits:
629                chunks += [digit+chunk for chunk in compendium if len(self.split(digit+chunk)) == 1]

Uses a backtracking algorithm to prove Conway's Cosmological Theorem. If we pass the parameter day = N the algorithm searches for all strings that might appear as chunks of an N-day old element. The search starts with strings of length 1 (i.e. the digits) and then searches for strings of length 2, then length 3, etc. For each string found in the search, the algorithm repeatedly applies the say-what-you-see operation until the result is a compound of common elements. The algorithm terminates when for some positive integer L, there are no strings of length L that can appear as chunks of an N-day old element, and all strings of length less than L which might appear as a chunk of an N-day old element are shown to eventually decay into a compound of common elements.

Running the program prints a few details about the search. In particular, an upper bound for the age of an exotic (i.e. not common) element is displayed.

The default parameter is day = 9, which results in a proof of the Cosmological Theorem that gives an upper bound of 27 for the age of an exotic element.

The proof is essentially the same as that of Zeilberger. The implementation is similar to that of Litherland.