De mens is al eeuwenlang op zoek naar geheime boodschappen die verborgen zitten in boeken, achterwaarts afgespeelde muziek, vreemd uitziende plateau's op Mars, of andere dingen. Sommigen geloven bijvoorbeeld stellig dat er verborgen boodschappen in de Bijbel zitten die niet op toeval kunnen berusten — ze moeten er wel moedwillig in aangebracht zijn door niemand minder dan God de Vader zelf.

De zoektocht naar verborgen boodschappen werd nieuw leven ingeblazen door de publicatie van het boek The Bible Code. Het is geschreven door journalist Michael Drosnin, die beweert dat de Hebreeuwse Bijbel een zeer complexe code bevat die gebeurtenissen onthult die plaatsvonden duizenden jaren nadat de Bijbel werd geschreven. Volgens Drosnin is het dan ook logisch dat er in de Bijbel boodschappen verborgen moeten zitten die iets te vertellen hebben over onze toekomst.

cover van boek over bijbelcodes artikel van The Sun over bijbelcodes

Om verborgen boodschappen te vinden, maakt Drosnin gebruik van een eenvoudige techniek: start bij een bepaalde letter in een tekst, en spring daarna telkens een vast aantal letters vooruit of achteruit. Op die manier valt bijvoorbeeld in bijbelvers Genesis 31:28 (King James versie) "And hast not suffered me to kiss my sons and my daughters? Thou hast now done foolishly in so doing." het woord Roswell te lezen door te starten vanaf de R in daughters, vier letters vooruit te springen naar de O in thou, nog vier letters vooruit te springen naar de S in hast, enzoverder. In datzelfde vers lees je ook het woord UFO door te starten vanaf de U in thou, en daarna telkens 12 letters vooruit te springen. En dat kan toch zeker geen toeval zijn?

Genesis 31:28 UFO en Roswell

De bewering dat de verborgen boodschappen in de Bijbel onmogelijk aan toeval toe te schrijven zijn, werd echter vanuit allerlei hoeken in vraag gesteld. Sommige critici van Drosnin zeggen dat de journalist niets anders doet dan data mining: in elke willekeurige tekst vallen met de gebruikte techniek en een gezonde dosis creativiteit een groot aantal verborgen boodschappen te vinden. Michael Drosnin reageerde in Newsweek op de kritiek door te stellen dat "When my critics find a message about the assassination of a prime minister encrypted in Moby Dick, I'll believe them". Wiskundige Brendan McKay van de Australian University nam de handschoen op, en toonde aan dat dat helemaal geen probleem bleek te zijn.

Opgave

Schrijf een programma waarmee individuele eiwitten en volledige proteomen kunnen gescand worden op voorkomens van woorden volgens de techniek van Michael Drosnin.

Eiwitten bestaan uit polymere ketens van aminozuren. De eiwitten die aangemaakt worden in cellen van levende organismen zijn opgebouwd uit 20 verschillende aminozuren, die elk voorgesteld worden door een hoofdletter. Omdat eiwitten op die manier net als woorden kunnen voorgesteld worden als een reeks hoofdletters, kunnen we de methode van Drosnin toepassen om op zoek te gaan naar woorden die verborgen zitten in eiwittten. De zes hoofdletters B, J, O, U, X en Z kunnen in een woord voorkomen, maar corresponderen niet met een aminozuur dat in een eiwit voorkomt. Ze corresponderen echter met één of meer aminozuren. Onderstaande tabel geeft bijvoorbeeld aan dat hoofletter B correspondeert met aminozuur D (aspartate) of N (asparagine), hoofletter J correspondeert met het aminozuur I (isoleucine), en hoofletter O correspondeert elk van de 20 aminozuren.

letter aminozuren
B D of N
J I
O elk van de 20 aminozuren
U V
X elk van de 20 aminozuren
Z E of Q

De Latijnse spreuk alea iacta est (de teerling is geworpen) kennen we vooral omdat ze in 49 voor Christus door Julius Caesar werd uitgesproken, toen die met een staand leger de Rubicon overstak om een staatsgreep te plegen in Rome. De woorden van deze zin zitten bijvoorbeeld verborgen in het menselijk eiwit

