Building a Regex Search Engine for DNA
benchling.engineering
benchling.engineering
One of the many great features of the Rust regex crate (https://doc.rust-lang.org/regex/regex/index.html) is that there's actually a regex_syntax crate that provides this parsing support (https://doc.rust-lang.org/regex/regex_syntax/index.html), which can be used for purposes like this, to do alternative kinds of matching engines.
Rust's regex library also has pretty good DFA and NFA matching engines, and an Aho-Corasick engine for parts of the regex that can be decomposed into just a set of simple strings to match, as well as some fairly well optimized exact substring search to speed up search for expressions with some substring that must match exactly, so it can be fairly fast for the kinds of matches like your "TAG(A|C|T|G)GG" example.
Another, even more radical change, would be to build a finite state transducer of your data set, which you can then do regex matching on directly by taking a union of an automaton representing the regex and the FST representing your data (which is itself an automaton). I learned about FSTs from the author of the Rust regex crate: http://blog.burntsushi.net/transducers/. An FST is actually used internally for certain types of searches in Elasticsearch/Lucene (https://www.elastic.co/guide/en/elasticsearch/reference/curr...). I don't know if these would be suited to this problem as is by just building an FST from your set of sequences, or maybe using the overlapping subsequences idea to make FSTs from a larger number of shorter strings, but it would be an interesting project to explore.
And yeah, Python is a strict requirement since the rest of our code is in Python - figured it wasn't worth throwing in another language when a rough parser worked.
Your trigram approach is roughly isomorphic to the FST approach. The main benefit of an FST is that it can store a huge number of keys in a very small amount of space that is still searchable without a separate decompression phase. In the case of trigrams with a low alphabet size, an FST probably isn't a big win. But say if you wanted to increase to a bigger N-gram for some reason, then perhaps an FST might help you out. It's true that Rust's fst library has a way to apply regexes to the FST for very fast search (Lucene also has this), but its subjects are terms in the FST, which in your case corresponds to trigrams. This means you still have to go through the "extract a set of trigrams from a regex to pre-filter on" dance.
And yeah, using the regex-syntax crate from Python would have been a fair amount of work. In particular, it still needs a C API before you can use it from Python, which would probably be a fair amount of grunt work.
In any case, as a computational biologist in a former life, this is very cool work. Nice job! :-)
Currently, only viral sequences are supported.
For example, finding all plasmids with a certain primer sequence, specific restriction sites, promoter motif...
hi! resident scientist at Benchling here! searching near-exact sequence can be really useful! for example, sometimes i would like to find if any of my plasmid has a certain signal peptide, it would be so hard without this search algorithm because so many DNA sequences can be translated to the same signal peptide. it would be great if i could just paste the amino acids and i could identify which plasmids contain the signal peptide.
But wouldn't a pre-processing pipeline that either automates annotation of the plasmid based on commonly used motifs (m17 primers, t7, sp6 promoters, fluorescent protein sequences, antibiotic resistence...etc) or requiring the user to annotate their plasmid during submission be more ideal? I would imagine the more common use case is to search for these elements?
I know you mention that this isn't a use case for blast, but it's simple to keep a private blast index to use for cases like this.
i think most people want a sensitive search that has a probabilistic model for substitutions and for indels, ideally with a heuristic so it's fast. BLAST does this, and it also includes support for "profiles" which are probabilistic models (and HMMER has an even more powerful version of said models).
I work in a different domain, and I find custom state machines to the preferred way to do matching over large sets of data. Of course using something like compressed bitmap indices are also helpful.
Do you have a way of generating state machines, or are they hand coded? Is it a perf optimization, or does it help integrating domain-specific logic?
https://swtch.com/~rsc/regexp/regexp1.html
To generate state machines
For C there is re2c http://re2c.org/
For Go there is https://github.com/opennota/re2dfa
My patterns do not change that often, so I end up hand coding them.
If you are looking to continue using regex for matching, I would look at using the TCL regex library as it has some optimizations that other regex libraries do not have.
It seems like every time I learn something new about bioinformatics, it just has more and more overlap with computational linguistics!
As for why we were using ES for the rest of search: we were using it for things like language stemming, matching terms with "slop", things like {'minimum_should_match': '3<67%'} (require exact matches if 3 or less terms, 67% if more than that), searching _all to match anything in the document, etc etc. I think a bunch of these (maybe even all) you can do in PG, but it was way easier to get going with ES.
ES is also distributed, which makes scaling up and doing maintenance a lot easier - a bunch of times we just threw new nodes at it and remove old nodes that had gone bad.
I remember reading this really wonderful article on the levenshtein distance on HN 2-3 months ago.
basically I have difficulty understanding why the Wagner-Fisher's algorithm works - the intuition behind it - I remember it was dynamic programming but I really want to read that article again.
The format was similar to medium with wonderful diagrams :( I tried searching for it on HN but searching on HN is really difficult and it was my own fault for not saving it on Pocket.
I would really appreciate the help since it was one of those "better explained" gems.
Have you tried https://hn.algolia.com/?query=levenshtein&sort=byPopularity&... ?
basically it explained 1-dimentional Levenshtein distance first.
That blew my mind since its much easier to think about the 1d case and then apply it for a 2d matrix.
but i remember reading only half-way through the article before had to do something else :( sorry - but I really want to read it again since it will really help me out at my work !
EDIT -
FOUND IT !
thanks a lot for that ! It was a god send !
http://davedelong.tumblr.com/post/134367865668/edit-distance...
here is the link - you just made my afternoon !
EDIT 2 -
someone should write a book which explains all maths/algorithms concepts using just links to blog posts by most HN popularity - just a though :)
You can peruse his open-source (re)implementation of the approach at https://github.com/google/codesearch .
Furthermore those search terms are so highly specialized that you can easily jit the regex.
Why using the super slow python and not a fast language? I thought DNA matching is a big and important enough problem where you shouldn't toy around with dinosaurs.
[Edit: 3 -> 2 bit]
We use fast languages when we need them. Python is just parsing a regex to generate constraints - for DNA search, parsing a 1000-char regex is super fast and rarely an issue.
In this case, we're already at sub 100ms searches (usually sub 50ms) so I don't see much benefit from playing around with L1 caches and JIT-ing when higher-level structure already gives the perf characteristics we need.
BTW, Go has a very complete RE2 parser: https://golang.org/pkg/regexp/syntax/ - it even handles simplifications for you, I'm actually using it together with a RDPT in namegrep.com.
And in JavaScript you also have ret.JS: https://github.com/fent/ret.js/.