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
Stuttering look and say sequences and a challenger to Conway's most complicated algebraic number from the silliest source (2022) preprint on the arXiv.
D3 visualizations of Conway's chemistry:
A D3 force graph showing the decay of Conway's elements.
A D3 collapsable tree showing the descendants of Methuselum.
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
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'].
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.
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'
.
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.
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
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.
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
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.
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).
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.
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.
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.
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.
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.
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.
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.
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 parameterkey
.
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.
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.
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.
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.
478 def get_split(self): 479 """Returns the split function.""" 480 return lambda string : self._split(string)
Returns the split function.
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.
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).
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).
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.
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)
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.
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.
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.