HGLAVPFRTTHPSLECGRTSWARWSLDIAEFWLAWEASDCITDEDTKFQGDAVVAQM

dat deel uitmaakt van een complexe reeks eiwitten die er samen voor zorgen dat we kunnen ruiken. Als we starten op positie 21, en telkens 4 posities vooruit springen, dan lezen we het woord ALEA. Hetzelfde woord lees je door op dezelfde positie te starten, en telkens 11 posities vooruit te springen. Je kunt ook starten op positie 36, en telkens 11 posities achteruit springen om het woord ALEA nog een derde keer terug te vinden in het eiwit. Onderstaande tabel geeft aan dat er in het eiwit ook nog twee voorkomens van het woord IACTA en twee voorkomens van het woord EST te vinden zijn.

start stap lengte woord
0 1 57 HGLAVPFRTTHPSLECGRTSWARWSLDIAEFWLAWEASDCITDEDTKFQGDAVVAQM
21 4 4                      A   L   E   A                       
21 11 4                      A          L          E          A  
36 -11 4    A          E          L          A                    
27 -6 5    A     T     C     A     I                             
27 6 5                            I     A     C     T     A     
29 -10 3          T         S         E                           
29 8 3                              E       S       T           

Definieer een klasse ProteinScanner die kan gebruikt worden om alle voorkomens van geldige woorden terug te vinden in een collectie eiwitten, volgens de methode van Michael Drosnin. Bij het aanmaken van een nieuwe scanner (ProteinScanner) moeten twee argumenten doorgegeven worden: i) de padnaam (str) van een FASTA-bestand dat een collectie eiwitten bevat, en ii) de padnaam (str) van een tekstbestand dat de geldige woorden bevat. In het FASTA-bestand bevat elke header line — die start met een groter dan teken (>) — het accession number (de unieke identificatiecode) van het eiwit op de daaropvolgende regels. De aminozuren van een eiwit worden altijd als hoofdletters voorgesteld. Elk woord bestaat enkel uit hoofdletters en staat op een afzonderlijke regel.

Op een scanner (ProteinScanner) moet je minstens de volgende methoden kunnen aanroepen:

Voorbeeld

In deze interactieve sessie gaan we ervan uit dat de tekstbestanden proteins.fasta en words.txt in de huidige directory staan.

>>> scanner = ProteinScanner('proteins.fasta', 'words.txt')
>>> scanner.scan_protein('protein1')
{(21, 11, 'ALEA'), (45, -2, 'TEX'), (1, 17, 'GTE'), (41, -6, 'TEX'), (29, -10, 'EST'), (37, 2, 'SCOOT'), (8, 6, 'TEX'), (18, 11, 'TEX'), (9, 5, 'TEX'), (41, -12, 'TEX'), (18, 10, 'TAB'), (29, -5, 'ESSEX'), (45, -10, 'TEX'), (10, 7, 'HRS'), (27, -6, 'IACTA'), (27, 6, 'IACTA'), (9, 20, 'TEX'), (14, 5, 'ESSEX'), (45, -16, 'TEX'), (29, 8, 'EST'), (8, 21, 'TEX'), (41, 2, 'TEX'), (21, 4, 'ALEA'), (18, 17, 'TEX'), (36, -11, 'ALEA'), (18, -4, 'TEX')}
>>> scanner.scan_protein('protein1', min_word_length=4)
{(29, -5, 'ESSEX'), (27, -6, 'IACTA'), (14, 5, 'ESSEX'), (37, 2, 'SCOOT'), (27, 6, 'IACTA'), (21, 11, 'ALEA'), (21, 4, 'ALEA'), (36, -11, 'ALEA')}
>>> scanner.scan_protein('protein1', min_word_length=5)
{(29, -5, 'ESSEX'), (27, -6, 'IACTA'), (37, 2, 'SCOOT'), (27, 6, 'IACTA'), (14, 5, 'ESSEX')}
>>> scanner.scan_protein('protein1', min_word_length=6)
set()

