Univerzita Karlova v Praze Matematicko-fyzikální fakulta BAKALÁŘSKÁ PRÁCE Vojtěch Šípek Vizuální dotazování v chemických databázích pomocí SMARTS vzorů Katedra softwarového inženýrství Vedoucí bakalářské práce: Studijní program: Studijní obor: RNDr. David Hoksza, Ph.D. Informatika Programování Praha 2014
Na tomto místě bych rád poděkoval vedoucímu práce RNDr. Davidu Hokszovi, Ph.D. za čas a cenné rady, které mi při psaní této práce věnoval. Dále bych chtěl poděkovat mojí rodině za podporu ve studiu a mé přítelkyni za trpělivost.
Prohlašuji, že jsem tuto bakalářskou práci vypracoval samostatně a výhradně s použitím citovaných pramenů, literatury a dalších odborných zdrojů. Beru na vědomí, že se na moji práci vztahují práva a povinnosti vyplývající ze zákona č. 121/2000 Sb., autorského zákona v platném znění, zejména skutečnost, že Univerzita Karlova v Praze má právo na uzavření licenční smlouvy o užití této práce jako školního díla podle 60 odst. 1 autorského zákona. V Praze dne 21.5.2014 Vojtěch Šípek
Název práce: Vizuální dotazování v chemických databázích pomocí SMARTS vzorů Autor: Vojtěch Šípek Katedra: Katedra softwarového inženýrství Vedoucí bakalářské práce: RNDr. David Hoksza, Ph.D. Abstrakt: Cílem této práce je vytvoření frameworku pro vizuální dotazování do chemických databází, který bude implementován jako webová aplikace. Pomocí grafického editoru v klientské části aplikace uživatel vytváří dotazy, které jsou převedeny do chemického dotazovacího jazyka SMARTS. Tento dotaz je následně zpracován na aplikačním serveru, který je napojen na chemickou databázi. Součástí frameworku je i sada nástrojů na vytváření databáze a indexu, který je nad ní postavený. Klíčová slova: Vizuální dotazování, Chemická databáze, SMILES, SMARTS Title: Visual Querying in Chemical Databases using SMARTS Patterns Author: Vojtěch Šípek Department: Department of Software Engineering Supervisor: RNDr. David Hoksza, Ph.D. Abstract: The purpose of this thesis is to create framework for visual querying in chemical databases which will be implemented as a web application. By using graphical editor, which is a part of client side, the user creates queries which are translated into chemical query language SMARTS. This query is parsed on the application server which is connected to the chemical database. This framework also contains tooling for creating the database and index structure above it. Keywords: Visual querying, Chemical database, SMILES, SMARTS
Obsah Úvod 2 1 Reprezentace a vyhledávání chemických struktur 4 1.1 SMILES................................ 4 1.2 SMARTS............................... 6 1.3 Reprezentace chemických struktur v paměti............ 7 2 Řešení 8 2.1 Klientská část............................. 8 2.1.1 Použíté knihovny....................... 8 2.1.2 Uživatelské rozhraní..................... 8 2.1.3 Serializace grafického vstupu................. 14 2.1.4 Odeslání dotazu a zobrazení jeho výsledků......... 19 2.2 Serverová část............................. 20 2.2.1 Použité nástroje a knihovny................. 21 2.2.2 Struktura serverové části................... 21 2.2.3 Databáze chemických struktur................ 21 2.2.4 Inicializace serveru...................... 23 2.2.5 Zpracování uživatelského dotazu a filtrování databáze... 24 2.2.6 Vyhledávání dotazu...................... 26 2.2.7 Progress Servlet........................ 28 2.3 Databázové nástroje......................... 28 2.3.1 Výroba databáze....................... 28 2.3.2 Výroba indexu........................ 29 2.3.3 Nástroj pro rozšiřování databáze............... 31 3 Experimentální část 33 3.1 Reprezentace databáze v paměti................... 33 3.2 Velikost indexu nad databází..................... 34 3.3 Účinnost indexu nad databází.................... 35 Závěr 37 Seznam použité literatury 38 Seznam použitých zkratek 40 Přílohy 41 1
Úvod Cílem této práce je vytvoření frameworku pro vizuální dotazování do chemických databází. Dotazem je popis chemické struktury, který může být poměrně obecný. V popisu atomů a vazeb této struktury můžeme používat logické spojky a specifikovat některé jejich další vlastnosti. Výsledkem je množina sloučenin z databáze, které danému dotazu odpovídají. Důraz je kladen především na efektivitu dotazování i do velmi obsáhlých (milióny až desítky milionů záznamů), reálných databází. Framework bude dostupný jako webová aplikace. Chemoinformatika Chemoinformatika je obor založený na výpočetní chemii, matematice a informatice. Jejím úkolem je nalézt vhodný způsob reprezentace chemických struktur a vyvinutí algoritmů pro jejich porovnávání a predikci jejich vlastností. Vyhledávání podstruktur Jednou ze stěžejních oblastí chemoinformatiky je vyhledávání podstruktur, které je hojně využíváno především ve farmaceutickém průmyslu. Proces vývoje nových léků čelí stále narůstajícímu počtu dostupných sloučenin, které jsou uchovávány ve velkých databázích. Vyhledávání podstruktur v těchto databázích může uživateli pomoci získat množinu sloučenin s určitými chemickými vlastnostmi. Pokud umíme určit vlastnosti nějaké molekuly na základě její chemické struktury, pak se očekává, že molekuly s podobnou strukturou budou mít podobné vlastnosti[9]. Příklad můžeme vidět na obrázku 1, kde vidíme molekuly tří opiátů - morfinu, kodeinu a heroinu. Vidíme, že jejich struktura má mnoho společných rysů. Navíc víme, že všechny tři patří do skupiny opiátů, o kterých se ví, že mají podobné účinky na lidské tělo. (a) Morfin (b) Kodein (c) Heroin Obrázek 1: Chemická struktura opiátů Struktura práce Tato práce je rozdělena do tří částí. V první části rozebereme jednak různé způsoby ukládání chemických struktur a jednak jazyk SMARTS[3], který budeme 2
využívat k dotazování. Ve druhé části podrobně popíšeme implementaci všech částí frameworku a v poslední, experimentální části rozebereme časovou a paměťovou náročnost na testovacích vstupech, které by měly být typické pro tento typ aplikace. 3
1. Reprezentace a vyhledávání chemických struktur Formáty pro zápis chemických struktur Pro reprezentaci chemických struktur existuje řada zavedených formátů. Mezi nejpoužívanější patří formáty SDF[16] a SMILES[2]. Zatímco formát SDF je trojrozměrný popis (obsahuje informace o vzájemné poloze atomů v prostoru), SMILES je pouze jednorozměrný popis, který je navíc velmi podobný lidskému zápisu chemických struktur. Na rozdíl od prvního jmenovaného formátu obsahuje pouze informace o jednotlivých atomech a vazbách mezi nimi. Informace, jako je vzájemná poloha jednotlivých atomů v prostoru, jsou pro jednoduchost a stručnost vynechány. Toto zjednodušení má několik výhod. Za prvé je to uživatelsky poměrně přátelský a přehledný formát, za druhé uspoří velké množství paměti (pokud jsou informace poskytované obsáhlejšími formáty jako SDF nadbytečné) a navíc je tím pádem i rychlejší jejich načítání do paměti. 1.1 SMILES Simplified molecular-input line-entry system neboli SMILES[2] je dnes téměř standardem pro reprezentaci chemických struktur a reakcí. Popis jazyka 1 Atomy Atomy jsou reprezentovány svojí chemickou značkou uzavřenou v hranatých závorkách. Pro prvky s dvoupísmennými zkratkami platí, že druhé písmeno musí být malé. Organické prvky B, C, N, O, P, S, F, Cl, Br, I mohou být psány bez hranatých závorek, pokud je počet navázaných vodíků přizpůsoben nejnižší normální valenci 2 prvku. Pro atomy psané mimo závorky se navázané atomy vodíku vynechávají. Číslo za explicitně psaným vodíkem značí počet vodíků navázaných na daný atom. Příklad: C = [CH4] [C] = Elementární Uhlík Atomy v aromatických kruzích musí být psány malými písmeny. Náboj atomu se zapisuje za značkou prvku pomocí znaků + a -, za kterými je volitelný číselný parametr určující velikost náboje. Příklad: 1 Detailní popis jazyka SMILES na [2] 2 B (3), C (4), N (3,5), O (2), P (3,5), S (2,4,6) a 1 pro halogeny 4
[Fe+2] = Atom železa s nábojem +2 [OH3+] = Atom kyslíku se 3 navázanými vodíky a nábojem +1. Vazby Vazby jsou značeny podle tabulky 1.1. Jednoduchá Dvojná = Trojná # Aromatická : Tabulka 1.1: Popis vazeb v jazyce SMILES Propojení 2 atomů vazbou se značí vložením znaku vazby mezi tyto atomy. Větvení struktury se píše mezi kulaté závorky jeden pár závorek pro každou větev. Příklad: C F O = F( C)( O) Složitější příklad s grafickým znázorněním větvení je na obrázku 1.1 Obrázek 1.1: Příklad větvení v jazyce SMILES. Obrázek použit z [10] Jednoduché a aromatické vazby jsou implicitní a tudíž se psát nemusí. Značení vazby, která uzavírá cyklus je navíc opatřeno číslem, které je napsáno za oba atomy, ke kterým tato vazba přísluší. Pokud je číslo větší než 9, je nutné před toto číslo napsat znak %. Příklad: c1ccccc1 značí cyklus šesti aromatických uhlíků c:%10:c:c:c:c:c:%10 značí to samé, jen jsme explicitně napsali aromatické vazby a pro uzavření kruhu použili číslo 10 5
1.2 SMARTS SMiles ARbitrary Target Specification neboli SMARTS[3] je jazyk, jehož syntaxe vychází z jazyka SMILES. Každý validní SMILES řetězec je zároveň validním SMARTS řetězcem. Tento jazyk se využívá pro popis dotazů při vyhledávání podstruktur v databázích. Jedná se o přímočaré rozšíření jazyka SMILES o wildcardy, logické spojky a specifikace některých dalších chemických vlastností. Popis jazyka 3 Rozdílem oproti jazyku SMILES je význam atomů mimo hranaté závorky. V jazyce SMILES se tyto atomy chápou tak, že je na ně implicitně navázán takový počet vodíků, aby náboj atomu byl 0. Ve SMARTS toto ovšem neplatí. Atom mimo hranaté závorky odpovídá i příslušnému atomu s nábojem. Například O se ve SMILES interpretuje jako [OH2], ve SMARTS tento řetězec odpovídá jakékoliv struktuře, ve které se vyskytuje alifatický kyslík. Dalším rozdílem je možnost popisu atomu nikoli jen chemickou značkou, ale i symbolem # a atomovým číslem 4 prvku. Popis atomů a vazeb je stejný jako v případě jazyka SMILES, proto je tu nebudeme znovu rozebírat. Zaměříme se pouze na rozšíření oproti SMILES. Wildcardy Wildcardy (někdy česky nazývané žolíky) jsou významnou a snadno použitelnou součástí jazyka. Jejich výčet a popis je shrnut v tabulce 1.2. Syntakticky se wildcardy vyskytují na stejných místech, jako atomy. Libovolný atom * Libovolný aromatický atom a Libovolný alifatický atom A Libovolná vazba Tabulka 1.2: Popis wildcardů v jazyce SMARTS Logické spojky Logické spojky jsou nezbytné pro složitější a strukturovanější dotazy. Popsány jsou v tabulce 1.3. Konjunkce s vyšší prioritou má přednost před disjunkcí a obyčejnou konjunkcí. Logické spojky lze dávat mezi libovolné fragmenty stejného typu, to znamená mezi typy vazeb, mezi atomové značky či čísla a mezi jednotlivé vlastnosti atomu. Například: [C,O] 3 Detailní popis jazyka SMARTS na [3]. Zde je popis pouze těch částí jazyka SMARTS, které jsou nezbytné pro tuto práci. 4 Počet protonů v atomovém jádru, nebo také pořadí prvku v periodické tabulce prvků 6
Disjunkce, Konjunkce ; Konjunkce (vyšší priorita) & Tabulka 1.3: Popis logických spojek v jazyce SMARTS odpovídá struktuře, která obsahuje alespoň jeden prvek z dvojice uhlík, kyslík. [#6&A;+1,+2] odpovídá struktuře, která obsahuje alifatický uhlík s nábojem +1 nebo +2. C,=C odpovídá struktuře, která obsahuje dvojici uhlíků spojenou buď jednoduchou, nebo dvojnou vazbou Další konfigurovatelné vlastnosti atomů Pro účely této práce nás budou zajímat další dvě vlastnosti, které se dají specifikovat pomocí jazyka SMARTS. Jsou to velikost náboje a valence. Náboj atomu se specifikuje znakem + a. Pokud je kladný, či záporný náboj větší než 1, pak se jeho velikost specifikuje za znaménkem náboje. Jelikož je SMARTS jazyk dotazovací, tak může validní SMARTS řetězec vypadat i takto: [+] což se odpovídá libovolné struktuře, která obsahuje atom s nábojem +1. Valence je údaj, který udává, kolik vazeb je k atomu připojeno. Dvojná vazba se počítá za dvě, trojná za tři. Aromatická vazba se počítá za 1,5, nicméně vyskytují se vždy v páru, takže to nenaruší celočíselnost. Valence se specifikuje písmenem v a číselným parametrem určujícím velikost valence. Například [C;v2] odpovídá libovolné struktuře, která obsahuje uhlík s valencí 2. 1.3 Reprezentace chemických struktur v paměti Dosud jsme psali pouze o formátech, ve kterých se chemické struktury ukládají do databází. Pro vyhledávání podstruktur však potřebujeme chemickou strukturu vhodně reprezentovat v paměti. Není příliš překvapivé, že chemické struktury bývají reprezentovány jako grafy. Za prvé je to naprosto intuitivní představa, kde vrcholy grafu jsou atomy a hrany jsou chemické vazby. Za druhé je to velmi výhodné, jelikož v teorii grafů je známo množství algoritmů, které se hojně využívají i v chemoinformatice[18]. 7
2. Řešení Součástí této práce je framework pro vyhledávání podstruktur v chemických databázích. Tento projekt by se dal rozdělit do tří základních částí klientská část, kterou tvoří javascriptový grafický editor na tvorbu dotazů, serverová část, která má za úkol převzít dotaz z klientské strany, vyhledat vhodné struktury z databáze a poslat zpět klientovi, a konečně databázové nástroje pro offline zpracování a indexaci dat. 2.1 Klientská část Primárním úkolem klientské části je poskytnout uživateli příjemné uživatelské rozhraní pro tvorbu SMARTS dotazů. Tato část aplikace se snaží naplno využít možností jazyka HTML5 a nepoužívat pro tvoření grafického editoru technologie, které se dříve k tomuto účelu používaly, ale dnes se již jejich použití z mnoha různých důvodů nedoporučuje (Flash, Java Applet, Silverlight). Postupně v této kapitole rozebereme použité technologie, následně popíšeme uživatelské rozhraní a na závěr se budeme věnovat zpracování grafického vstupu a odeslání dotazu na server. 2.1.1 Použíté knihovny Easel.js [4] Jelikož náš editor kreslí na HTML5 canvas, tak je tato knihovna ideálním nástrojem. Jedná se o objektovou nadstavbu nad HTML5 canvasem. Programátor pomocí API kreslí na canvas rovnou celé geometrické tvary, písmo, či vkládá obrázky. Navíc jsou na tyto fragmenty programátorovi předány reference a díky nim může s fragmenty libovolně pohybovat, či na ně navazovat event handlery. jquery [5] Dnes již standardní knihovna pro práci s HTML DOM. Tato knihovna poskytuje programátorovi nástroje, které výrazně zmenší velikost JavaScriptového kódu oproti používání standardních JavaScriptových funkcí. V našem případě ji používáme hlavně pro dynamické úpravy HTML elementů. jquery UI [6] Framework pro tvorbu uživatelského rozhraní webových stránek. Programátorovi nabízí mnoho předpřipravených UI elementů (dialogová okna, tlačítka progressbar atd.). 2.1.2 Uživatelské rozhraní Uživatelské rozhraní tvoří grafický editor pro tvorbu SMARTS vzorů. Náhled editoru je na obrázku 2.1. Tento editor je složen z HTML5 canvasu, panelu nástrojů na pravé straně a panelem serializace ve spodní části. 8
Obrázek 2.1: SMARTS Grafický editor Panel nástrojů Panel nástrojů je rozdělen do dvou částí nabídku atomů a nabídku vazeb. Tlačítka v sekci atomů přepnou editor do stavu, kdy canvas na prázdných místech reaguje na kliknutí myši tím, že na tomto místě vykreslí atom či strukturu přiřazenou k aktivnímu tlačítku. V nabídce atomů najdeme tlačítko (2.2) na vyvolání pop-up okna pro výběr množiny prvků (2.3). Pod ním najdeme výčet prvků, o nichž se předpokládá, že budou často používané (tento seznam by měl být upraven podle typu databáze, do níž se uživatel dotazuje). Navíc je zde tlačítko s obrázkem benzenového jádra, které slouží jako ukázka, že lze snadno definovat i složitější struktury, než je samostatný atom, přímo v panelu nástrojů (vzhled tlačítka a výsledek jeho použití na 2.4). Obrázek 2.2: Tlačítko pro vyvolání dialogu pro výběr množiny prvků V pop-up okně pro výběr množiny prvků se nám zobrazí periodická tabulka prvků, ve které je každý prvek tlačítkem. Každý prvek můžeme dát do množiny aromatických prvků a množiny alifatických prvků. Podle toho, zda máme v horním panelu tohoto okna vybráno Aromatic (pro aromatické), Aliphatic (pro alifatické), nebo Both (pro aromatické i alifatické) vybíráme prvky do množiny pomocí kliknutí. Co je právě v množině poznáme podle barevných okrajů prvků v tabulce světle modré pro aromatické, červené pro alifatické a fialové, pokud prvek patří do množiny aromatický i alifatický. Například na obrázku 2.5 9
Obrázek 2.3: Dialog pro výběr množiny prvků - prázdná množina Obrázek 2.4: Tlačítko benzenu a výsledek jeho použití 10
vidíme, že v právě vybrané množině máme aromatický uhlík, alifatický kyslík a aromatický i alifatický dusík. Obrázek 2.5: Dialog pro výběr množiny prvků - neprázdná množina Pro odebrání prvku z množiny stačí vybrat stejný typ prvku(aromatic, Aliphatic, Both), jakým je prvek vybrán a znovu na něj kliknout. Kliknutím na tlačítko OK zavřeme dialogové okno a editor přepneme do stavu, kdy po kliknutí na prázdné místo na canvasu se nakreslí na toto místo atom, který reprezentuje množinu prvků, kterou jsme specifikovali v dialogovém okně. V nabídce vazeb najdeme čtyři tlačítka, pro každý typ vazby (jednoduchá, dvojná, trojná a aromatická) jedno. Tlačítka v této sekci přepnou editor do stavu, kdy po kliknutí na atom na canvasu se vytvoří dočasná vazba mezi nakliknutým atomem a myší. Po nakliknutí dalšího atomu se vytvoří vazba mezi prvním a druhým nakliknutým atomem. To, zda je editor ve stavu, který zapříčinil panel nástrojů, pozná uživatel tak, že pozadí tlačítka zodpovědného za tento stav je přebarvené na oranžovo. Canvas a vlastnosti atomů a vazeb Jak již bylo řečeno, ke kreslení na HTML5 canvas využíváme knihovnu Easel.js. Základním prvkem této knihovny je třída Stage, které se předá v konstruktoru HTML5 canvas, na který pak renderuje svůj obsah. Do této třídy se pak typicky vkládají instance třídy Container, do které se dají vložit buď další instance třídy Container nebo jiné kontejnerové třídy (například Text). Další možností je definovat Containeru třídu Graphics, pomocí které můžeme do Containeru kreslit. 11
Hlavním úkolem našeho canvasu je grafická reprezentace atomů a vazeb mezi nimi. Proto jsme vytvořili třídy Atom a Bond, které jsou poděděné od třídy Container. Tyto třídy zajišťují vykreslení atomů a vazeb a také jejich interakci s uživatelem (podpora Drag and Drop, změna vzhledu při úpravě vlastností). Atomy na canvasu mají dva grafické prvky, podle kterých uživatel pozná alespoň hrubé vlastnosti atomu barva a text uvnitř atomu. Pokud atom reprezentuje pouze jeden prvek, pak je uvnitř atomu chemická značka tohoto prvku. Pokud atom reprezentuje prvků více, pak je uvnitř atomu znak *. Co se týče barvy, tak je to stejné jako s okraji prvků v periodické tabulce v sekci popisující pop-up okno pro výběr množiny světle modrá pro aromatický atom, červená pro alifatický atom a fialová, pokud tento atom reprezentuje jak aromatické, tak alifatické prvky. Při kliknutí na atom respektive vazbu se tento element označí (což se projeví změnou barvy okraje elementu na zelenou) a napravo od panelu nástrojů se objeví panel s vlastnostmi označeného elementu. Pro označenou vazbu se v panelu s vlastnostmi zobrazí čtyři checkboxy, které udávají, jaké typy vazeb tato vazba může reprezentovat. Na zaškrtávání či odškrtávání checkboxů reaguje i grafické provedení vazby. Grafické provedení vazeb je popsáno na obrázku 2.6. (a) Jednoduchá (b) Dvojná (c) Trojná (d) Aromatická (e) Kombinovaná Obrázek 2.6: Grafické provedení vazeb Pro označený atom se v panelu vlastností objeví pět sekcí. V prvních dvou sekcích nalezneme seznam alifatických a aromatických prvků, které může tento atom reprezentovat. Pokud se prvky do této buňky nevejdou, je jejich výčet oříznut. V další části opět nalezneme tlačítko 2.2. Kliknutím na toto tlačítko se opět vyvolá pop-up okno pro výběr množiny prvků. V tomto případě jsou však už předem označeny prvky, které patří do množiny, kterou reprezentuje označený atom. Kliknutím na tlačítko OK potvrdíme změnu množiny vybraného atomu. Poslední dvě sekce určují další chemické vlastnosti atomu náboj a valenci. Pokud nejsou náboj respektive valence specifikovány, pak atom odpovídá atomu s libovolnou hodnotou těchto vlastností. Jsou-li specifikovány, pak atom odpovídá pouze atomům s valencí respektive nábojem, které jsou obsaženy v zadané množině hodnot. Hodnotu do množiny přidá uživatel tak, že ji napíše do textboxu v dané sekci a klikne na tlačítko add. Vyplněná hodnota se objeví ve výčtu hodnot dané sekce. Pro vymazání hodnoty napíše uživatel hodnotu, kterou chce vymazat, 12
do daného textboxu a klikne na tlačítko delete. Tato změna se opět projeví ve výčtu hodnot. Příklad panelu vlastností pro atomy a vazby je na obrázku 2.7. V panelu vlastností pro atom je zde popsán atom, který může být pouze alifatickým uhlíkem, jeho náboj může být buď +1 nebo -2 a jeho valence může být pouze 4. V panelu vlastností pro vazbu je zde popsána vazba, která může být jen jednoduchou vazbou. Obrázek 2.7: Příklad panelu vlastností pro atomy (vlevo) a vazby (vpravo) Atomy lze po canvasu libovolně přemisťovat pomocí techniky drag and drop. Označený element (atom, vazba) se smaže po stisknutí klávesy delete. Pro zrušení aktuálního stavu editoru (označení elementu, vybrání prvku či typu vazby z panelu nástrojů) stačí stisknout klávesu escape. Panel serializace Jakmile je uživatel hotov s popisováním svého dotazu pomocí grafického vstupu má dvě možnosti jak pokračovat. Za prvé může kliknout na tlačítko SMARTS, které vytvoří SMARTS řetězec, jenž odpovídá uživatelskému vstupu, a ukáže ho uživateli v centrální části panelu serializace. Pokud uživatele SMARTS řetězec nezajímá a chce se rovnou dotazovat do databáze, pak klikne na tlačítko Submit query, jenž ho odkáže na stránku s tabulkou výsledků. 13
2.1.3 Serializace grafického vstupu Serializace je určitě algoritmicky nejzajímavější částí klientské strany našeho frameworku. Pro její účely si udržujeme v paměti seznam všech vazeb (bondcontainer) a všech atomů (atomcontainer), které jsou na canvasu. Tyto atomy jsou reprezentovány třídou Atom, která obsahuje nejen informace o vlastnostech tohoto atomu, ale i seznam referencí na třídy Bond, které reprezentují vazby, jež jsou k tomuto atomu připojeny. Navíc jsou v třídě Bond uchovávány kromě informací o možných typech vazby i reference na atomy, k nimž je vazba připojena. Díky tomu a díky podobnosti molekul a grafů popsané v kapitole 1.3 můžeme pro serializaci použít klasické grafové algoritmy v našem případě DFS. Serializace probíhá ve dvou fázích. V první si vytvoříme popis grafu ve formátu JSON (důvod vytváření této reprezentace si vysvětlíme v kapitole věnované serverové části frameworku), ve druhé pak samotný SMARTS řetězec. Vytváření JSON reprezentace grafu je poměrně přímočaré. Tento JSON řetězec má následující formát: 1 { 2 " atoms ": { //V tomto objektu jsou popsány jednotlivé atomy 3 " 0": { //Pro každý atom je zde objekt, jehož identifikátor 4 //je jeho index v poli atomcontainer 5 " possiblearomaticnumbers ": [ 6 //Zde mohou být specifikována atomová čísla prvků, 7 //jenž jsou v atomu specifikovány jako aromatické 8 ], 9 " possiblealiphaticnumbers ": [ 10 //Zde mohou být specifikována atomová čísla prvků, 11 //jenž jsou v atomu specifikovány jako alifatické 12 6,7,8 13 ], 14 " possiblevalences ": [ 15 //Zde mohou být specifikovány možná valence atomu 16 1,2 17 ], 18 " possiblecharges ": [ 19 //Zde mohou být specifikovány možné náboje atomu 20 1,3, -2 21 ] 22 }, 23 "1": { 24 " possiblearomaticnumbers ": [ 25 6 26 ], 27 " possiblealiphaticnumbers ": [ 28 7 29 ], 30 " possiblevalences ": [ 31 ], 32 " possiblecharges ": [ 33 2 34 ] 35 } 36 }, 37 " bonds ": [ //V tomto poli jsou postupně specifikovány jednotlivé vazby 38 { 14
39 //Flag, který určuje, 40 //zda tato vazba reprezentuje jednoduchou vazbu 41 " singlebond ": true, 42 //Flag, který určuje, 43 //zda tato vazba reprezentuje dvojnou vazbu 44 " doublebond ": false, 45 //Flag, který určuje, 46 //zda tato vazba reprezentuje trojnou vazbu 47 " triplebond ": false, 48 //Flag, který určuje, 49 //zda tato vazba reprezentuje aromatickou vazbu 50 " aromaticbond ": false, 51 //Id prvního atomu, ke kterému je vazba připojena 52 " firstatomid ": "1", 53 //Id druhého atomu, ke kterému je vazba připojena 54 " secondatomid ": "0" 55 } 56 ] 57 } Sekce atoms a bonds vytvoříme postupně pomocí foreach cyklů přes pole atom- Container a bondcontainer. Tento objekt pak předáme standardní JavaScriptové funkci JSON.stringify, což nám z objektu vytvoří JSON řetězec. Vytváření SMARTS řetězce je už o něco složitější a využijeme při něm, jak je již výše uvedeno, algoritmus Depth First Search neboli DFS. Nyní si popíšeme funkci dfs třídy Serializer, která se postupně zavolá na každý atom, který je dosažitelný 1 z prvního atomu v poli atomcontainer. Jednotlivé fragmenty SMARTS řetězce budeme postupně přidávat do pole resultarray. Navíc ke každému atomu si budeme pamatovat, na jakém indexu v poli resultarray začíná jeho popis. Důvod použití tohoto pole a zaznamenání indexu pro každý atom si vysvětlíme později. Funkce dfs dostává dva parametry atom a bond. První parametr je reference na instanci třídy Atom a určuje, který atom se má v tuto chvíli zpracovat. Druhý parametr je reference na instanci třídy Bond a určuje, přes jakou vazbu jsme se do tohoto atomu dostali. Pokud je druhý atom roven hodnotě undefined 2, pak to znamená, že jsme v prvním zpracovávaném atomu. Při samotném vytváření SMARTS řetězce využíváme toho, že pořadí atomů v tomto řetězci lze psát v libovolném pořadí, v jakém pracuje algoritmus DFS. Například Navíc C O N = O( C)( N) = N O C. C C C 1 Používáme zde grafové názvosloví. Podobnost molekul a grafů jsme již popsali v kapitole 1.3 2 V jazyce JavaScript je tato hodnota implicitně přiřazena každé (i do této chvíle neexistující) proměnné 15
je to samé, jako (C( C( C))). Přesně této vlastnosti využijeme. Hloubka zanoření závorek bude vždy rovná hloubce zanoření rekurze. Stačí nám tedy po zanoření napsat otevírací závorku, zpracovat atom, rekurzivně se přesunout na sousedící atomy a po příchodu z rekurze závorku uzavřít. Přesně toto také uděláme. Na začátku funkce dfs zapíšeme do výstupního řetězce otevírací závorku a zkontrolujeme, zda jsme přišli přes vazbu. Pokud ano, tak napíšeme příslušné typy vazeb podle tabulky 1.1 a spojíme je znakem, podle tabulky 1.3. Pak zpracujeme vlastnosti atomu do hranatých závorek budeme postupně psát dvojice atomové číslo prvku-typ prvku spojenou znakem &. Například: #6&A pro alifatický uhlík. Pokud jsou specifikovány možné náboje atomu, pak je za výčet prvků přidán znak ; a následně výčet nábojů oddělených znakem,. Naprosto stejným způsobem jsou následně zapsány možné valence. Popis atomu, který má specifikovaný náboj a valenci může vypadat například takto: [#6&A;+1;v2] Co jsme dosud nezmínili a budeme v následujících odstavcích potřebovat je to, jak poznáme, které atomy či vazby jsme již zpracovali. Kvůli tomu má každá vazba a každý atom flag visited, který je na počátku serializace pro každý element nastaven na false. Při začátku zpracování atomu se jeho flag visited nastaví na true. Po zpracování vlastností atomu se postupně projde pole vazeb, které jsou navázány na zpracovávaný atom. Jsou tři případy, které mohou nastat pro každou z těchto vazeb: 1. Pokud je flag atomu na druhém konci vazby nastaven na false, tak to znamená, že ani vazba zatím nemohla být zpracována. V tomto případě zavoláme rekurzivně funkci dfs, kde prvním parametrem bude atom na druhém konci vazby a druhým parametrem bude samotná vazba. Před zavoláním rekurze ještě nastavíme flag vazby na true. 2. Pokud je flag atomu na druhém konci vazby nastaven na true a zároveň flag vazby je nastaven na true, pak už nemáme co zpracovávat a tuto vazbu přeskočíme. 3. Poslední možností je, že flag sousedícího atomu je nastaven na true, ale flag vazby je nastaven na false. Tato situace značí, že jsme narazili na cyklus. Ze syntaxe jazyka SMARTS vyplývá, že musíme oba konce této vazby označit indexem cyklu, který je na počátku 1 a při nalezení cyklu se vždy 16
inkrementuje. Kvůli nutnosti označení obou atomů máme zadefinováno pole resultarray a u každého atomu si pamatujeme index, na kterém začíná jeho definice v tomto poli. Stačí si tedy vygenerovat řetězec, který definuje aktuálně zpracovávanou vazbu, připojit za ní index cyklu a tento celý řetězec připojit za definici obou koncových atomů (pokud je index cyklu dvojciferný, pak musíme před toto číslo vložit znak % ). Po zpracování všech vazeb navázaných na zpracovávaný atom (a tím pádem i vystoupení z rekurze) přidáme do výstupního řetězce uzavírací závorku. Po proběhnutí algoritmu na vytvoření SMARTS řetězce se zkontroluje, zda mají všechny atomy v poli atomcontainer nastavený flag visited na true, jinými slovy, zda byly zpracovány všechny atomy. Pokud existuje atom, který má flag nastavený na false, pak to znamená, že graf nebyl souvislý. Toto zapříčiní vyvolání chybové hlášky uživateli s textem Graph has to be connected. Příklad serializace Pro úplnost si nyní uveďme příklad. Ukážeme si postup při serializaci struktury z obrázku 2.8. Na tomto obrázku vidíme uživatelský dotaz a panely vlastností pro dva elementy, u kterých není jasné, co představují přímo z jejich grafického provedení atom uprostřed a vazba mezi tímto atomem a atomem kyslíku. Ostatní atomy nemají specifikovaný žádný náboj ani valenci. Obrázek 2.8: Příklad serializace 17
Je úplně jedno, v jakém atomu serializaci začneme. Začněme tedy například v atomu uprostřed. Na pořadí vazeb vedoucích z atomu také nezáleží. Určeme si tedy, že první dvě vazby z tohoto atomu vedou k uhlíkům a poslední vazba je na kyslík (u zbývajících atomů je v tomto případě pořadí zanedbatelné). Postupně budeme tvořit SMARTS řetězec. Po každém kroku si ho budeme aktualizovat. V prvním kroku jsme vstoupili do funkce dfs, parametr atom popisuje atom uprostřed a parametr bond je undefined. Proto nebudeme žádnou vazbu zapisovat a zapíšeme pouze otevírací kulatou závorku. Nezapomeneme nastavit tomuto atomu flag visited na true. ( Poté do hranatých závorek zapíšeme popis atomu. Nejprve popíšeme možné alifatické a aromatické prvky, následně za znak ; zapíšeme možné náboje a valence. ([#6&A,#7&A,#6&a;+1;v4] Následně se pro každou vazbu, která má flag visited nastavený na false zanoříme do rekurze a znovu zavoláme funkci dfs, kde prvním parametrem bude atom na druhé straně této vazby a druhým parametrem bude samotná vazba. U druhého atomu (uhlík) již máme v parametrech dfs specifikovanou vazbu, přes kterou jsme přišli. Proto kromě otevírací závorky připíšeme do vznikajícího SMARTS řetězce možné typy této vazby a vazbě nastavíme flag na true. ([#6&A,#7&A,#6&a;+1;v4]( Zpracujeme vlastnosti atomu. V tomto případě je to snadné. ([#6&A,#7&A,#6&a;+1;v4]( [#6&A] Opět se posuneme v rekurzi hlouběji. Nyní jsme přešli přes dvojnou vazbu k druhému uhlíku (vazbu, přes kterou jsme přešli k prvnímu uhlíku ignorujeme, jelikož tato vazba i atom na jejím druhém konci mají nastavený flag visited na true). Přidáme tedy do SMARTS řetězce opět otevírací závorku a typ vazby. ([#6&A,#7&A,#6&a;+1;v4]( [#6&A](= Zpracujeme atom stejným způsobem, jako předchozí. ([#6&A,#7&A,#6&a;+1;v4]( [#6&A](=[#6&A] Vazbu k prvnímu uhlíku opět ignorujeme. Vazba k prostřednímu atomu má flag nastavený na false, ale atom na druhém konci má flag nastavený na true. Tato situace značí cyklus. Proto za popis tohoto atomu a atomu na druhém konci aktuálně zpracovávané vazby připíšeme typ vazby a index cyklu (jelikož jde o první cyklus, je index cyklu 1). ([#6&A,#7&A,#6&a;+1;v4] 1( [#6&A](=[#6&A] 1 18
U horního uhlíku jsme již prošli všechny vazby, proto zapíšeme uzavírací závorku a vrátíme se v rekurzi o úroveň výš. ([#6&A,#7&A,#6&a;+1;v4] 1( [#6&A](=[#6&A] 1) Nyní jsme opět u prvního uhlíku, ve kterém už ovšem máme také všechny vazby zpracované. Proto znovu zapíšeme uzavírací závorku a vrátíme se v rekurzi o úroveň výš. ([#6&A,#7&A,#6&a;+1;v4] 1( [#6&A](=[#6&A] 1)) Jsme znovu u prostředního atomu. Vazby napravo od něj jsou již zpracované. Zbývá zpracovat vazbu ke kyslíku. Znovu se tedy zanoříme do rekurze, zapíšeme otevírací závorku a typ vazby. Ta je v tomto případě buď jednoduchá nebo dvojná. ([#6&A,#7&A,#6&a;+1;v4] 1( [#6&A](=[#6&A] 1))(,= Zpracujeme atom kyslíku a jelikož tu nejsou žádné vazby na zpracování, vrátíme se v rekurzi, takže zapíšeme uzavírací závorku. ([#6&A,#7&A,#6&a;+1;v4] 1( [#6&A](=[#6&A] 1))(,=[#8&A]) Opět jsme u prostředního atomu, u kterého jsme také s prací hotovi. Uzavřeme poslední závorku a SMARTS řetězec je hotov. ([#6&A,#7&A,#6&a;+1;v4] 1( [#6&A](=[#6&A] 1))(,=[#8&A])) 2.1.4 Odeslání dotazu a zobrazení jeho výsledků Jak již bylo řečeno, pro odeslání dotazu klikne uživatel na tlačítko Submit query v panelu serializace. Stisk tohoto tlačítka vyvolá serializační proces. Oba výsledky serializace JSON reprezentace grafu a SMARTS řetězec se zabalí do objektu, který se znovu serializuje pomocí funkce JSON.stringify a uloží se do HTML5 Web Storage. Konkrétně do sessionstorage. Okno prohlížeče je následně přesměrováno na stránku s výsledky dotazování. Na této stránce se nejprve přečte obsah sessionstorage, odkud se získá dotaz, který se má poslat na server. Následně se pošle na server dotaz pomocí protokolu HTTP, konkrétně pomocí dotazovací metody GET. Dotaz obsahuje celkem čtyři proměnné proměnnou smarts, která obsahuje dotazovací SMARTS řetězec, proměnnou json, ve které je JSON reprezentace grafu, proměnnou timestamp, která obsahuje přesný čas, kdy došlo k přesměrování na tuto stránku (tento údaj později slouží jako identifikace konkrétního dotazu), a číselnou proměnnou numofrecords, která určuje po kolika strukturách, které odpovídají dotazu, má server poslat odpověď. Adresa tohoto GET u je adresa servletu smarts (podrobně si jej popíšeme v části 2.2, která je věnovaná serverové straně frameworku). Navíc se každých 500 milisekund, až do přijetí odpovědi, pošle na server dotaz (na adresu servletu progress, který bude také rozebrán v části 2.2) na aktuální pokrok v prohledávání databáze. Odpověď na tento dotaz je ve formátu počet_zpracovaných_struktur/počet_všech_struktur. Tento údaj se zapíše nad tabulkou s výsledky (??? toto možná předělám - jquery UI poskytuje grafický progressbar, což by asi vypadalo lépe). 19
Jakmile přijde odpověď (opět v JSON formátu, popis této odpovědi bude opět popsána v sekci 2.2), tak je ihned zpracována a je z ní vytvořena tabulka s výsledky dotazu pro každou strukturu jeden řádek. Každý takovýto řádek má tři sloupce. V prvním je uvedeno pořadové číslo výsledku (to se rovná číslu řádku tabulky), ve druhém sloupci je uvedeno ID chemické struktury v databázi (to je rovno číslu řádku v databázi) a ve třetím sloupci je SMILES reprezentace chemické struktury, která je zároveň hypertextovým odkazem na stránku, kde je tato struktura podrobně popsána (typicky v nějaké volně dostupné online databázi chemických struktur, jako je ChEMBL[7] či PubChem[8]). Příklad takové tabulky je na obrázku 2.9. Nad tabulkou vidíme, že databáze obsahuje 1318187 záznamů, které jsme už všechny prošli. V tabulce vidíme, že dotazu odpovídají pouze 3 záznamy v databázi. Obrázek 2.9: Příklad tabulky zobrazující výsledky dotazu Pod tabulkou s výsledky se mohou vyskytovat tři různé elementy. 1. V případě, že jsme ve stavu, kdy byl poslán dotaz na server, ale ještě nepřišla odpověď, pak je zde animace (ve formátu GIF), která znázorňuje, že stránka na odpověď čeká. 2. Pokud jsme již výsledek dostali, ale ještě je možné ze serveru dostat další odpovědi (prohledávání databáze ještě nedoběhlo do konce), pak je pod tabulkou tlačítko, ve kterém je nápis Load n more, kde n značí obsah konstanty numofrecords. Kliknutím na toto tlačítko pošleme další dotaz na server. To, že má server pokračovat v prohledávání databáze až na místě, kde předtím skončil, si pamatuje serverová strana ve své Session a to, že se jedná o ten samý dotaz, pozná podle shodné proměnné timestamp. 3. Pokud je prohledávání databáze již dokončeno, pak se pod tabulkou objeví vypnuté tlačítko s nápisem All results loaded. 2.2 Serverová část Úkolem serverové části aplikace je zpracování dotazu z klientské části, prohledání databáze a odeslání výsledků dotazu zpět. V této kapitole probereme, jak tato část aplikace funguje a jaké se v ní používají nástroje. Případná úskalí (hlavně co se týče úspory času a paměti) si rozebereme v kapitole 3, která se zabývá porovnáním časové a paměťové náročností různých způsobů implementace. 20
2.2.1 Použité nástroje a knihovny Apache Tomcat Apache Tomcat[12] je jedním z nejznámějších open-source aplikačních serverů. Je založený na jazyce Java a principu Java Servletů[11]. Jeho výhodou oproti konkurenčním aplikačním serverům je hlavně jeho jednoduchost a přímočarost. Chemistry Developtment Kit CDK neboli Chemistry Developtment Kit[13] je velice obsáhlá open-source knihovna pro chemoinformatiky a bioinformatiky. Nabízí velké množství nástrojů a rozhraní pro práci s chemickými strukturami. Gson Gson[14] je knihovna pro zpracování a vytváření JSON řetězců v jazyce Java. Pomocí této knihovny je možné vyrobit instanci specifické třídy na základě JSON řetězce. Struktura této třídy ovšem musí vyhovovat struktuře daného JSON řetězce. 2.2.2 Struktura serverové části Implementace serverové části se dá rozdělit do čtyř částí: 1. Inicializace serveru sem patří načtení databáze do paměti a vyrobení pomocných proměnných 2. Zpracování uživatelského dotazu a prefiltrace databáze 3. Samotné porovnávání chemických struktur se SMARTS řetězcem 4. Podpora zjišťování pokroku v prohledávání databáze Části 1-3 jsou implementovány v rámci Serveltu smarts, čtvrtá část je implementována v rámci Servletu progress. Všechny tyto části si postupně rozebereme. Nejdříve si však musíme popsat databázi, se kterou budeme pracovat. 2.2.3 Databáze chemických struktur Naše databáze je textový soubor, jehož každý řádek popisuje jeden záznam. Záznam má dvě části oddělené středníkem SMILES řetězec a URL, na kterém je popis struktury, kterou reprezentuje daný SMILES (tento popis bude typicky umístěn na serveru nějaké on-line dostupné chemické databáze jako [7] či [8]). Naše aplikace však počítá i s tím, že nad touto databází bude postaven index tvořený z filtrů (tvoření indexu a jednotlivých filtrů bude popsáno v kapitole 2.3). Tento index slouží k urychlení dotazů nad touto databází. 21
Filtry Filtrem s vlastností x budeme označovat soubor, který popisuje, které struktury v databázi mají vlastnost x. Struktura filtru s vlastností x se dá popsat jako bitový řetězec, kde n-tý bit ve filtru popisuje, zda n-tá struktura v databázi má (bit nastavený na 1), nebo nemá (bit nastavený na 0) vlastnost x. Pokud bychom tedy měli databázi o osmi záznamech, pak by filtr s vlastností obsahuje uhlík mohl vypadat takto: 10001001 což však bude v souboru reprezentováno pomocí jediného bytu s hodnotou 137 a interpretoval by se jako první, pátý a osmý záznam v databázi obsahuje uhlík. Index, který naše aplikace očekává obsahuje 4 typy filtrů: 1. Filtry s vlastností obsahuje atom s atomovým číslem n, pro n [1, 109] & n N 2. Filtry s vlastností obsahuje dvojici atomů s atomovými čísly n a m, které jsou spojeny vazbou v, pro n, m [1, 109] & n, m N & n m a pro v {-,=,#, } Pomlčka značí jednoduchou vazbu, rovnítko dvojnou a kříž trojnou vazbu. To je stejné značení jako v jazyce SMILES. Aromatická vazba je značena vlnovkou, jelikož dvojtečka, kterou je aromatická vazba značena ve SMILES, je zakázána v názvu souborů ve filesystému operačního systému Windows. 3. Filtry s vlastností obsahuje atom s atomovým číslem n a nábojem m, pro n [1, 109] & n N a pro m [ 10, 10] & m Z 4. Filtry s vlastností obsahuje atom s atomovým číslem n a valencí m, pro n [1, 109] & n N a pro m [0, 10] & m N Pokud filtr s danou vlastností neexistuje, předpokládá se, že jeho vlastnosti neodpovídá žádná struktura v databázi. Informace o indexu Součástí indexu nejsou jen jednotlivé filtry, ale také soubor pojmenovaný info.index. Tento soubor obsahuje informace o celém indexu. Na prvním řádku má uvedený počet záznamů v databázi. Navíc pro každý filtr v indexu je v informačním souboru řádek, ve kterém je uvedeno jméno filtru a počet záznamů, které odpovídají vlastnosti daného filtru. 22
2.2.4 Inicializace serveru Jelikož je popisovaný software navržen jako webová aplikace, počítá se s tím, že ho může používat více uživatelů najednou. Z tohoto důvodu by bylo velmi pomalé číst databázi z disku při každém uživatelském dotazu (pokud by dotaz poslalo více uživatelů ve stejnou chvíli, disk by byl zatížen a jeho čtení by bylo pomalejší). Proto je při inicializaci serveru celá databáze, včetně indexu, načtena do paměti. Pro načítání samotné databáze jsme si vytvořili třídu DbRecord, která má 2 datové položky řetězec smiles, který popisuje danou chemickou strukturu a řetězec url (viz 2.2.3). Instance této třídy budou reprezentovat záznamy databáze. Databáze je pak načtena do kontejneru typu ArrayList<DbRecord>. Počet filtrů není zanedbatelný a jejich velikost roste lineárně s velikostí databáze. Načtení všech filtrů do paměti by mohlo být pro velké databáze nereálné. Nicméně velká většina filtrů bude úplně prázdná, nebo velmi řídká (obsahuje malý počet bitů nastavených na 1), což vede na myšlenku neukládat celý obsah souboru do paměti, ale jen ho zpracovat a uložit si čísla záznamů, které danému filtru vyhovují. Na druhou stranu existuje řada filtrů, které budou naopak velmi husté (téměř všechny bity budou nastaveny na 1; například v organických databázích, které se používají ve farmacii, to budou filtry popisující chování organických prvků). Uložení takovýchto filtrů pomocí seznamu čísel záznamů by však bylo velmi neefektivní. Číslo záznamu, reprezentované jako Integer, má velikost 32 bitů, v souboru má tato informace pouze 1 bit. Tento případ naopak vede na myšlenku uložit si celý soubor jako pole bytů. Díky této znalosti můžeme filtry ukládat do paměti dvojím způsobem. Jednak do proměnné smallfilters třídy HashMap<String, List<Integer> > pro řídké filtry a jednak do proměnné filters třídy HashMap<String, byte[]> pro husté filtry. Obě mapy budou mít klíč rovný jménu souboru, ve kterém je filtr uložený (bez přípony.index). Abychom však mohli rozumně rozpoznat, zda je filtr hustý, či řídký, museli bychom celý filtr zpracovat a spočítat počet záznamů. Tuto informaci ale už máme v informačním souboru o indexu. Z tohoto souboru načteme do proměnné numberofrecords celkový počet záznamů v databázi a následně do proměnné filterstats třídy HashMap<String, Integer> uložíme velikosti filtrů. Dále si definujeme proměnnou listlimit, která určuje, kolika záznamům v databázi musí filtr vyhovovat, aby byl uložen pomocí pole bytů a ne pomocí dynamického pole čísel. V naší implementaci je tato hodnota nastavená na numberofrecords, jelikož je to 32 přesně hodnota, při které by měly být reprezentace polem bytů a dynamickým polem stejně velké (pokud zanedbáme režii na správu dynamického pole a přesah bitů, pokud není numberofrecords dělitelné osmi). V tuto chvíli již máme nejen dostatek informací pro načtení filtrů do paměti dvojím způsobem, ale zároveň jsme schopni při používání filtrů poznat, zda máme hledat filtr x v mapě filters nebo smallfilters stačí se zeptat, zda záznam v mapě filterstats pod klíčem x je větší, či roven proměnné listlimit. 23
2.2.5 Zpracování uživatelského dotazu a filtrování databáze Výroba grafové reprezentace dotazu Jak jsme si již popsali v kapitole 2.1.3, dotaz poslaný z klientské části aplikace se skládá ze dvou částí SMARTS a JSON řetězce. Tyto dvě reprezentace popisují tutéž věc a obsahují naprosto rovnocenné informace o vlastnostech a struktuře dotazu. Proč tedy nestačí posílat jen jednu reprezentaci? Důvodem je to, že se SMARTS řetězci umí pracovat chemické knihovny, které budeme využívat. Tyto knihovny samozřejmě mají vlastní parser pro jazyk SMARTS, pomocí kterého si vyrobí své interní struktury. Z těchto struktur je však pro programátora poněkud obtížné (a v některých případech i nereálné) získávat informace o jejich vlastnostech. Na druhou stranu práce s JSON reprezentací je poměrně snadná. Výroba na klientské straně je pro nás v podstatě zadarmo, jelikož pro převod objektu do JSON řetězce je v JavaScriptu standartní funkce. Pro rozpársování JSONu na serverové straně existuje množství knihoven, které převedou tento řetězec do objektů, které si sami vytvoříme a tudíž si je můžeme přizpůsobit tak, aby se nám s nimi pohodlně pracovalo. Pro tento účel jsme si vytvořili tři třídy SMARTSGraph, SMARTSGraphAtom a SMARTSGraphBond. Jak název napovídá, třída SMARTSGraph bude reprezentovat graf, který bude popisovat SMARTS dotaz. Vrcholy (atomy) v tomto grafu budou reprezentované třídou SMARTSGraphAtom a hrany (vazby) bude reprezentovat třída SMARTSGraphBond. Všimněme si podobnosti struktury JSONu (tato struktura je popsaná v kapitole 2.1.3) a našich třech grafových tříd. V kořenové úrovni JSON objektu máme datové položky pojmenované stejně, jako v třídě SMARTSGraph. Navíc odpovídají i jejich typy pole bonds odpovídá třídě ArrayList a objekt atoms odpovídá třídě HashMap (mapování objektu na hash mapu je stejné jako v jazyce JavaScript, na základě kterého byl JSON formát vytvořen. Názvy datových položek jsou klíče a jejich hodnoty jsou zároveň i hodnotami v mapě). Stejná podobnost je i o úroveň níž. Prvky pole bonds se dají namapovat na proměnné v třídě SMARTSGraphBond a hodnoty v objektu atoms zase na proměnné v třídě SMARTSGraphAtom. Díky této podobnosti můžeme použít knihovnu Gson, pro vyrobení instance třídy SMARTSGraph z našeho JSON řetězce. V tuto chvíli, kdy máme k dispozici všechny potřebné informace o dotazu, můžeme použít filtry k vytvoření podmnožiny databáze, nad kterou se má smysl vůbec dotazovat. Filtrování dotazu Pokud bychom dotaz vyhledávali na celé databázi, která může mít desítky milionů záznamů, pak by bylo vyhledávání ve většině případech velmi pomalé. V článku [1] je porovnání dvou reálně používaných algoritmů pro zjišťování, zda struktura odpovídá SMARTS vzoru. Medián rychlosti jednoho dotazu je pro první algoritmus 0,04ms a pro druhý 0,1ms. Pokud bychom tedy SMARTS dotaz 24
testovali přes celou databázi, pak by vyhodnocení dotazu na databázi o velikosti 73 000 000 záznamů (velikost databáze [8]) trvalo necelých 50 minut (při testování na stejném stroji jako na [1], při použití algoritmu s mediánem 0,04ms a předpokladu, že průměrný čas testu bude podobný, jako medián). Pro některé dotazy bude skutečně nutné testovat nad celou databází už jen z toho důvodu, že některé dotazy mohou jako výsledek celou databázi vydat. Pro většinu dotazů však budeme moci množinu (dále nazývanou jako vyhledávací množina ), nad kterou budeme dotaz vyhledávat, zúžit. K tomu nám pomůže index databáze a grafová struktura, kterou jsme si vyrobili z JSON řetězce popisujícího SMARTS dotaz. Myšlenkou je prostupně projít všechny elementy grafu (atomy, vazby), pro každý tento element vyrobit vyhledávací množinu a poté z těchto množin vyrobit jejich průnik, který bude vyhledávací množinou pro celý dotaz. Začneme vazbami. Z kapitoly 2.2.3 víme, že filtry v indexu databáze jsou pro vazby ve formátu (atomové čáslo prvního atomu)(typ vazby)(atomové čáslo druhého atomu). Proto si pro každou vazbu v grafu nejprve vyrobíme 2 množiny čísel, které budou reprezentovat možná atomová čísla atomů na obou koncích této vazby. Mezi těmito čísly atomů pak můžeme mít všechny typy vazeb, které jsou ve zpracovávané vazbě definovány. Tímto způsobem získáme množinu trojic číslo-vazba-číslo, které reprezentují databázové filtry. Vyhledávací množinu pro tuto vazbu získáme tak, že vyrobíme sjednocení všech těchto filtrů. Například mějme trojici atom1-vazba-atom2, kde atom1 může být uhlík či dusík, atom2 uhlík či kyslík a vazba může být jednoduchá nebo dvojná. Množina atomových čísel pro atom1 je tedy {6,7}, pro atom2 {6,8} a znak možné vazby je z množiny {,=}. Pak všechny možné trojice (které zároveň odpovídají jménům filtrů) jsou 6 6, 6=6, 6 8, 6=8, 7 8, 7=8, 6 7, 6=7. Poslední dvě trojice mají čísla v obráceném pořadí, než v jakém jsou atomy. To kvůli pravidlům pojmenovávání filtrů z kapitoly 2.2.3 číslo na pravé straně vazby musí být větší, či rovno číslu na straně levé. Zároveň si do množiny usedatoms budeme poznamenávat atomová čísla atomů, na které jsme již narazili. Pro atomy je situace podobná. U atomu můžeme specifikovat množinu atomových čísel, množinu možných nábojů a množinu možných valencí. Filtry pro atomy máme trojího druhu dvojice atom-valence, dvojice atom-náboj a samostatný atom. Filtry pro samostatné atomy bez valence či náboje budeme používat pouze ve chvíli, kdy daný atom má prázdné množiny s možnými náboji a valencemi a zároveň jeho atomové číslo není v množině usedatoms (to znamená pouze v případě, kdy dotazem je pouze jeden atom, který má specifikovaná pouze možná atomová čísla). V ostatních případech budeme používat filtry, které jsou specifičtější a tudíž by filtr pro samostatný atom nijak výslednou vyhledávací množinu neovlivnil. Pokud atom má specifikovanou množinu nábojů či valencí, pak bude výsledná vyhledávací množina rovna sjednocení všech filtrů, které odpovídají dvojici atom- 25
náboj či atom-valence, pro všechny atomy z množiny atomových čísel, valence z množiny možných valencí a náboje z množiny možných nábojů. Například mějme atom s atomovým číslem 6, množinou možných nábojů {- 1,1} a množinou možných valencí {3,4}, pak výsledná vyhledávací množina bude rovna sjednocení filtrů 6;-1, 6;1, 6;v3 a 6;v4. Jako druhý příklad si můžeme uvést atom, který může být uhlíkem či dusíkem a je jediný v celém dotazu. Pak výsledná vyhledávací množina bude rovna sjednocení filtrů 6 a 7. Reálná implementace Na konci této části uvedeme několik poznámek k implementaci. Při vytváření vyhledávací množiny bychom mohli mít problém s tím, že máme filtry uložené v paměti ve dvou různých formátech pole bytů a dynamické pole čísel. Proto jsme implementovali funkci listtobyte, která převede dynamické pole čísel na pole bytů (využívá k tomu samozřejmě znalosti o celkovém počtu záznamů v databázi). Při vytváření vyhledávací množiny tedy již pracujeme pouze s poli bytů. Další věc, kterou bychom zde měli zmínit je, že kvůli úspoře paměti nepostupujeme tak, že si pro každý element v grafu (atom, vazbu) nejprve vytvoříme vyhledávací množinu a pak na konci z těchto množin vytvoříme jejich průnik. Ve skutečnosti si v jeden okamžik pamatujeme maximálně 2 vyhledávací množiny. V každém okamžiku si pamatujeme aktuální verzi vyhledávací množiny pro celý dotaz (na začátku je prázdná, přiřadím do ní první dílčí vyhledávací množinu, kterou získám) tuto proměnnou pojmenujeme result. Po vyrobení vyhledávací množiny konkrétního elementu si ihned vyrobíme průnik těchto množin a ten uložíme opět do proměnné result. Stejný postup je při postupném vytváření vyhledávací množiny pro element. Poslední poznámkou, kterou zde zmíníme je, že reálně samozřejmě nepracujeme s množinami, ale s poli bytů. Proto mohou být výše uvedené množinové operace poněkud zavádějící. Ve skutečnosti pochopitelně provádíme logické operace OR pro sjednocení a AND pro průnik. 2.2.6 Vyhledávání dotazu Nyní, když máme podmnožinu databáze, nad kterou se máme dotazovat, již nám nic nebrání v samotném porovnávání SMARTS vzoru a SMILESů z databáze. K tomuto účelu využijeme knihovnu CDK. Tato knihovna má třídy, které většinu práce udělají za nás. Nejprve si vytvoříme instanci třídy SMARTSQueryTool, které dáme do konstruktoru náš SMARTS vzor a instanci třídy SmilesParser. Poté nám již stačí si pro každý záznam z vyhledávací množiny vyrobit pomocí SmilesParser u instanci rozhraní IAtomContainer a zeptat třídy SMARTSQueryTool, zda daná molekula odpovídá našemu dotazu. Formát odpovědi Odpověď budeme formátovat jako JSON a jeho struktura bude následující: 26