Описание задачи
Наш департамент работает с большим количеством информации, которую удобно представлять в виде графа. Это могут быть последовательности аминокислот, связанные с различными видами структур, или же химические реагенты и катализаторы, связанные с помощью реакций с получившимися продуктами.
Оказывается, на рынке уже есть графовая база данных - neo4j. Удобнее всего её установить с помощью Docker образа. Несложно догадаться, что к этой базе данных существует биндинг для Haskell: hasbolt и набор плюшек для него hasbolt-extras. Некоторое представление о работе с этим библиотеками поможет составить доклад, а также их документация на Hackage.
Важно. Начиная с 4-й версии Neo4j полностью поменяли протокол и не все Haskell-библиотеки на него перешли. На текущий момент лучше использовать версию Neo4j 3.5.
- компонент
Molecule
должен иметь поляid :: Int
,smiles :: String
,iupacName :: String
; - компонент
Reaction
должен иметь поляid :: Int
,name :: String
; - компонент
Catalyst
должен иметь поляid :: Int
,smiles :: String
,name :: Maybe String
; - компонент
PRODUCT_FROM
должен иметь полеamount :: Float
; - компонент
ACCELERATE
должен иметь полеtemperature :: Float
,pressure :: Float
; - дополните структуру данных необходимыми на ваш взгляд полями;
- населите базу данных представителями (хотя бы по 20 образцов каждого вида). Данные подразумеваются совершенно синтетические.
Между записями в структурах данных могут быть различные зависимости (например, между представлением smiles и именем IUPAC).
- создайте соответствующие типы в Haskell-библиотеке;
- напишите функцию, которая умеет принимать реацию на вход и загружать её в базу;
- напишите функцию, которая по номеру реакции в базе будет возвращать её в Haskell-объект;
- напишите функцию, которая по двум заданным молекулам ищет путь через реакции и молекулы с наименьшей длиной.
В процессе развития системы будут добавляться различные компоненты, например, механизм реакции. Предлагается ответить на следующие вопросы:
- какие абстракции или вспомогательные компоненты можно ввести на уровне базы данных, чтобы новые полученные знания ладно укладывались в систему?
- какие абстракции вы бы предложили ввести в Haskell-реализацию?
Для работы с базой данных предназначены модули Functions.TextRequest и Functions.GraphRequest.
Модуль Functions.TextRequest формирует Cypher запросы на основе текстовых шаблонов. В его основе лежит Database.Bolt . Модуль экспортирует следующие функции:
- добавление реакции в БД:
putReaction :: ReactionData -> BoltActionT IO (Id Reaction)
- извлечение реакции из БД по указанному Id:
getReaction :: Id Reaction -> BoltActionT IO (Maybe ReactionData)
- Нахождение всех кратчайших путей через Реакции и Молекулы между молекулами:
findShortPath :: Molecule -> Molecule -> BoltActionT IO [Transformation]
- Удаление узла реакции и связей из БД по переданному Id:
deleteReaction :: Id Reaction -> BoltActionT IO ()
- Нахождение всех кратчайших путей через промежуточные Реакции и Молекулы между начальной и конечной Молекулами по переданным Id:
findShortPathById :: Id Molecule -> Id Molecule -> BoltActionT IO [Transformation]
Следует отметить, что для функций findShortPath, findShortPathById важен порядок переданных агрументов, так как происходит поиск пути только в одном направлении. Если требуется найти кратчайший путь в любом из направлений, то следует использовать функцию дважды с изменением порядка агрументов. Если оба агрумента совпадают, то возвращается путь, содержащий только переданную молекулу. Если начальная или конечная Молекулы отсутствуют в базе, то возвращается пустой список Трансформаций.
Модуль Functions.GraphRequest основан на построении запросов из графовых шаблонов. В его основе лежит модуль Database.Bolt.Extras.Graph. Модуль экспортирует только 2 функции:
- добавление реакции в БД:
putReaction :: ReactionData -> BoltActionT IO (Id Reaction)
- извлечение реакции из БД по указанному Id:
getReaction :: Id Reaction -> BoltActionT IO (Maybe ReactionData)
Для генерации данных и наполнения тестовой базы данных используется функция putSampleData из модуля SampleData.
Функция использует данные из файлов Reactions.csv
,Molecules.csv
,Catalyst.scv
в папке SampleData
.
putSampleData :: Int -> BoltActionT IO ()
Ниже приведен пример использования данной функции из модуля Main:
boltCfg :: BoltCfg
boltCfg = def { host = "localhost"
, user = "neo4j"
, password = "testDB"
}
runQueryDB :: BoltActionT IO a -> IO a
runQueryDB act = bracket (connect boltCfg) close (`run` act)
main :: IO ()
main = do
putStrLn "Enter number of generated reactions:"
n <- read <$> getLine
runQueryDB $ putSampleData n
Также модуль экспортирует функции randomReaction
, randomMolecule
, randomCatalyst
, randomProductFrom
, randomAccelerate
для генерации составных частей реакции и randomReactionData
для генерации реакции.
В данной библиотеке используется логика, запрещающая повторное создание или модификацию уже существующей а базе данных реакции. Если в результате генерации реакции будет повторно использоваться уже существующее имя реакции, то такая реакция не будет добавлена в тестовую БД. Это следует учитывать при выборе числа генераций, передаваемых в функцию putSampleData. Обычно 35 генераций достаточно для получения не менее 20 образцов каждого типа.
В структуру базы данных можно добавить компонент-узел Mechanism
и компонент-взаимоотношение DESCRIBES
.
Механизм реакции должен содержать информацию об интермедиатах, переходных состояниях и продуктах реакции. Соответственно возможно введение:
- компонентов группы "Интермедиатов"
- Карбкатион
Carbocation
- Карбанион
Carbanion
- ...
- Нитрен
Nitrene
- Карбкатион
- компонента "Переходного состояния"
TransitionState
- компонента-взаимоотношение
TRANSITION_IN
- компонента-взаимоотношение
INTERMEDIATE_IN
В Haskell можно ввести типы данных
data DESCRIBES = DESCRIBES{..}
data TRANSITION_IN = TRANSITION_IN{..}
data INTERMEDIATE_IN = INTERMEDIATE_IN{..}
data Intermediate = Carbocation{..}
| Carbanion{..}
| ...
| Nitrene{..}
data TransitionState = TransitionState{..}
data Mechanism = Mechanism [(Intermediate, INTERMEDIATE_IN)] [(TransitionState, TRANSITION_IN)]
Тип данных ReactionData
следует дополнить полем Mechanism
data ReactionData = ReactionData
{ rdName :: Name Reaction
, ...
, rdMechanism :: Maybe (Mechanism, DESCRIBES)
}