>>> scanner.scan_proteins()
{'protein1': {(21, 11, 'ALEA'), (45, -2, 'TEX'), (1, 17, 'GTE'), (41, -6, 'TEX'), (29, -10, 'EST'), (37, 2, 'SCOOT'), (8, 6, 'TEX'), (18, 11, 'TEX'), (9, 5, 'TEX'), (41, -12, 'TEX'), (18, 10, 'TAB'), (29, -5, 'ESSEX'), (45, -10, 'TEX'), (10, 7, 'HRS'), (27, -6, 'IACTA'), (27, 6, 'IACTA'), (9, 20, 'TEX'), (14, 5, 'ESSEX'), (45, -16, 'TEX'), (29, 8, 'EST'), (8, 21, 'TEX'), (41, 2, 'TEX'), (21, 4, 'ALEA'), (18, 17, 'TEX'), (36, -11, 'ALEA'), (18, -4, 'TEX')}, 'protein2': {(76, -38, 'TEX'), (7, 38, 'TEX'), (65, -20, 'TEX'), (65, 14, 'TEX'), (76, 3, 'TEX'), (65, -7, 'TEX'), (4, 9, 'API'), (73, -2, 'PROLOG'), (6, 26, 'LEE'), (61, 12, 'API'), (76, -18, 'TEX'), (0, 4, 'MASON'), (76, -2, 'TEX'), (65, 3, 'TEX'), (7, 31, 'TEX'), (38, 19, 'EST'), (65, -27, 'TEX'), (88, 2, 'LEE'), (7, 25, 'TEX'), (65, 9, 'TEX'), (8, 5, 'SPOOK'), (47, 6, 'KYOTO'), (76, -31, 'TEX'), (76, -8, 'TEX'), (76, 14, 'TEX')}, 'protein3': {(32, -10, 'API'), (34, -7, 'TAB'), (65, -11, 'TEX'), (7, 34, 'TEX'), (74, -20, 'TEX'), (55, 3, 'GIBSON'), (65, -24, 'TEX'), (16, 25, 'TEX'), (54, 10, 'EST'), (78, -11, 'SNOUT'), (34, 20, 'TEX'), (34, 7, 'TEX'), (74, -33, 'TEX'), (82, -2, 'PROLOG')}, 'protein4': {(82, -40, 'TEX'), (82, -29, 'TEX'), (83, -32, 'TEX'), (105, -52, 'TEX'), (87, -8, 'TEX'), (106, -29, 'TEX'), (106, -53, 'TEX'), (68, 11, 'TEX'), (68, 22, 'TEX'), (58, -2, 'SPOOK'), (82, -5, 'TEX'), (83, -41, 'TEX'), (25, 9, 'TEX'), (82, 6, 'TEX'), (105, -17, 'TEX'), (34, 24, 'EST'), (106, -17, 'TAB'), (95, 5, 'TEX'), (106, -18, 'TEX'), (83, -4, 'TEX'), (83, 7, 'TEX'), (105, -1, 'TEX'), (81, -34, 'TEX'), (81, -28, 'TEX'), (113, -13, 'RETOOK'), (2, 33, 'NIT'), (76, -29, 'TEX'), (25, 26, 'TEX'), (97, -9, 'LEE'), (81, -39, 'TEX'), (81, 7, 'TEX'), (68, -21, 'TEX'), (87, -36, 'TEX'), (59, -6, 'LEE'), (105, -5, 'TEX'), (17, 30, 'LEE'), (95, -5, 'TEX'), (95, -16, 'TEX'), (81, 9, 'TEX'), (83, -36, 'TEX'), (106, -6, 'TEX'), (81, -4, 'TEX'), (87, -34, 'TEX'), (47, 17, 'EST'), (68, -26, 'TEX'), (68, -15, 'TEX'), (87, 3, 'TEX'), (76, -23, 'TEX'), (76, -34, 'TEX'), (76, 1, 'TEX'), (76, 12, 'TEX'), (82, -31, 'TEX'), (51, -13, 'EST'), (82, 8, 'TEX'), (68, 20, 'TEX'), (83, -30, 'TEX'), (68, 9, 'TEX'), (105, -28, 'TEX'), (111, -8, 'NIT'), (76, 3, 'TEX'), (6, 47, 'LEE'), (73, 3, 'GTE'), (39, -7, 'EST'), (86, -2, 'NIT'), (76, -37, 'TEX'), (82, -3, 'TEX'), (83, -6, 'TEX'), (25, 22, 'TEX'), (83, 5, 'TEX'), (105, -26, 'TEX'), (105, -15, 'TEX'), (102, 1, 'GIOTTO'), (68, -29, 'TEX'), (106, -27, 'TEX'), (106, -16, 'TEX'), (102, -7, 'GTE'), (87, -40, 'TEX'), (95, 9, 'TEX'), (25, 17, 'TEX'), (87, 13, 'TEX'), (95, -42, 'TEX'), (81, -30, 'TEX'), (68, -34, 'TEX'), (102, -34, 'GTE'), (95, -44, 'TEX'), (81, -2, 'TEX'), (7, 35, 'LEE'), (76, -25, 'TEX'), (25, 28, 'TEX'), (95, -18, 'TEX'), (17, 17, 'LEE'), (6, 41, 'LEE'), (104, -11, 'EST'), (23, 28, 'LEE'), (2, 52, 'NIT'), (95, -7, 'TEX'), (82, -35, 'TEX'), (16, 37, 'LEE'), (68, -17, 'TEX'), (106, -2, 'TEX'), (87, -10, 'TEX'), (87, 1, 'TEX'), (25, 14, 'TEX'), (76, 14, 'TEX')}, 'protein5': {(108, -36, 'TEX'), (76, -17, 'TEX'), (13, 46, 'TEX'), (61, -2, 'LEE'), (87, -31, 'LEE'), (94, 13, 'TEX'), (17, 3, 'SPOOK'), (76, -30, 'TEX'), (76, -4, 'TEX'), (94, -38, 'TEX'), (13, 59, 'TEX'), (74, -18, 'LEE'), (2, 44, 'TEX'), (76, 31, 'TEX'), (102, 2, 'PISTOL'), (98, -48, 'NIT'), (93, -36, 'TEX'), (105, -48, 'TEX'), (127, -55, 'TEX'), (108, -52, 'TEX'), (107, -1, 'EST'), (93, -34, 'TEX'), (62, -3, 'LEE'), (13, 43, 'TEX'), (13, 44, 'TEX'), (80, -21, 'LEE'), (2, 55, 'TEX'), (2, 57, 'TEX'), (91, 11, 'API'), (76, -20, 'TEX'), (127, -20, 'TEX'), (110, -22, 'KABOOM'), (93, -21, 'TEX'), (8, 12, 'API'), (105, -33, 'TEX'), (105, -46, 'TEX'), (13, 12, 'TEX'), (76, -38, 'TEX'), (108, -1, 'TEX'), (105, 2, 'TEX'), (13, 25, 'TEX'), (93, 14, 'TEX'), (76, 12, 'TAB'), (127, -41, 'TAB'), (106, -4, 'SPOOK'), (94, -35, 'TEX'), (93, -37, 'TEX'), (105, -49, 'TEX'), (124, -52, 'TEX'), (79, 23, 'API'), (124, -17, 'TEX'), (108, -49, 'TEX'), (87, -15, 'LEE'), (2, 54, 'TEX'), (94, -37, 'TEX'), (2, 23, 'TEX'), (2, 36, 'TEX'), (94, -22, 'TEX'), (76, -19, 'TEX'), (108, -51, 'TEX'), (13, 33, 'TEX')}}
>>> scanner.scan_proteins(min_word_length=6)
{'protein1': set(), 'protein2': {(73, -2, 'PROLOG')}, 'protein3': {(82, -2, 'PROLOG'), (55, 3, 'GIBSON')}, 'protein4': {(113, -13, 'RETOOK'), (102, 1, 'GIOTTO')}, 'protein5': {(110, -22, 'KABOOM'), (102, 2, 'PISTOL')}}

Bronnen