diff --git a/README.md b/README.md
index 4471e86..fadbe91 100644
--- a/README.md
+++ b/README.md
@@ -2,17 +2,18 @@
[![Build Status](https://dev.azure.com/cwzou/mongo-rdkit/_apis/build/status/rdkit.mongo-rdkit?branchName=master)](https://dev.azure.com/cwzou/mongo-rdkit/_build/latest?definitionId=1&branchName=master)
Mongo-rdkit is an integration between MongoDB,
-a NoSQL database platform, and RDKit, a collection of chemoinformatics and machine-learning software.
+a NoSQL database platform, and RDKit, a collection of cheminformatics and machine-learning software.
This package contains tools to create and manipulate a chemically-intelligent database, as well as
methods for high-performance searches on the database that leverage native MongoDB features.
Useful links:
* [BSD License](https://github.com/rdkit/mongo-rdkit/blob/master/LICENSE) - a business friendly license for open-source.
-* [Jupyter Notebooks](https://github.com/rdkit/mongo-rdkit/tree/master/docs) - resources for getting started.
+* [Jupyter Notebooks](https://github.com/rdkit/mongo-rdkit/tree/master/docs) - walkthroughs for main functionality.
+* [Testing Guide](https://github.com/rdkit/mongo-rdkit/blob/master/docs/testing.md) - walkthrough of running `mongordkit` tests.
## Documentation
Jupyter Notebooks and resources for getting started in the [docs](https://github.com/rdkit/mongo-rdkit/tree/master/docs)
-folder on GitHub
+folder on GitHub.
## Installation
As the package is not officially configured with a setup.py file or pushed onto PyPi, these are working install instructions.
@@ -43,9 +44,43 @@ echo $PYTHONPATH
You can now `import mongordkit` in your Python interpreter or run all tests using the `pytest` command.
### Windows:
+Similarly, ensure that `conda` has been added to `PATH`.
+Clone the repository into your desired directory and navigate into it.
+Create a conda environment called mongo_rdkit that includes dependencies:
+```
+conda env create --quiet --force --file env.yml
+```
+Activate this conda environment:
+```
+call activate mongo_rdkit
+```
+Check that you are able to import mongordkit:
+```
+python -c "import mongordkit"
+```
+If this fails, you may need to add the current directory manually to `PYTHONPATH`:
+```
+set PYTHONPATH=%PYTHONPATH%;C:.
+```
+You can now use `mongordkit` in your interpreter and run tests using `python -m pytest`.
+## Package Contents
+### Modules
+`mongordkit` contains two main modules, each of which contains a variety of importable methods and classes.
+`Database` contains functionality for writing and registering data. `Search` contains functionality for setting up and performing
+substructure and similarity search. Detailed walkthroughs can be found in the notebooks, listed below.
+
+### Notebooks
+- **Creating and Writing to MongoDB**: documentation and demos for creating and modifying mongo-rdkit databases.
+- **Similarity and Substructure Search**: documentation and demos for similarity and substructure search.
+- **Similarity Benchmarking**: documentation for reproducing similarity benchmarking.
+- **Substructure Benchmarking**: documentation for reproducing substructure benchmarking.
+### Configuration
+- **azure_pipelines.yml**: CI/CD pipeline configurations.
+- **conftest.py**: `pytest` configurations.
+- **env.yml**: required dependencies.
## License
Code released under the BSD License.
diff --git a/docs/notebooks/.ipynb_checkpoints/.ipynb-checkpoint b/docs/notebooks/.ipynb_checkpoints/.ipynb-checkpoint
deleted file mode 100644
index 80af95b..0000000
--- a/docs/notebooks/.ipynb_checkpoints/.ipynb-checkpoint
+++ /dev/null
@@ -1,92 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Creating and Writing to MongoDB\n",
- "\n",
- "Methods that directly modify MongoDB database instances are included in the `mongordkit.Database` module.\n",
- "\n",
- "\n",
- "\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "metadata": {},
- "outputs": [],
- "source": [
- "from mongordkit.Database import *"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Creating Databases\n",
- "Users can opt to bring their own database instances, but `Database.create` provides a variety of ways to create a `mongordkit`-compatible MongoDB instance:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 13,
- "metadata": {},
- "outputs": [
- {
- "ename": "DuplicateKeyError",
- "evalue": "E11000 duplicate key error collection: MyDatabase.registration index: _id_ dup key: { _id: ObjectId('5f0f64b2eaae47671ad2fb9d') }",
- "output_type": "error",
- "traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
- "\u001b[0;31mDuplicateKeyError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mdb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mDatabase\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreateFromHostPort\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'MyDatabase'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mhost\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mport\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mdb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mDatabase\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreateFromURI\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'MyDatabase'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;32m~/Desktop/mongo-rdkit/mongordkit/Database/create.py\u001b[0m in \u001b[0;36mcreateFromHostPort\u001b[0;34m(dbname, host, port)\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0mdb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mclient\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mdbname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0mcollection\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdb\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'registration'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 19\u001b[0;31m \u001b[0mcollection\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minsert_one\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mSTANDARD_SETTING\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 20\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mdb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 21\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;32m~/anaconda3/envs/py37_rdkit_beta/lib/python3.7/site-packages/pymongo/collection.py\u001b[0m in \u001b[0;36minsert_one\u001b[0;34m(self, document, bypass_document_validation, session)\u001b[0m\n\u001b[1;32m 696\u001b[0m \u001b[0mwrite_concern\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwrite_concern\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 697\u001b[0m \u001b[0mbypass_doc_val\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mbypass_document_validation\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 698\u001b[0;31m session=session),\n\u001b[0m\u001b[1;32m 699\u001b[0m write_concern.acknowledged)\n\u001b[1;32m 700\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;32m~/anaconda3/envs/py37_rdkit_beta/lib/python3.7/site-packages/pymongo/collection.py\u001b[0m in \u001b[0;36m_insert\u001b[0;34m(self, docs, ordered, check_keys, manipulate, write_concern, op_id, bypass_doc_val, session)\u001b[0m\n\u001b[1;32m 610\u001b[0m return self._insert_one(\n\u001b[1;32m 611\u001b[0m \u001b[0mdocs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mordered\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcheck_keys\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmanipulate\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwrite_concern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mop_id\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 612\u001b[0;31m bypass_doc_val, session)\n\u001b[0m\u001b[1;32m 613\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 614\u001b[0m \u001b[0mids\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;32m~/anaconda3/envs/py37_rdkit_beta/lib/python3.7/site-packages/pymongo/collection.py\u001b[0m in \u001b[0;36m_insert_one\u001b[0;34m(self, doc, ordered, check_keys, manipulate, write_concern, op_id, bypass_doc_val, session)\u001b[0m\n\u001b[1;32m 598\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 599\u001b[0m self.__database.client._retryable_write(\n\u001b[0;32m--> 600\u001b[0;31m acknowledged, _insert_command, session)\n\u001b[0m\u001b[1;32m 601\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 602\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdoc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mRawBSONDocument\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;32m~/anaconda3/envs/py37_rdkit_beta/lib/python3.7/site-packages/pymongo/mongo_client.py\u001b[0m in \u001b[0;36m_retryable_write\u001b[0;34m(self, retryable, func, session)\u001b[0m\n\u001b[1;32m 1489\u001b[0m \u001b[0;34m\"\"\"Internal retryable write helper.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1490\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_tmp_session\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msession\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1491\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_retry_with_session\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mretryable\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1492\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1493\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_reset_server\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maddress\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;32m~/anaconda3/envs/py37_rdkit_beta/lib/python3.7/site-packages/pymongo/mongo_client.py\u001b[0m in \u001b[0;36m_retry_with_session\u001b[0;34m(self, retryable, func, session, bulk)\u001b[0m\n\u001b[1;32m 1382\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mlast_error\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1383\u001b[0m \u001b[0mretryable\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1384\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msession\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msock_info\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mretryable\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1385\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mServerSelectionTimeoutError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1386\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mis_retrying\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;32m~/anaconda3/envs/py37_rdkit_beta/lib/python3.7/site-packages/pymongo/collection.py\u001b[0m in \u001b[0;36m_insert_command\u001b[0;34m(session, sock_info, retryable_write)\u001b[0m\n\u001b[1;32m 595\u001b[0m retryable_write=retryable_write)\n\u001b[1;32m 596\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 597\u001b[0;31m \u001b[0m_check_write_command_response\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mresult\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 598\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 599\u001b[0m self.__database.client._retryable_write(\n",
- "\u001b[0;32m~/anaconda3/envs/py37_rdkit_beta/lib/python3.7/site-packages/pymongo/helpers.py\u001b[0m in \u001b[0;36m_check_write_command_response\u001b[0;34m(result)\u001b[0m\n\u001b[1;32m 219\u001b[0m \u001b[0mwrite_errors\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"writeErrors\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 220\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mwrite_errors\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 221\u001b[0;31m \u001b[0m_raise_last_write_error\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mwrite_errors\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 222\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 223\u001b[0m \u001b[0merror\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"writeConcernError\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;32m~/anaconda3/envs/py37_rdkit_beta/lib/python3.7/site-packages/pymongo/helpers.py\u001b[0m in \u001b[0;36m_raise_last_write_error\u001b[0;34m(write_errors)\u001b[0m\n\u001b[1;32m 200\u001b[0m \u001b[0merror\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mwrite_errors\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 201\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"code\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m11000\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 202\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mDuplicateKeyError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merror\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"errmsg\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m11000\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 203\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mWriteError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merror\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"errmsg\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"code\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 204\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;31mDuplicateKeyError\u001b[0m: E11000 duplicate key error collection: MyDatabase.registration index: _id_ dup key: { _id: ObjectId('5f0f64b2eaae47671ad2fb9d') }"
- ]
- }
- ],
- "source": [
- "db = Database.create.createFromHostPort('MyDatabase', host=None, port=None)\n",
- "db = Database.create.createFromURI('MyDatabase', url=None)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "py37_rdkit_beta",
- "language": "python",
- "name": "py37_rdkit_beta"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.7"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/docs/notebooks/.ipynb_checkpoints/Creating and Writing to MongoDB-checkpoint.ipynb b/docs/notebooks/.ipynb_checkpoints/Creating and Writing to MongoDB-checkpoint.ipynb
deleted file mode 100644
index 0dd6819..0000000
--- a/docs/notebooks/.ipynb_checkpoints/Creating and Writing to MongoDB-checkpoint.ipynb
+++ /dev/null
@@ -1,199 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Creating and Writing to MongoDB\n",
- "\n",
- "Last updated: 7/12/20\n",
- "\n",
- "Methods that directly modify MongoDB database instances are included in the `mongordkit.Database` module.\n",
- "\n",
- "\n",
- "\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from mongordkit.Database import create, write, utils\n",
- "import pymongo"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Reset Cells\n",
- "Run the contents of this cell to reset the local MongoDB database used in this notebook."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "client = pymongo.MongoClient()\n",
- "print(client.list_database_names())\n",
- "client.drop_database('TestDatabase')\n",
- "print(client.list_database_names())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Creating Databases\n",
- "Users can opt to bring their own database instances, but `Database.create` provides methods that will create ready-made MongoDB instances, defaulting to your local MongoDB:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Return a database using a host port, such as the local port:\n",
- "TestDB = create.createFromHostPort('TestDatabase', host='localhost', port=27017)\n",
- "\n",
- "# Return a database using a MongoDB URI, such as that provided by Atlas:\n",
- "TestDB = create.createFromURL('TestDatabase', url=None)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "These databases are created with a `registration` collection. The registration collection includes several documents that consist of common pre-made settings, with the default being `STANDARD_SETTING`. All settings are documented in `Database.utils`."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print(utils.STANDARD_SETTING)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Writing to a Database\n",
- "`Database.write` provides write functionality. Its core method is `writeFromSDF`, which relies on rdkit's `ForwardSDMolSupplier` to write data from an SDF file into a specified database.\n",
- "\n",
- "For each molecule in the SDF, `writeFromSDF` inserts a document containing at the minimum a unique identifying index, that molecule's SMILES, a pickle of the molecule's rdmol, and a field that specifies the registration option used to store the molecule."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Write the contents of first_200_props.sdf, a test dataset, into the TestDatabase created above. \n",
- "# The index will default to the molecule's inchikey.\n",
- "# Return the number of molecules succesfully imported.\n",
- "write.writeFromSDF(TestDB, '../../data/test_data/first_200.props.sdf', 'test')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The above call is the most basic version of `writeFromSDF`. For additional flexibility, `writeFromSDF` takes several optional arguments that allow users to specify how inbound molecules should be standardized, a field relating to the data's origin, customize the index, and change how many molecules are inserted into the database at a time. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Write the contents of first_200_props.sdf, a test dataset, into the TestDatabase created above. \n",
- "# This write will use canonical SMILES as the identifying index and thus does not conflict with the above write. \n",
- "# If we had used inchikey again, the write would have imported 0 molecules.\n",
- "write.writeFromSDF(TestDB, '../../data/test_data/first_200.props.sdf', 'test', reg_option='standard_setting', index_option='canonical_smiles', chunk_size=100, limit=None)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "In order to maintain consistency, the registration options and index options are drawn from a set of predetermined options specified in `Database.utils`."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## `.create` Module Contents\n",
- "\n",
- "mongordkit.Database.create.**createFromHostPort**(database, host=None (*string*), port=None (*string*))\n",
- "\n",
- "mongordkit.Database.create.**createFromURL**(database, url=None (*string*))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## `.write` Module Contents"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "mongordkit.Database.write.**writeFromSDF**(database, source_sdf, source_name *(string)*, reg_option=\"standard_setting\", index_option=\"inchikey\", chunk_size=100, limit=None)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As of 7/15/20, `writeFromSDF` supports the following registration options: \n",
- "* 'standard_setting'\n",
- "\n",
- "And the following index options: \n",
- "* 'inchikey'\n",
- "* 'canonical_smiles'\n",
- "* 'het_atom_tautomer'"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "py37_rdkit_beta",
- "language": "python",
- "name": "py37_rdkit_beta"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.7"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/docs/notebooks/.ipynb_checkpoints/Similarity Testing-checkpoint.ipynb b/docs/notebooks/.ipynb_checkpoints/Similarity Testing-checkpoint.ipynb
deleted file mode 100644
index c2a351f..0000000
--- a/docs/notebooks/.ipynb_checkpoints/Similarity Testing-checkpoint.ipynb
+++ /dev/null
@@ -1,244 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Similarity Search Benchmarking"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {},
- "outputs": [],
- "source": [
- "import mongordkit\n",
- "import time\n",
- "import pymongo\n",
- "import rdkit\n",
- "import matplotlib\n",
- "import matplotlib.pyplot as plt\n",
- "import numpy as np\n",
- "import pandas as pd\n",
- "from rdkit import Chem\n",
- "from statistics import mean\n",
- "import mongomock\n",
- "from rdkit.Chem import AllChem\n",
- "from mongordkit.Database import write\n",
- "from mongordkit.Search import similarity"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "populating mongodb collection with compounds from chembl...\n",
- "inserted chunk...\n",
- "inserted chunk...\n",
- "200 molecules successfully imported\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "200"
- ]
- },
- "execution_count": 2,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "#Create a mongomock database instance and write to it. \n",
- "client = mongomock.MongoClient()\n",
- "db = client.db\n",
- "\n",
- "#Write 200 molecules into the database\n",
- "write.writeFromSDF(db, '../../data/test_data/first_200.props.sdf', 'test')"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [],
- "source": [
- "#Add Morgan fingerprints into the database\n",
- "similarity.addMorganFingerprints(db)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[[1.0, 'CC1=CC(=O)C=CC1=O']]"
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "#Check that similarity search is working, at least for one molecule. \n",
- "doc = db.molecules.find_one()\n",
- "m = Chem.Mol(doc['rdmol'])\n",
- "results = similarity.similaritySearch(m, db, .8)\n",
- "results"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 14,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "populating mongodb collection with compounds from chembl...\n",
- "The specified setting does not exist. Will only insert default molecules\n",
- "inserted chunk...\n",
- "inserted chunk...\n",
- "1000 molecules successfully imported\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "1000"
- ]
- },
- "execution_count": 14,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "#Create a regular mongoDB database instance and write the first 1000 molecules to it. \n",
- "client = pymongo.MongoClient()\n",
- "db = client.db\n",
- "db.molecules.drop()\n",
- "db.mfp_counts.drop()\n",
- "write.writeFromSDF(db, '../../../chembl_27.sdf', 'test', reg_option='inchikey', index_option='inchikey', chunk_size=500, limit=500)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 26,
- "metadata": {},
- "outputs": [],
- "source": [
- "similarity.addMorganFingerprints(db)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Measuring performance for similarity threshold 0.7.\n"
- ]
- }
- ],
- "source": [
- "#Run benchmarks for similarity search with and without aggregation parameters. \n",
- "thresholds = [0.7, 0.75, 0.8, 0.85, 0.9, 0.95]\n",
- "times = []\n",
- "repetitions = 5\n",
- "for t in thresholds: \n",
- " print(\"Measuring performance for similarity threshold {}.\".format(t))\n",
- " temp_times = []\n",
- " for r in range(repetitions):\n",
- " start = time.time()\n",
- " for m in db.molecules.find():\n",
- " mol = Chem.Mol(m['rdmol'])\n",
- " similarity.similaritySearchAggregate(mol, db, t)\n",
- " print('working')\n",
- " end = time.time()\n",
- " temp_times.append(end - start)\n",
- " times.append([t, mean(temp_times)])\n",
- "print(times)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 25,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[]"
- ]
- },
- "execution_count": 25,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "x_list = [v[0] for v in times]\n",
- "y_list = [v[1]*1000 for v in times]\n",
- "plt.xlabel('thresholds')\n",
- "plt.ylabel('time (ms)')\n",
- "plt.plot(x_list, y_list)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "py37_rdkit_beta",
- "language": "python",
- "name": "py37_rdkit_beta"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.7"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/docs/notebooks/.ipynb_checkpoints/Similarity and Substructure Search-checkpoint.ipynb b/docs/notebooks/.ipynb_checkpoints/Similarity and Substructure Search-checkpoint.ipynb
deleted file mode 100644
index 9e5d205..0000000
--- a/docs/notebooks/.ipynb_checkpoints/Similarity and Substructure Search-checkpoint.ipynb
+++ /dev/null
@@ -1,318 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Similarity and Substructure Search\n",
- "\n",
- "Last updated: 7/11/20\n",
- "\n",
- "Methods for similarity and substructure search are included in the `mongordkit.Search` module."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [],
- "source": [
- "from mongordkit.Search import similarity, substructure, utils\n",
- "from mongordkit.Database import create, write\n",
- "from rdkit import Chem\n",
- "import pymongo"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Reset Cells\n",
- "\n",
- "Run these cells to reset the local MongoDB instance used in this notebook."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "['TestDatabase', 'admin', 'config', 'db', 'local']\n",
- "['admin', 'config', 'db', 'local']\n"
- ]
- }
- ],
- "source": [
- "client = pymongo.MongoClient()\n",
- "print(client.list_database_names())\n",
- "client.drop_database('TestDatabase')\n",
- "print(client.list_database_names())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Similarity Search\n",
- "\n",
- "`mongordkit.Search.similarity` supports similarity search best on a database prepared by `mongordkit.Database.write`. Users can also use any database that has a `molecules` collection where each document in that collection has the following fields:\n",
- "- `'rdmol': binary pickle object`\n",
- "- `'smiles': some SMILES string`"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's run through an example of similarity search. First, we'll have to set up our database:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "populating mongodb collection with compounds from chembl...\n",
- "200 molecules successfully imported\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "200"
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "TestDB = create.createFromHostPort('TestDatabase', host='localhost', port=27017)\n",
- "write.writeFromSDF(TestDB, '../../data/test_data/first_200.props.sdf', 'test')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "`similarity.SimSearchNaive` will directly loop through the database and display results. However, this implementation is extremely slow for any decently-sized database. Instead, `similarity` supports precalculating the following kinds of fingerprints for screening: \n",
- "- Morgan (length 1048)\n",
- "\n",
- "through `similarity.addMorganFingerprints`. For each document in a passed in database's `molecules` collection, this method creates a nested field that contains `{morgan_fp: {bits: }, {count: }}`. Note that `addMorganFingerprints` also creates indices on `morgan_fp[bits]` and `morgan_fp[count]` to speed search. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [],
- "source": [
- "similarity.addMorganFingerprints(TestDB, radius=2, length=1024)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "{'bits': [33,\n",
- " 56,\n",
- " 84,\n",
- " 130,\n",
- " 313,\n",
- " 314,\n",
- " 356,\n",
- " 547,\n",
- " 650,\n",
- " 698,\n",
- " 744,\n",
- " 747,\n",
- " 849,\n",
- " 853,\n",
- " 967],\n",
- " 'count': 15}"
- ]
- },
- "execution_count": 6,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "TestDB.molecules.find_one()['morgan_fp']"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "From here, we can directly perform similarity search. `similarity` provides two methods that take advantage of fingerprint screening: `similaritySearch` and `similaritySearchAggregate`. The latter shifts much of the computation into the MongoDB server by using an aggregation pipeline and may improve performance when working with performant or sharded MongoDB servers. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 19,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "similaritySearch: [[0.35294117647058826, 'c1ccc(P(c2ccccc2)c2ccccc2)cc1'], [0.4117647058823529, 'Cc1ccc(S)cc1'], [0.35, 'CC(O)(c1ccccc1)c1ccccc1']]\n",
- "\n",
- "\n",
- "similaritySearchAggregate: [[0.35294117647058826, 'c1ccc(P(c2ccccc2)c2ccccc2)cc1'], [0.4117647058823529, 'Cc1ccc(S)cc1'], [0.35, 'CC(O)(c1ccccc1)c1ccccc1']]\n"
- ]
- }
- ],
- "source": [
- "q_mol = Chem.MolFromSmiles('Cc1ccccc1')\n",
- "\n",
- "# Perform a similarity search on TestDB for q_mol with a Tanimoto threshold of 0.4. \n",
- "results1 = similarity.similaritySearch(q_mol, TestDB, 0.35)\n",
- "\n",
- "# Do the same thing, but use the MongoDB Aggregation Pipeline. \n",
- "results2 = similarity.similaritySearchAggregate(q_mol, TestDB, 0.35)\n",
- "\n",
- "print('similaritySearch: {}'.format(results1))\n",
- "print('\\n')\n",
- "print('similaritySearchAggregate: {}'.format(results2))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Substructure Search\n",
- "\n",
- "Likewise, `mongordkit.Search.substructure` supports substructure search best on databases prepared by `write`. Database requirements are identical to those for similarity search: a `molecules` collection whose documents have `rdmol` and `smiles` fields. \n",
- "\n",
- "`substructure.SubSearchNaive` provides a fingerprint-less, slower implementation of substructure search suitable for very small databases:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "['c1ccc(-c2ccccc2OCCOc2ccccc2-c2ccccc2)cc1',\n",
- " 'COc1ccc(Cc2ccc(OC)cc2)cc1',\n",
- " 'COc1cc([N+](=O)[O-])c(N)c([N+](=O)[O-])c1',\n",
- " 'COc1ccc(/C=N/O)cc1',\n",
- " 'Cc1nc2ccccc2c(Oc2ccccc2)c1-c1ccccc1',\n",
- " 'O/N=C/c1ccc2c(c1)OCO2',\n",
- " 'COc1ccc(CC#N)cc1',\n",
- " 'COc1ccc(C(C)(C)C#N)cc1']"
- ]
- },
- "execution_count": 27,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "q_mol = Chem.MolFromSmiles('C1=CC=CC=C1OC')\n",
- "\n",
- "# Perform a substructure search for q_mol on TestDB. \n",
- "substructure.SubSearchNaive(q_mol, TestDB, chirality=False)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "By adding pattern fingerprints, which are optimized for substructure search, we can use `substructure.SubSearch`, which takes advantage of fingerprint screening to avoid as many expensive calls to `HasSubstructMatch` as possible. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {},
- "outputs": [
- {
- "ename": "NameError",
- "evalue": "name 'substructure' is not defined",
- "output_type": "error",
- "traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
- "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0msubstructure\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAddPatternFingerprints\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mTestDB\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmolecules\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTestDB\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmorgan_fp_counts\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlength\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0msubstructure\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mSubSearch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mq_mol\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTestDB\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mchirality\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;31mNameError\u001b[0m: name 'substructure' is not defined"
- ]
- }
- ],
- "source": [
- "substructure.AddPatternFingerprints(TestDB.molecules, TestDB.mfp_counts, length=None)\n",
- "substructure.SubSearch(q_mol, TestDB, chirality=False)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## `.similarity` Contents\n",
- "\n",
- "mongordkit.Search.similarity.**AddMorganFingerprints**(mol_collection (*MongoDB collection*), count_collection (*MongoDB collection*), radius=2 (*int: radius of Morgan fingerprint*), length=2048 (*int: length of Morgan fingerprint bit vector*)) --> None\n",
- "\n",
- "mongordkit.Search.similarity.**SimSearchNaive**(mol (*rdmol object*), mol_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, smiles]*\n",
- "\n",
- "mongordkit.Search.similarity.**SimSearch**(mol (*rdmol object*), mol_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, smiles]*\n",
- "\n",
- "mongordkit.Search.similarity.**SimSearchAggregate**(mol (*rdmol object*), mol_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, smiles]*\n",
- "\n",
- "mongordkit.Search.**AddRandPermutations**(perm_collection (*MongoDB collection), len=2048, num=100) --> None\n",
- "\n",
- "mongordkit.Search.similarity.**SimSearchLSH**(mol (*rdmol object*), db (*MongoDB database containing hash collections*), mol_collection (*MongoDB collection*), perm_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, smiles]*"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## `.substructure` Contents\n",
- "\n",
- "mongordkit.Search.substructure.**AddPatternFingerprints**(db, length=2048 (*int: length of Pattern fingerprint bit vector*)) --> None\n",
- "\n",
- "mongordkit.Search.similarity.**SubSearchNaive**(pattern (*rdmol object*), db, chirality=False (*boolean: include chirality in search or not*)) --> *list: results with format [smiles]*\n",
- "\n",
- "mongordkit.Search.similarity.**SubSearch**(pattern (*rdmol object*), db, chirality=False (*boolean: include chirality in search or not*)) --> *list: results with format [smiles]*"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "py37_rdkit_beta",
- "language": "python",
- "name": "py37_rdkit_beta"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.7"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/docs/notebooks/Creating and Writing to MongoDB.ipynb b/docs/notebooks/Creating and Writing to MongoDB.ipynb
index 91e557e..fdbf379 100644
--- a/docs/notebooks/Creating and Writing to MongoDB.ipynb
+++ b/docs/notebooks/Creating and Writing to MongoDB.ipynb
@@ -6,7 +6,7 @@
"source": [
"# Creating and Writing to MongoDB\n",
"\n",
- "Last updated: 7/12/20\n",
+ "Last updated: 8/10/20\n",
"\n",
"Methods that directly modify MongoDB database instances are included in the `mongordkit.Database` module.\n",
"\n",
@@ -16,11 +16,12 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
- "from mongordkit.Database import create, write, utils\n",
+ "from mongordkit.Database import create, write, utils, registration\n",
+ "from rdkit import Chem\n",
"import pymongo"
]
},
@@ -29,26 +30,28 @@
"metadata": {},
"source": [
"## Reset Cells\n",
- "Run the contents of this cell to reset the local MongoDB database used in this notebook."
+ "Run the contents of this cell to reset the local MongoDB database, `demo_db`, used in this notebook."
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"client = pymongo.MongoClient()\n",
- "print(client.list_database_names())\n",
- "client.drop_database('TestDatabase')\n",
- "print(client.list_database_names())"
+ "client.drop_database('demo_db')\n",
+ "demo_db = client.demo_db\n",
+ "\n",
+ "# Disable rdkit warnings\n",
+ "rdkit.RDLogger.DisableLog('rdApp.*')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Creating Databases\n",
+ "## Creating Databases (DEPRECATED for now)\n",
"Users can opt to bring their own database instances, but `Database.create` provides methods that will create ready-made MongoDB instances, defaulting to your local MongoDB:"
]
},
@@ -58,11 +61,11 @@
"metadata": {},
"outputs": [],
"source": [
- "# Return a database using a host port, such as the local port:\n",
- "TestDB = create.createFromHostPort('TestDatabase', host='localhost', port=27017)\n",
+ "# # Return a database using a host port, such as the local port:\n",
+ "# db = create.createFromHostPort('demo_db', host='localhost', port=27017)\n",
"\n",
- "# Return a database using a MongoDB URI, such as that provided by Atlas:\n",
- "TestDB = create.createFromURL('TestDatabase', url=None)"
+ "# # Return a database using a MongoDB URI, such as that provided by Atlas:\n",
+ "# TestDB = create.createFromURL('demo_db', url=None)"
]
},
{
@@ -78,7 +81,140 @@
"metadata": {},
"outputs": [],
"source": [
- "print(utils.STANDARD_SETTING)"
+ "# print(utils.STANDARD_SETTING)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Data Registration\n",
+ "`Database.registration` constructs document representations of molecules according to configurable schemes and handles data registration settings.\n",
+ "\n",
+ "It does this in two parts. First, it defines the global variable `HASH_FUNCTIONS` as a dictionary that maps hash function names to methods. It also defines the global variables `DEFAULT_SCHEME_NAME`, `DEFAULT_AUTHOR`, `DEFAULT_PREPROCESS`, and `DEFAULT_INDEX`, which are used in scheme creation and are thus defined for easy configuration. \n",
+ "\n",
+ "Second, the file defines the `MolDocScheme` object, which stores scheme information in its instance variables and is passed into `.write` methods in order to specify molecule document format. By default, `MolDocScheme` includes scheme name, author, whether or not the molecule has been pre-processed, an index option, two hashes, fingerprints, and value fields. All of the information contained in a `MolDocScheme` object can be used directly to generate documents for molecules:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'rdmol': Binary(b'\\xef\\xbe\\xad\\xde\\x00\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x07\\x00\\x00\\x00\\x07\\x00\\x00\\x00\\x80\\x01\\x06\\x00`\\x00\\x00\\x00\\x01\\x03\\x06@(\\x00\\x00\\x00\\x03\\x04\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x0b\\x00\\x01\\x00\\x01\\x02h\\x0c\\x02\\x03h\\x0c\\x03\\x04h\\x0c\\x04\\x05h\\x0c\\x05\\x06h\\x0c\\x06\\x01h\\x0c\\x14\\x01\\x06\\x01\\x06\\x05\\x04\\x03\\x02\\x17\\x00\\x00\\x00\\x00\\x16', 0),\n",
+ " 'index': 'YXFVVABEGXRONW-UHFFFAOYSA-N',\n",
+ " 'smiles': 'Cc1ccccc1',\n",
+ " 'scheme': 'default',\n",
+ " 'hashes': {'MolFormula': 'C7H8',\n",
+ " 'SmallWorldIndexBRL': 'B7R1L5',\n",
+ " 'AtomBondCounts': '7,7',\n",
+ " 'cx_smiles': 'Cc1ccccc1',\n",
+ " 'NetCharge': '0',\n",
+ " 'CanonicalSmiles': 'Cc1ccccc1',\n",
+ " 'inchikey_standard': 'YXFVVABEGXRONW-UHFFFAOYSA-N',\n",
+ " 'inchikey_KET_15T': 'YXFVVABEGXRONW-UHFFFAOYNA-N',\n",
+ " 'SmallWorldIndexBR': 'B7R1',\n",
+ " 'DegreeVector': '0,1,5,1',\n",
+ " 'ElementGraph': 'CC1CCCCC1',\n",
+ " 'HetAtomTautomer': 'C[C]1[CH][CH][CH][CH][CH]1_0_0',\n",
+ " 'inchi_standard': 'InChI=1S/C7H8/c1-7-5-3-2-4-6-7/h2-6H,1H3',\n",
+ " 'RedoxPair': 'C[C]1[CH][CH][CH][CH][CH]1',\n",
+ " 'AnonymousGraph': '**1*****1',\n",
+ " 'Mesomer': 'C[C]1[CH][CH][CH][CH][CH]1_0',\n",
+ " 'Regioisomer': '*C.c1ccccc1',\n",
+ " 'inchi_KET_15T': 'InChI=1/C7H8/c1-7-5-3-2-4-6-7/h2-6H,1H3',\n",
+ " 'MurckoScaffold': 'c1ccccc1',\n",
+ " 'ArthorSubstructureOrder': '00070007010007000000002a000000',\n",
+ " 'noiso_smiles': 'Cc1ccccc1',\n",
+ " 'ExtendedMurcko': '*c1ccccc1',\n",
+ " 'HetAtomProtomer': 'C[C]1[CH][CH][CH][CH][CH]1_0'},\n",
+ " 'fingerprints': {},\n",
+ " 'value_data': {}}"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "rdmol = Chem.MolFromSmiles('Cc1ccccc1')\n",
+ "scheme = registration.MolDocScheme()\n",
+ "scheme.generate_mol_doc(rdmol)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `MolDocScheme` class also defines a series of instance methods, such as `MolDocScheme.set_index` and `MolDocScheme.remove_field`, that can be used to modify document schemes:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "removed AnonymousGraph from scheme\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "{'rdmol': Binary(b'\\xef\\xbe\\xad\\xde\\x00\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x07\\x00\\x00\\x00\\x07\\x00\\x00\\x00\\x80\\x01\\x06\\x00`\\x00\\x00\\x00\\x01\\x03\\x06@(\\x00\\x00\\x00\\x03\\x04\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x0b\\x00\\x01\\x00\\x01\\x02h\\x0c\\x02\\x03h\\x0c\\x03\\x04h\\x0c\\x04\\x05h\\x0c\\x05\\x06h\\x0c\\x06\\x01h\\x0c\\x14\\x01\\x06\\x01\\x06\\x05\\x04\\x03\\x02\\x17\\x00\\x00\\x00\\x00\\x16', 0),\n",
+ " 'index': 'C7H8',\n",
+ " 'smiles': 'Cc1ccccc1',\n",
+ " 'scheme': 'default',\n",
+ " 'hashes': {'MolFormula': 'C7H8',\n",
+ " 'SmallWorldIndexBRL': 'B7R1L5',\n",
+ " 'AtomBondCounts': '7,7',\n",
+ " 'cx_smiles': 'Cc1ccccc1',\n",
+ " 'NetCharge': '0',\n",
+ " 'CanonicalSmiles': 'Cc1ccccc1',\n",
+ " 'inchikey_standard': 'YXFVVABEGXRONW-UHFFFAOYSA-N',\n",
+ " 'inchikey_KET_15T': 'YXFVVABEGXRONW-UHFFFAOYNA-N',\n",
+ " 'SmallWorldIndexBR': 'B7R1',\n",
+ " 'DegreeVector': '0,1,5,1',\n",
+ " 'ElementGraph': 'CC1CCCCC1',\n",
+ " 'HetAtomTautomer': 'C[C]1[CH][CH][CH][CH][CH]1_0_0',\n",
+ " 'inchi_standard': 'InChI=1S/C7H8/c1-7-5-3-2-4-6-7/h2-6H,1H3',\n",
+ " 'RedoxPair': 'C[C]1[CH][CH][CH][CH][CH]1',\n",
+ " 'Mesomer': 'C[C]1[CH][CH][CH][CH][CH]1_0',\n",
+ " 'Regioisomer': '*C.c1ccccc1',\n",
+ " 'inchi_KET_15T': 'InChI=1/C7H8/c1-7-5-3-2-4-6-7/h2-6H,1H3',\n",
+ " 'MurckoScaffold': 'c1ccccc1',\n",
+ " 'ArthorSubstructureOrder': '00070007010007000000002a000000',\n",
+ " 'noiso_smiles': 'Cc1ccccc1',\n",
+ " 'ExtendedMurcko': '*c1ccccc1',\n",
+ " 'HetAtomProtomer': 'C[C]1[CH][CH][CH][CH][CH]1_0'},\n",
+ " 'fingerprints': {},\n",
+ " 'value_data': {}}"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "scheme.remove_field('CanonicalSmiles')\n",
+ "scheme.add_hash_field('MolFormula')\n",
+ "scheme.set_index('MolFormula')\n",
+ "scheme.generate_mol_doc(rdmol)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Because `MolDocScheme` objects contain no functions—only references to functions—they can be pickled. In fact, the methods in `write` can save `MolDocSchemes` so that custom schemes are retrievable for later use."
]
},
{
@@ -86,47 +222,288 @@
"metadata": {},
"source": [
"## Writing to a Database\n",
- "`Database.write` provides write functionality. Its core method is `writeFromSDF`, which relies on rdkit's `ForwardSDMolSupplier` to write data from an SDF file into a specified database.\n",
+ "`Database.write` provides write functionality. Its core method is `WriteFromSDF`, which relies on rdkit's `ForwardSDMolSupplier` to write data from an SDF file into a specified database.\n",
"\n",
- "For each molecule in the SDF, `writeFromSDF` inserts a document containing at the minimum a unique identifying index, that molecule's SMILES, a pickle of the molecule's rdmol, and a field that specifies the registration option used to store the molecule."
+ "For each molecule in the SDF, `WriteFromSDF` inserts a document whose fields are specified by the `MolDocScheme` object passed into the function (one with default settings is created if the `scheme` argument is left blank)."
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 5,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "populating mongodb collection with compounds from SDF...\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:46] WARNING: Accepted unusual valence(s): Cu(4); Metal was disconnected\n",
+ "RDKit WARNING: [15:39:46] WARNING: Accepted unusual valence(s): Cu(4); Metal was disconnected\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Accepted unusual valence(s): Cu(4); Metal was disconnected; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Accepted unusual valence(s): Cu(4); Metal was disconnected; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "200 molecules successfully imported\n",
+ "0 duplicates skipped\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "200"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "# Write the contents of first_200_props.sdf, a test dataset, into the TestDatabase created above. \n",
+ "# Write the contents of first_200_props.sdf, a test dataset, into the collection demo_db.molecules.\n",
"# The index will default to the molecule's inchikey.\n",
"# Return the number of molecules succesfully imported.\n",
- "write.writeFromSDF(TestDB, '../../data/test_data/first_200.props.sdf', 'test')"
+ "write.WriteFromSDF(demo_db.molecules, '../../data/test_data/first_200.props.sdf')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "The above call is the most basic version of `writeFromSDF`. For additional flexibility, `writeFromSDF` takes several optional arguments that allow users to specify how inbound molecules should be standardized, a field relating to the data's origin, customize the index, and change how many molecules are inserted into the database at a time. "
+ "The above call is the most basic version of `writeFromSDF`. For additional flexibility, `writeFromSDF` takes several optional arguments—users can specify a custom scheme object, a registration collection to write scheme objects to, how many molecules are inserted at a time (this can affect performance), and limit the number of molecules written in."
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 6,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "populating mongodb collection with compounds from SDF...\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:47] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:48] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:48] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:48] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:48] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:48] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:48] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:48] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:48] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:48] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:48] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:39:50] WARNING: Accepted unusual valence(s): Cu(4); Metal was disconnected\n",
+ "RDKit WARNING: [15:39:50] WARNING: Accepted unusual valence(s): Cu(4); Metal was disconnected\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:39:50] WARNING: Omitted undefined stereo\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "100 molecules successfully imported\n",
+ "0 duplicates skipped\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "100"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "# Write the contents of first_200_props.sdf, a test dataset, into the TestDatabase created above. \n",
+ "# Write the first 100 molecules of first_200_props.sdf, a test dataset, into demo_db.molecules\n",
"# This write will use canonical SMILES as the identifying index and thus does not conflict with the above write. \n",
"# If we had used inchikey again, the write would have imported 0 molecules.\n",
- "write.writeFromSDF(TestDB, '../../data/test_data/first_200.props.sdf', 'test', reg_option='standard_setting', index_option='canonical_smiles', chunk_size=100, limit=None)"
+ "scheme = registration.MolDocScheme()\n",
+ "scheme.set_index('CanonicalSmiles')\n",
+ "write.WriteFromSDF(demo_db.molecules, '../../data/test_data/first_200.props.sdf', \n",
+ " scheme, reg_collection=demo_db.schema, chunk_size=50, limit=100)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "In order to maintain consistency, the registration options and index options are drawn from a set of predetermined options specified in `Database.utils`."
+ "In the case that users aren't working with an SDF, `.write` also provides `WriteFromMolList`, which will take a Python list of rdmol objects in place of the SDF argument in `WriteFromSDF`."
]
},
{
@@ -144,27 +521,91 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "## `.write` Module Contents"
+ "## `.registration` Module Contents"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'AnonymousGraph': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.AnonymousGraph)>,\n",
+ " 'ElementGraph': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.ElementGraph)>,\n",
+ " 'CanonicalSmiles': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.CanonicalSmiles)>,\n",
+ " 'MurckoScaffold': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.MurckoScaffold)>,\n",
+ " 'ExtendedMurcko': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.ExtendedMurcko)>,\n",
+ " 'MolFormula': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.MolFormula)>,\n",
+ " 'AtomBondCounts': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.AtomBondCounts)>,\n",
+ " 'DegreeVector': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.DegreeVector)>,\n",
+ " 'Mesomer': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.Mesomer)>,\n",
+ " 'HetAtomTautomer': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.HetAtomTautomer)>,\n",
+ " 'HetAtomProtomer': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.HetAtomProtomer)>,\n",
+ " 'RedoxPair': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.RedoxPair)>,\n",
+ " 'Regioisomer': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.Regioisomer)>,\n",
+ " 'NetCharge': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.NetCharge)>,\n",
+ " 'SmallWorldIndexBR': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.SmallWorldIndexBR)>,\n",
+ " 'SmallWorldIndexBRL': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.SmallWorldIndexBRL)>,\n",
+ " 'ArthorSubstructureOrder': (rdmol, f=rdkit.Chem.rdMolHash.HashFunction.ArthorSubstructureOrder)>,\n",
+ " 'inchi_standard': ,\n",
+ " 'inchikey_standard': ,\n",
+ " 'inchi_KET_15T': (rdmol)>,\n",
+ " 'inchikey_KET_15T': (rdmol)>,\n",
+ " 'noiso_smiles': (rdmol)>,\n",
+ " 'cx_smiles': }"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "registration.HASH_FUNCTIONS"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "mongordkit.Database.write.**writeFromSDF**(database, source_sdf, source_name *(string)*, reg_option=\"standard_setting\", index_option=\"inchikey\", chunk_size=100, limit=None) --> *int: number of molecules imported*"
+ "**Class** mongordkit.Database.registration.**MolDocScheme()**\n",
+ "\n",
+ "**Instance variables**:\n",
+ "```\n",
+ "self.scheme_name = DEFAULT_SCHEME_NAME\n",
+ "self.author = DEFAULT_AUTHOR\n",
+ "self.pre_processed = DEFAULT_PREPROCESS\n",
+ "self.index_option = DEFAULT_INDEX\n",
+ "self.hashes = set(HASH_FUNCTIONS.keys())\n",
+ "self.fingerprints = {}\n",
+ "self.value_fields = {}\n",
+ "```\n",
+ "**Instance methods**:\n",
+ "- set_index(self, new_index) --> *None*\n",
+ "- get_index_value(self, rdmol) --> *calculated index value*\n",
+ "- add_hash_field(self, field_name, field_method) --> *None*\n",
+ "- add_value_field(self, field_name, field_value) --> *None*\n",
+ "- add_all_hashes(self) --> *None*\n",
+ "- remove_field(self, field_name) --> *None*\n",
+ "- generate_mol_doc(self, rdmol) --> *Dict: document representing molecule according to scheme*"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## `.write` Module Contents"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "As of 7/15/20, `writeFromSDF` supports the following registration options: \n",
- "* 'standard_setting'\n",
+ "mongordkit.Database.write.**WriteFromSDF**(database, sdf, scheme=MolDocScheme(), reg_collection=None, chunk_size=100, limit=None, warnings=False (*Make this true to turn on rdkit warnings*) --> *int: number of molecules imported*\n",
"\n",
- "And the following index options: \n",
- "* 'inchikey'\n",
- "* 'canonical_smiles'\n",
- "* 'het_atom_tautomer'"
+ "mongordkit.Database.write.**WriteFromMolList**(database, list, scheme=MolDocScheme(), reg_collection=None, chunk_size=100, limit=None) --> *int: number of molecules imported*"
]
}
],
diff --git a/docs/notebooks/Explore LSH.ipynb b/docs/notebooks/Explore LSH.ipynb
deleted file mode 100644
index ed142b5..0000000
--- a/docs/notebooks/Explore LSH.ipynb
+++ /dev/null
@@ -1,284 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Exploring Locality Sensitive Hashing"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 56,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Imports\n",
- "\n",
- "import numpy as np\n",
- "from rdkit import Chem\n",
- "from rdkit.Chem import AllChem\n",
- "import sys\n",
- "import functools\n",
- "import mongomock"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 14,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Permutation function\n",
- "\n",
- "def get_permutations(len_permutations=2048, num_permutations=100):\n",
- " return map(lambda _: np.random.permutation(2048), range(num_permutations))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 44,
- "metadata": {},
- "outputs": [],
- "source": [
- "permutations = get_permutations()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 45,
- "metadata": {},
- "outputs": [],
- "source": [
- "def get_min_hash(mol, permutations):\n",
- " qfp = list(AllChem.GetMorganFingerprintAsBitVect(mol, 2, nBits=2048))\n",
- " min_hash = []\n",
- " for perm in permutations:\n",
- " for idx, i in enumerate(perm):\n",
- " if qfp_bits[i]:\n",
- " min_hash.append(idx)\n",
- " break \n",
- " return min_hash"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 46,
- "metadata": {},
- "outputs": [],
- "source": [
- "mol = Chem.MolFromSmiles('C1=CC=CC=C1OC')\n",
- "min_hash = get_min_hash(mol, permutations)\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 54,
- "metadata": {},
- "outputs": [],
- "source": [
- "def hash_to_buckets(min_hash, num_buckets=25, nBits=2048):\n",
- " if len(min_hash) % num_buckets:\n",
- " raise Exception('number of buckets must be divisiable by the hash length')\n",
- " buckets = []\n",
- " hash_per_bucket = int(len(min_hash) / num_buckets)\n",
- " num_bits = (nBits-1).bit_length()\n",
- "# if num_bits * hash_per_bucket > sys.maxint.bit_length():\n",
- "# raise Exception('numbers are too large to produce valid buckets')\n",
- " for b in range(num_buckets):\n",
- " buckets.append(functools.reduce(lambda x,y: (x << num_bits) + y, min_hash[b:(b + hash_per_bucket)]))\n",
- " return buckets"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 55,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[250056202389,\n",
- " 1941707205052,\n",
- " 782309908621,\n",
- " 1281762813978,\n",
- " 3814522409145,\n",
- " 1211290208280,\n",
- " 224114294943,\n",
- " 1589238888575,\n",
- " 206825584784,\n",
- " 1366332571687,\n",
- " 1091525753125,\n",
- " 1237114759205,\n",
- " 336236456125,\n",
- " 2517006411838,\n",
- " 318620430363,\n",
- " 1623757740190,\n",
- " 532689514536,\n",
- " 232591015954,\n",
- " 1357377474657,\n",
- " 343673079808,\n",
- " 155025670508,\n",
- " 833224401069,\n",
- " 1527081060,\n",
- " 3127462010894,\n",
- " 1486478143488]"
- ]
- },
- "execution_count": 55,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "hash_to_buckets(min_hash)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 57,
- "metadata": {},
- "outputs": [],
- "source": [
- "client = mongomock.MongoClient()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 58,
- "metadata": {},
- "outputs": [],
- "source": [
- "db = client.db"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 59,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[]"
- ]
- },
- "execution_count": 59,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "db.list_collection_names()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 60,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- ""
- ]
- },
- "execution_count": 60,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "db.molecules.insert_one({'_id': 1, 'molecule': 'boom'})"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 61,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "['molecules']"
- ]
- },
- "execution_count": 61,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "db.list_collection_names()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 62,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- ""
- ]
- },
- "execution_count": 62,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "db.molecules.find()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 63,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "{'_id': 1, 'molecule': 'boom'}"
- ]
- },
- "execution_count": 63,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "db.molecules.find_one()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "py37_rdkit_beta",
- "language": "python",
- "name": "py37_rdkit_beta"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.7"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/docs/notebooks/Exploring Multiprocessing.ipynb b/docs/notebooks/Exploring Multiprocessing.ipynb
deleted file mode 100644
index 300afb8..0000000
--- a/docs/notebooks/Exploring Multiprocessing.ipynb
+++ /dev/null
@@ -1,340 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [],
- "source": [
- "import pymongo\n",
- "import rdkit\n",
- "import math\n",
- "from rdkit import Chem\n",
- "from rdkit.Chem import AllChem\n",
- "from rdkit.Chem.rdmolops import PatternFingerprint\n",
- "from mongordkit.Database import write\n",
- "from mongordkit import Search"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "metadata": {},
- "outputs": [],
- "source": [
- "client = pymongo.MongoClient()\n",
- "db = client['multip']\n",
- "molecules = db.molecules"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "write.writeFromSDF(db.molecules, '../../data/test_data/first_200.props.sdf', 'test')"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 10,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[ObjectId('5f22bce9ad3e9621e4119c64'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c65'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c66'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c67'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c68'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c69'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c6a'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c6b'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c6c'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c6d'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c6e'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c6f'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c70'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c71'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c72'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c73'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c74'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c75'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c76'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c77'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c78'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c79'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c7a'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c7b'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c7c'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c7d'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c7e'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c7f'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c80'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c81'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c82'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c83'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c84'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c85'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c86'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c87'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c88'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c89'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c8a'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c8b'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c8c'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c8d'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c8e'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c8f'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c90'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c91'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c92'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c93'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c94'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c95'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c96'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c97'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c98'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c99'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c9a'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c9b'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c9c'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c9d'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c9e'),\n",
- " ObjectId('5f22bce9ad3e9621e4119c9f'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ca0'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ca1'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ca2'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ca3'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ca4'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ca5'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ca6'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ca7'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ca8'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ca9'),\n",
- " ObjectId('5f22bce9ad3e9621e4119caa'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cab'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cac'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cad'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cae'),\n",
- " ObjectId('5f22bce9ad3e9621e4119caf'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cb0'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cb1'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cb2'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cb3'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cb4'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cb5'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cb6'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cb7'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cb8'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cb9'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cba'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cbb'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cbc'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cbd'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cbe'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cbf'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cc0'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cc1'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cc2'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cc3'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cc4'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cc5'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cc6'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cc7'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cc8'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cc9'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cca'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ccb'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ccc'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ccd'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cce'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ccf'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cd0'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cd1'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cd2'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cd3'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cd4'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cd5'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cd6'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cd7'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cd8'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cd9'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cda'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cdb'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cdc'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cdd'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cde'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cdf'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ce0'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ce1'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ce2'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ce3'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ce4'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ce5'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ce6'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ce7'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ce8'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ce9'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cea'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ceb'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cec'),\n",
- " ObjectId('5f22bce9ad3e9621e4119ced'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cee'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cef'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cf0'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cf1'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cf2'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cf3'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cf4'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cf5'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cf6'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cf7'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cf8'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cf9'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cfa'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cfb'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cfc'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cfd'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cfe'),\n",
- " ObjectId('5f22bce9ad3e9621e4119cff'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d00'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d01'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d02'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d03'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d04'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d05'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d06'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d07'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d08'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d09'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d0a'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d0b'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d0c'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d0d'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d0e'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d0f'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d10'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d11'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d12'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d13'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d14'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d15'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d16'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d17'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d18'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d19'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d1a'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d1b'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d1c'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d1d'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d1e'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d1f'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d20'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d21'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d22'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d23'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d24'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d25'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d26'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d27'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d28'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d29'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d2a'),\n",
- " ObjectId('5f22bce9ad3e9621e4119d2b')]"
- ]
- },
- "execution_count": 10,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "document_ids = molecules.find().distinct('_id')\n",
- "document_ids"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 11,
- "metadata": {},
- "outputs": [],
- "source": [
- "def chunk(list, length):\n",
- " for i in range(0, len(l), length):\n",
- " yield l[i:i + n]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 13,
- "metadata": {},
- "outputs": [],
- "source": [
- "def calculate(chunk, input):\n",
- " client = pymongo.MongoClient('localhost', 27017, maxPoolSize=1000)\n",
- " db = client['multip']\n",
- " collection = db['molecules']\n",
- " chunk_list = []\n",
- " for id in chunk: \n",
- " result = \n",
- " chunk_list.append()\n",
- " \n",
- " \n",
- "# # define client inside function\n",
- " \n",
- " \n",
- " \n",
- " \n",
- "# client = pymongo.MongoClient('localhost', 27017, maxPoolSize=10000)\n",
- "\n",
- "# db = client['multip']\n",
- "# collection = db['molecules']\n",
- "# chunk_result_list = []\n",
- "# # loop over the id's in the chunk and do the calculation with each\n",
- "# # my problem right now is that I want to be able to chunk the CURSOR.\n",
- "# for id in chunk:\n",
- "# #do the calculation with document collection.find_one(id) \n",
- "# chunk_result_list.append(result)\n",
- "# return chunk_result_list"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "There are two ways that we can split the processing here. \n",
- "\n",
- "When the cursor query is expensive, we want to split up the cursor. \n",
- "\n",
- "When the cursor query is not, we simply want to keep the cursor together, and parallelize the subsequent operations. Let's start with the latter. We've got a cursor object that is iterable. \n",
- "\n",
- "What you could do is iterate through the cursor and get all the object ids. Then you could find all of them again and \n",
- "For "
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "py37_rdkit_beta",
- "language": "python",
- "name": "py37_rdkit_beta"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.7"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/docs/notebooks/Similarity Benchmarking.ipynb b/docs/notebooks/Similarity Benchmarking.ipynb
new file mode 100644
index 0000000..701d377
--- /dev/null
+++ b/docs/notebooks/Similarity Benchmarking.ipynb
@@ -0,0 +1,441 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Similarity Search Benchmarking\n",
+ "\n",
+ "These benchmarks were originally run on an early 2015 MacBook Pro with a 2.7 GHz dual-core i5 processor and 8GB of memory. \n",
+ "\n",
+ "They make use of a ChEMBL_27 dataset. \n",
+ "## Setup Work\n",
+ "### Imports"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import mongordkit\n",
+ "import time\n",
+ "import pymongo\n",
+ "import rdkit\n",
+ "import matplotlib\n",
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "from os import sys\n",
+ "import pandas as pd\n",
+ "from rdkit import Chem\n",
+ "from statistics import mean, median\n",
+ "import mongomock\n",
+ "from rdkit.Chem import AllChem\n",
+ "from mongordkit.Database import write\n",
+ "from mongordkit.Search import similarity\n",
+ "from mongordkit import Search"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Database Setup\n",
+ "Here we set up a database called `test` that will hold our molecules. We will construct a collection called `molecules_100K` to hold the first 100,000 molecules in the ChEMBL_27 dataset and a collection called `molecules_1M` to hold the first 1,000,000 molecules in the ChEMBL_27 dataset. If you have already run benchmarks from `mongo-rdkit` on your local MongoDB instance, these should have been set up already."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Initialize the client that will connect to the database.\n",
+ "client = pymongo.MongoClient()\n",
+ "db = client.test\n",
+ "chembl = '../../../chembl_27.sdf'\n",
+ "\n",
+ "# Disable rdkit warnings\n",
+ "rdkit.RDLogger.DisableLog('rdApp.*')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# If necessary, write the first 100,000 compounds to molecules_100K.\n",
+ "if db.molecules_100K.count_documents({}) != 100000:\n",
+ " write.WriteFromSDF(db.molecules_100K, chembl, chunk_size=1000, limit=100000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# If necessary, write the first 1,000,000 compounds to molecules_1M.\n",
+ "if db.molecules_1M.count_documents({}) != 1000000:\n",
+ " write.writeFromSDF(db.molecules_1M, chembl, chunk_size=1000, limit=1000000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "In molecules_100K: 100000 documents\n",
+ "In molecules_1M: 629512 documents\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Let's ensure that there are actually 100,000 and 1M documents in these collections, respectively.\n",
+ "print(f\"In molecules_100K: {db.molecules_100K.count_documents({})} documents\")\n",
+ "print(f\"In molecules_1M: {db.molecules_1M.count_documents({})} documents\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Next, we have to prepare the database for search by adding in fingerprints and hash collections.\n",
+ "Search.PrepareForSearch(db, db.molecules_100K, db.molecules_100KCt, db.molecules_100KPm)\n",
+ "Search.PrepareForSearch(db, db.molecules_1M, db.molecules_1MCt, db.molecules_1MPm)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Query Set Setup\n",
+ "To benchmark, we'll use the first 200 compounds in ChEMBL. Let's get an rdmol for each of these and write them into a list. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "first_200 = []\n",
+ "for rdmol in Chem.ForwardMolSupplier('../../data/test_data/first_200.props.sdf'): \n",
+ " first_200.append(rdmol)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Benchmarks"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We will search each compound five times against the target database, taking the mean value as representative of that molecule. We'll then take the median and mean for all 200 compounds, repeating the entire process for thresholds 0.7, 0.75, 0.8, 0.85, and 0.9. \n",
+ "\n",
+ "We will benchmark both the `SimSearchAggregate` and `SimSearchLSH` methods, keeping in mind that the LSH method does not return exact results. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "thresholds = [0.7, 0.75, 0.8, 0.85, 0.9, 0.95]\n",
+ "repetitions = 5"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### `SimSearchAggregate`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Benchmark against the first 100,000 molecules in ChEMBL. \n",
+ "aggregate_means_100K = []\n",
+ "aggregate_medians_100K = []\n",
+ "\n",
+ "for t in thresholds: \n",
+ " print(f\"Measuring performance for similarity threshold {t}...\")\n",
+ " query_times = []\n",
+ " for rdmol in first_200:\n",
+ " temp_times = []\n",
+ " for r in range(repetitions):\n",
+ " start = time.time()\n",
+ " _ = similarity.SimSearchAggregate(rdmol, db.molecules_100K, db.molecules_100KCt, threshold=t)\n",
+ " end = time.time()\n",
+ " temp_times.append(end - start)\n",
+ " query_times.append(mean(temp_times))\n",
+ " aggregate_means_100K.append([t, mean(query_times)])\n",
+ " aggregate_medians_100K.append([t, median(query_times)])\n",
+ "\n",
+ "print(f\"Aggregate means: {aggregate_means_100K}\")\n",
+ "print(f\"Aggregate medians: {aggregate_medians_100K}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Before we take a look at the 1M molecule dataset, let's graph these times to get a better idea of how similarity search increases in time required with lowered similarity thresholds: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x_list = [v[0] for v in aggregate_medians_100K]\n",
+ "y_list = [v[1] for v in aggregate_medians_100K]\n",
+ "plt.xlabel('thresholds')\n",
+ "plt.ylabel('time (s)')\n",
+ "plt.title('SimSearchAggregate medians / 100K dataset')\n",
+ "plt.plot(x_list, y_list)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "And here are the equivalent benchmarks against a million-molecule dataset:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Benchmark against the first 1M molecules in ChEMBL. \n",
+ "aggregate_means_1M = []\n",
+ "aggregate_medians_1M = []\n",
+ "\n",
+ "for t in thresholds: \n",
+ " print(f\"Measuring performance for similarity threshold {t}...\")\n",
+ " query_times = []\n",
+ " for rdmol in first_200:\n",
+ " temp_times = []\n",
+ " for r in range(repetitions):\n",
+ " start = time.time()\n",
+ " _ = similarity.SimSearchAggregate(rdmol, db.molecules_1M, db.molecules_1MCt, threshold=t)\n",
+ " end = time.time()\n",
+ " temp_times.append(end - start)\n",
+ " query_times.append(mean(temp_times))\n",
+ " aggregate_means_1M.append([t, mean(query_times)])\n",
+ " aggregate_medians_1M.append([t, median(query_times)])\n",
+ "\n",
+ "print(f\"Aggregate means: {aggregate_means_1M}\")\n",
+ "print(f\"Aggregate medians: {aggregate_medians_1M}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x_list = [v[0] for v in aggregate_medians_1M]\n",
+ "y_list = [v[1] for v in aggregate_medians_1M]\n",
+ "plt.xlabel('thresholds')\n",
+ "plt.ylabel('time (s)')\n",
+ "plt.title('SimSearchAggregate medians / 1M dataset')\n",
+ "plt.plot(x_list, y_list)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### `SimSearchLSH`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We will benchmark speed for LSH in the same way as we did for normal similarity search. As noted by the original ChEMBL authors of this approach, however, LSH also introduces an element of inaccuracy. Thus, we will also include a section on comparing results of `SimSearchAggregate` and `SimSearchLSH`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Benchmark against the first 100,000 molecules in ChEMBL. \n",
+ "LSH_means_100K = []\n",
+ "LSH_medians_100K = []\n",
+ "\n",
+ "for t in thresholds: \n",
+ " print(f\"Measuring performance for similarity threshold {t}...\")\n",
+ " query_times = []\n",
+ " for rdmol in first_200:\n",
+ " temp_times = []\n",
+ " for r in range(repetitions):\n",
+ " start = time.time()\n",
+ " _ = similarity.SimSearchLSH(rdmol, db, db.molecules_100K, \n",
+ " db.molecules_100KP, db.molecules_100KCt, threshold=t)\n",
+ " end = time.time()\n",
+ " temp_times.append(end - start)\n",
+ " query_times.append(mean(temp_times))\n",
+ " LSH_means_100K.append([t, mean(query_times)])\n",
+ " LSH_medians_100K.append([t, median(query_times)])\n",
+ "\n",
+ "print(f\"LSH means: {LSH_means_100K}\")\n",
+ "print(f\"LSH medians: {LSH_medians_100K}\")\n",
+ "\n",
+ "x_list = [v[0] for v in LSH_medians_100K]\n",
+ "y_list = [v[1] for v in LSH_medians_100K]\n",
+ "plt.xlabel('thresholds')\n",
+ "plt.ylabel('time (s)')\n",
+ "plt.title('SimSearchLSH medians / 100K dataset')\n",
+ "plt.plot(x_list, y_list)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Benchmark against the first 100,000 molecules in ChEMBL. \n",
+ "LSH_means_1M = []\n",
+ "LSH_medians_1M = []\n",
+ "\n",
+ "for t in thresholds: \n",
+ " print(f\"Measuring performance for similarity threshold {t}...\")\n",
+ " query_times = []\n",
+ " for rdmol in first_200:\n",
+ " temp_times = []\n",
+ " for r in range(repetitions):\n",
+ " start = time.time()\n",
+ " _ = similarity.SimSearchLSH(rdmol, db, db.molecules_1M, \n",
+ " db.molecules_1MP, db.molecules_1MCt, threshold=t)\n",
+ " end = time.time()\n",
+ " temp_times.append(end - start)\n",
+ " query_times.append(mean(temp_times))\n",
+ " LSH_means_1M.append([t, mean(query_times)])\n",
+ " LSH_medians_1M.append([t, median(query_times)])\n",
+ "\n",
+ "print(f\"LSH means: {LSH_means_1M}\")\n",
+ "print(f\"LSH medians: {LSH_medians_1M}\")\n",
+ "\n",
+ "x_list = [v[0] for v in LSH_medians_1M]\n",
+ "y_list = [v[1] for v in LSH_medians_1M]\n",
+ "plt.xlabel('thresholds')\n",
+ "plt.ylabel('time (s)')\n",
+ "plt.title('SimSearchLSH medians / 1M dataset')\n",
+ "plt.plot(x_list, y_list)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In order to compare accuracy, we will use the approach written about in the ChEMBL blog post: finding the symmetric set difference between the two sets of results as a percentage of the size of the union of the two result sets. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "results = []\n",
+ "\n",
+ "for t in thresholds: \n",
+ " print(f\"Measuring accuracy for similarity threshold {t}...\")\n",
+ " nmols_w_discrepancies = 0\n",
+ " discrepancies_per_mol = []\n",
+ " discrepancy_percent_per_mol = []\n",
+ " for rdmol in first_200:\n",
+ " sim_lsh = similarity.SimSearchLSH(rdmol, db, db.molecules_100K, \n",
+ " db.molecules_100KP, db.molecules_100KCt, threshold=t)\n",
+ " sim_agg = similarity.SimSearchAggregate(rdmol, db.molecules_100K, db.molecules_100KCt, threshold=t)\n",
+ " if sim_lsh: \n",
+ " set_lsh = set(result[1] for result in sim_lsh)\n",
+ " else:\n",
+ " set_lsh = set()\n",
+ " if sim_agg: \n",
+ " set_agg = set(result[1] for result in sim_agg)\n",
+ " else: \n",
+ " set_agg = set()\n",
+ " sym_set_diff = (set_lsh ^ set_agg)\n",
+ " discrepancies = len(sym_set_diff)\n",
+ " total = len(set_lsh | set_agg)\n",
+ " if discrepancies:\n",
+ " nmols_w_discrepancies += 1\n",
+ " discrepancies_per_mol.append(discrepancies)\n",
+ " discrepancy_percent_per_mol.append(discrepancies / total * 100)\n",
+ " results.append([t, f'nmols_w_discrepancies: {nmols_w_discrepancies}', \n",
+ " np.mean(discrepancies_per_mol), np.mean(discrepancy_percent_per_mol)])\n",
+ "print(results)\n",
+ "x_list = [v[0] for v in results]\n",
+ "y_list = [v[3] for v in results]\n",
+ "plt.xlabel('thresholds')\n",
+ "plt.ylabel('discrepancy percent per molecule')\n",
+ "plt.title('LSH Accuracy / 100K dataset')\n",
+ "plt.plot(x_list, y_list)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Discussion\n",
+ "These times are already very reasonable for a similarity search. However, it is worth noting that these benchmarks were run on a local MongoDB instance, effectively making no distinction between the client and the server. A MongoDB instance that has more horizontal scaling could benefit greatly from the aggregation pipeline, thus speeding search even further. \n",
+ "\n",
+ "The time complexity also increases greatly with decreasing Tanimoto thresholds."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "py37_rdkit_beta",
+ "language": "python",
+ "name": "py37_rdkit_beta"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/notebooks/Similarity Testing.ipynb b/docs/notebooks/Similarity Testing.ipynb
deleted file mode 100644
index 986a6c0..0000000
--- a/docs/notebooks/Similarity Testing.ipynb
+++ /dev/null
@@ -1,532 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Similarity Search Benchmarking\n",
- "\n",
- "These benchmarks were originally run on an early 2015 MacBook Pro with a 2.7 GHz dual-core i5 processor and 8GB of memory. \n",
- "\n",
- "They make use of a ChEMBL_27 dataset. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {},
- "outputs": [],
- "source": [
- "import mongordkit\n",
- "import time\n",
- "import pymongo\n",
- "import rdkit\n",
- "import matplotlib\n",
- "import matplotlib.pyplot as plt\n",
- "import numpy as np\n",
- "from os import sys\n",
- "import pandas as pd\n",
- "from rdkit import Chem\n",
- "from statistics import mean\n",
- "import mongomock\n",
- "from rdkit.Chem import AllChem\n",
- "from mongordkit.Database import write\n",
- "from mongordkit.Search import similarity"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "populating mongodb collection with compounds from chembl...\n",
- "199 molecules successfully imported\n"
- ]
- }
- ],
- "source": [
- "#Create a mongomock database instance and write to it. \n",
- "client = mongomock.MongoClient()\n",
- "db = client.db\n",
- "\n",
- "#Write 200 molecules into the database\n",
- "write.writeFromSDF(db.molecules, '../../data/test_data/first_200.props.sdf', 'test', chunk_size=100, limit=199)\n",
- "doc = db.molecules.find_one()\n",
- "m = Chem.Mol(doc['rdmol'])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [],
- "source": [
- "#Add Morgan fingerprints into the database\n",
- "similarity.addMorganFingerprints(db)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 14,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[]"
- ]
- },
- "execution_count": 14,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "#Check that similarity search is working, at least for one molecule. \n",
- "doc = db.molecules.find_one()\n",
- "m = Chem.Mol(doc['rdmol'])\n",
- "results = similarity.similaritySearch(m, db, .8)\n",
- "results"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "populating mongodb collection with compounds from chembl...\n",
- "inserted chunk...\n",
- "inserted chunk...\n",
- "1000 molecules successfully imported\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "1000"
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "#Create a regular mongoDB database instance and write the first 1000 molecules to it. \n",
- "client = pymongo.MongoClient()\n",
- "db = client.db\n",
- "db.molecules.drop()\n",
- "db.mfp_counts.drop()\n",
- "write.writeFromSDF(db, '../../../chembl_27.sdf', 'test', reg_option='standard_setting', index_option='inchikey', chunk_size=500, limit=500)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "metadata": {},
- "outputs": [],
- "source": [
- "def calc_tanimoto(Na, Nb):\n",
- " Nab = len(set(Na).intersection((set(Nb))))\n",
- " return float(Nab) / (len(Na) + len(Nb) - Nab)\n",
- "\n",
- "def similarity_search_naive(query_mol, db, threshold): \n",
- " results = []\n",
- " qfp = list(AllChem.GetMorganFingerprintAsBitVect(query_mol, 2, nBits=1024).GetOnBits())\n",
- " for mol in db.molecules.find():\n",
- " mfp = list(AllChem.GetMorganFingerprintAsBitVect(Chem.Mol(mol['rdmol']), 2, nBits=1024).GetOnBits())\n",
- " tanimoto = calc_tanimoto(qfp, mfp)\n",
- " if calc_tanimoto(qfp, mfp) >= threshold:\n",
- " results.append([tanimoto, mol['smiles']])\n",
- " return results"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Measuring performance for similarity threshold 0.7.\n",
- "Measuring performance for similarity threshold 0.75.\n",
- "Measuring performance for similarity threshold 0.8.\n",
- "Measuring performance for similarity threshold 0.85.\n",
- "Measuring performance for similarity threshold 0.9.\n",
- "Measuring performance for similarity threshold 0.95.\n",
- "[[0.7, 3.236401987075806], [0.75, 2.964214563369751], [0.8, 2.850223159790039], [0.85, 2.716036558151245], [0.9, 2.50888934135437], [0.95, 2.7822859287261963]]\n",
- "Measuring performance for similarity threshold 0.7.\n"
- ]
- },
- {
- "ename": "NameError",
- "evalue": "name 'query_mol' is not defined",
- "output_type": "error",
- "traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
- "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 23\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mm\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmolecules\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfind\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 24\u001b[0m \u001b[0mmol\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mChem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mMol\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mm\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'rdmol'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 25\u001b[0;31m \u001b[0m_\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msimilarity_search_naive\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmol\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdb\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 26\u001b[0m \u001b[0mend\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtime\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 27\u001b[0m \u001b[0mtemp_times\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mend\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mstart\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;32m\u001b[0m in \u001b[0;36msimilarity_search_naive\u001b[0;34m(mol, db, t)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0msimilarity_search_naive\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmol\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdb\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mresults\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mqfp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mAllChem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGetMorganFingerprintAsBitVect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery_mol\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnBits\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1024\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGetOnBits\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 8\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mmol\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmolecules\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfind\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0mmfp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mAllChem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGetMorganFingerprintAsBitVect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mChem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mMol\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmol\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'rdmol'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnBits\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1024\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGetOnBits\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;31mNameError\u001b[0m: name 'query_mol' is not defined"
- ]
- }
- ],
- "source": [
- "#Run benchmarks for similarity search with and without aggregation parameters, then with LSH + aggregation. \n",
- "thresholds = [0.7, 0.75, 0.8, 0.85, 0.9, 0.95]\n",
- "times = []\n",
- "repetitions = 5\n",
- "for t in thresholds: \n",
- " print(\"Measuring performance for similarity threshold {}.\".format(t))\n",
- " temp_times = []\n",
- " for r in range(repetitions):\n",
- " start = time.time()\n",
- " for m in db.molecules.find():\n",
- " mol = Chem.Mol(m['rdmol'])\n",
- " _ = similarity.similaritySearch(mol, db, t)\n",
- " end = time.time()\n",
- " temp_times.append(end - start)\n",
- " times.append([t, mean(temp_times)])\n",
- "print(times)\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Measuring performance for similarity threshold 0.7.\n",
- "Measuring performance for similarity threshold 0.75.\n"
- ]
- },
- {
- "ename": "KeyboardInterrupt",
- "evalue": "",
- "output_type": "error",
- "traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
- "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32mbreak\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mmol\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mChem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mMol\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mm\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'rdmol'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0m_\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msimilarity_search_naive\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmol\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdb\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 12\u001b[0m \u001b[0mcounter\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[0mend\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtime\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;32m\u001b[0m in \u001b[0;36msimilarity_search_naive\u001b[0;34m(query_mol, db, threshold)\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mqfp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mAllChem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGetMorganFingerprintAsBitVect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery_mol\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnBits\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1024\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGetOnBits\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mmol\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmolecules\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfind\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0mmfp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mAllChem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGetMorganFingerprintAsBitVect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mChem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mMol\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmol\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'rdmol'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnBits\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1024\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGetOnBits\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 10\u001b[0m \u001b[0mtanimoto\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcalc_tanimoto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mqfp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmfp\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcalc_tanimoto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mqfp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmfp\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m>=\u001b[0m \u001b[0mthreshold\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
- ]
- }
- ],
- "source": [
- "for t in thresholds: \n",
- " print(\"Measuring performance for similarity threshold {}.\".format(t))\n",
- " temp_times = []\n",
- " for r in range(5):\n",
- " start = time.time()\n",
- " counter = 0\n",
- " for m in db.molecules.find():\n",
- " if counter > 100: \n",
- " break\n",
- " mol = Chem.Mol(m['rdmol'])\n",
- " _ = similarity_search_naive(mol, db, t)\n",
- " counter += 1\n",
- " end = time.time()\n",
- " temp_times.append(end - start)\n",
- " times.append([t, mean(temp_times)])\n",
- "print(times)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 40,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[]"
- ]
- },
- "execution_count": 40,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "x_list = [v[0] for v in times]\n",
- "y_list = [v[1]*1000 for v in times]\n",
- "plt.xlabel('thresholds')\n",
- "plt.ylabel('time (ms)')\n",
- "plt.title('Without Aggregation')\n",
- "plt.plot(x_list, y_list)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 41,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Measuring performance for similarity threshold 0.7.\n",
- "Measuring performance for similarity threshold 0.75.\n",
- "Measuring performance for similarity threshold 0.8.\n",
- "Measuring performance for similarity threshold 0.85.\n",
- "Measuring performance for similarity threshold 0.9.\n",
- "Measuring performance for similarity threshold 0.95.\n",
- "[[0.7, 6.002911186218261], [0.75, 5.983159065246582], [0.8, 5.641262626647949], [0.85, 5.888340759277344], [0.9, 6.869273900985718], [0.95, 5.446581506729126]]\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "[]"
- ]
- },
- "execution_count": 41,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "thresholds = [0.7, 0.75, 0.8, 0.85, 0.9, 0.95]\n",
- "times = []\n",
- "repetitions = 5\n",
- "for t in thresholds: \n",
- " print(\"Measuring performance for similarity threshold {}.\".format(t))\n",
- " temp_times = []\n",
- " for r in range(repetitions):\n",
- " start = time.time()\n",
- " for m in db.molecules.find():\n",
- " mol = Chem.Mol(m['rdmol'])\n",
- " _ = similarity.similaritySearchAggregate(mol, db, t)\n",
- " end = time.time()\n",
- " temp_times.append(end - start)\n",
- " times.append([t, mean(temp_times)])\n",
- "print(times)\n",
- "x_list = [v[0] for v in times]\n",
- "y_list = [v[1]*1000 for v in times]\n",
- "plt.xlabel('thresholds')\n",
- "plt.ylabel('time (ms)')\n",
- "plt.title('With Aggregation')\n",
- "plt.plot(x_list, y_list)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 42,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "populating mongodb collection with compounds from chembl...\n",
- "The specified setting does not exist. Will only insert default molecules\n",
- "inserted chunk...\n",
- "inserted chunk...\n",
- "1000 molecules successfully imported\n"
- ]
- }
- ],
- "source": [
- "db.molecules.drop()\n",
- "db.mfp_counts.drop()\n",
- "write.writeFromSDF(db, '../../../chembl_27.sdf', 'test', reg_option='inchikey', index_option='inchikey', chunk_size=500, limit=500)\n",
- "similarity.addMorganFingerprints(db)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 47,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Measuring performance for similarity threshold 0.7.\n",
- "Measuring performance for similarity threshold 0.75.\n",
- "Measuring performance for similarity threshold 0.8.\n",
- "Measuring performance for similarity threshold 0.85.\n",
- "Measuring performance for similarity threshold 0.9.\n",
- "Measuring performance for similarity threshold 0.95.\n",
- "[[0.7, 0.0038346290588378907], [0.75, 0.003863954544067383], [0.8, 0.00497593879699707], [0.85, 0.00534672737121582], [0.9, 0.004187107086181641], [0.95, 0.006510639190673828]]\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "[]"
- ]
- },
- "execution_count": 47,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYIAAAEWCAYAAABrDZDcAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjMsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+AADFEAAAgAElEQVR4nO3dd3gc1dX48e9Rd5Gr5N6bjG1syRhjwJgSwMa40Q0hQELghYT2I0AgIZCQkAJvElPyQmiBJBjTJWNMBwOmGGxr5d671kWWu2X18/tjR2aRJXll7eystOfzPPtoduq5knbOzp2594qqYowxJnbFeR2AMcYYb1kiMMaYGGeJwBhjYpwlAmOMiXGWCIwxJsZZIjDGmBhnicBEBRE5ICJ96li+QUTOjmRMseJov3vT9FkiMGEnIveIyOxq81bXMm8qgKq2VNV1zvznReQPEYr1GhGZG+K6z4tIuYh0cTsut4jIHBH5afC84N+9iU2WCIwbPgNOFZF4ABHpBCQCw6vN6+esG/VEpAVwEbAX+KGLx0lwa9/G1MYSgXHDtwRO/JnO+zHAJ8DKavPWqqofQERURPqJyPUETrR3OVUWbwXtN1NEFonIXhF5WURSqhaIyHUiskZEdonIzKpv7SLSy9l3QtC6c0TkpyJyHPAkcLJzrD11lOkiYA/wAHB18AIRaSYiL4jIbhFZLiJ3iciWoOXDRSRXRPaLyKtO7H9wlp0hIltE5Jcisg34lzN/goj4RGSPiHwpIkND3F9bEZklIgVOPLNEpJuz7EHgNOBxp7yPB//unenWIvJvZ/uNInKviMQ5y64Rkbki8r/OvteLyHl1/M5MI2GJwISdqpYC8wic7HF+fg7MrTbviKsBVX0KeBF4yKmymBi0+FJgHNAbGApcAyAiZwF/cpZ3BjYCM0KIczlwA/CVc6w2dax+NfCSs9+BIjI8aNn9QC+gD3AOcGXVAhFJAt4EngfaOfu4oNq+OznLegLXO/t+DvgfoD3wT2CmiCSHsL84AsmkJ9ADOAQ87pT31wT+Djc55b2phnI+BrR2ynI6cBXw46DlJxFI6GnAQ8CzIiI1/sZMo2GJwLjlU7476Z9G4AT0ebV5n9Zzn4+qql9VdwFv8d3VxQ+B51R1oaqWAPcQ+Jbf69jD/46I9ADOBKar6nbgI75/VXAp8EdV3a2qW4BHg5aNAhKc2MtU9Q3gm2qHqATuV9USVT0EXAf8U1XnqWqFqr4AlDj7qnN/qlqoqq+rapGq7gceJHBCD6Wc8cBlwD2qul9VNwB/BX4UtNpGVX1aVSuAFwgk3o6h7N9EL0sExi2fAaNFpC2QrqqrgS+BU5x5Q6j//YFtQdNFQEtnuguBqwAAVPUAUAh0PcbYq/sRsFxVfc77F4ErRCQx6Pibg9YPnu4C5Ov3e3cMXg5QoKrFQe97Ar9wqoX2OFVW3Z191bk/EWkuIv90qnX2Efgdt6m6N3MUaUASQb9LZzr493j4b6CqRc5kS0yjZonAuOUrAlUM1wNfAKjqPsDvzPOr6vpatq1vl7h+AidP4PCN3fZAPnDQmd08aP1O9TzWVUAfEdnm1OP/jcBJs6p+fCvQLWj97kHTW4Gu1apPgpfXFMNm4EFVbRP0aq6qL4Wwv18AGcBJqtqK767Aqtavq7w7gTKCfpcEqpfy69jGNAGWCIwrnCqO+cDtBKqEqsx15tV1NbCdQB11qKYDPxaRTBFJBv4IzFPVDapaQOBEdqWIxIvIT4C+1Y7Vzal7P4KInOysP5JAVVQmgauZ6XxXPfQKcI9zo7YrEFz3/hVQAdwkIgkiMtnZV12eBm4QkZMkoIWInC8iqSHsL5XAfYE9ItKOwP2LYLX+bp3qnleAB0UkVUR6Evhb/fco8ZpGzhKBcdOnQAcCJ/8qnzvz6koEzwKDnGqR7KMdRFU/An4DvE7gG3NfYGrQKtcBdxKoLhpMoIqqysfAUmCbiOysYfdXAzmqulhVt1W9gEeACc7J9gFgC7Ae+BB4jUCdftWN8wuBawk8dXQlMKtqeS3lme/E/DiwG1iDc2M8hP1NA5oR+Hb/NfButd0/AlzsPPXzKEe6mcBV1DoCf7fpBG5cmyZMbGAaY8JLRG4EpqpqjTdpRWQe8KSq/itMxwvr/kzssSsCYxpIRDqLyKkiEiciGQTq6d8MWn66iHRyqnKuJvDoa/Vv6vU5Xlj3Z4y1YjSm4ZIIPOvfm0B1zQzg/4KWZxCoe28JrAUuVtWtDTheuPdnYpxVDRljTIyzqiFjjIlxja5qKC0tTXv16uV1GMYY06gsWLBgp6qm17Ss0SWCXr16MX/+fK/DMMaYRkVENta2zKqGjDEmxlkiMMaYGGeJwBhjYpwlAmOMiXGWCIwxJsZZIjDGmBjnaiIQkTYi8pqIrHDGcj252vIzJDD+rM953edmPMYYY47kdjuCR4B3VfVip7/35jWs87mqTnA5DmOMadSmfbiKUX3aM6pP+7Dv27UrAhGpGh3pWQj0o66qe9w6njHGNFX5ew4x7cPVfLt+lyv7d7NqqA9QAPxLRHJF5BlnCMHqThaRPBF5R0QG17QjEbleROaLyPyCggIXQzbGmOgz0+cHYFJmF1f272YiSACGA0+oahaBUY/urrbOQqCnqg4DHgNqHI1KVZ9S1RGqOiI9vcauMowxpsnK8eWT1aMNPdvX9F264dxMBFuALao6z3n/GoHEcJiq7lPVA870bCBRRNJcjMkYYxqVFdv2sWLbfqZkdnXtGK4lAmdc183OiE0APwCWBa/jjLIkzvRIJ55Ct2IyxpjGJsfnJz5OOH9oZ9eO4fZTQzcDLzpPDK0DfiwiNwCo6pPAxcCNIlIOHCIwzquNlGOMMUBlpTLT5+e0/mmktUx27TiuJgJV9QEjqs1+Mmj548DjbsZgjDGN1fyNu8nfc4g7xg5w9TjWstgYY6JUti+fZonxnDuok6vHsURgjDFRqLS8ktmLt3LOoI60SHa3Ft8SgTHGRKHPVhWwp6iMKVnutB0IZonAGGOiULYvn7bNEzmtv/ttpywRGGNMlDlQUs6Hy7dz/tDOJMa7f5q2RGCMMVHmvSXbKC6rdLURWTBLBMYYE2Vy8vx0a9uME3q2jcjxLBEYY0wUKdhfwtzVBUzO7ILT8YLrLBEYY0wUmbXIT6USsWohsERgjDFRJdvn57jOrejfMTVix7REYIwxUWL9zoPkbd7DFJfGHaiNJQJjjIkSM31+RNwbgKY2lgiMMSYKqCo5vnxO6t2Ozq2bRfTYlgiMMSYKLM7fy7qdB5kcwZvEVSwRGGNMFMjO9ZMUH8f4Ie4NQFMbSwTGGOOxikrlrUV+zshIp3XzxIgf3xKBMcZ47Ku1hRTsL2FKVuSrhcASgTHGeC7bl09qcgJnDezgyfEtERhjjIeKyyp4d8k2xg7pREpivCcxWCIwxhgPfbR8BwdKyiPapUR1lgiMMcZD2b58OqQmc3Lf9p7FYInAGGM8sreojDkrdzBxWBfi4yLT02hNLBEYY4xHZi/ZSlmFelotBJYIjDHGM9m5+fRJa8GQrq08jcMSgTHGeMC/5xDz1u9icmbXiA1AUxtLBMYY44GZeX4AJke4p9GaWCIwxhgP5Pj8ZHZvQ6+0Fl6HYonAGGMibdX2/Szfui/iA9DUxtVEICJtROQ1EVkhIstF5ORqy0VEHhWRNSKySESGuxmPMcZEg+zcfOLjhPOHRkciSHB5/48A76rqxSKSBDSvtvw8oL/zOgl4wvlpjDFNUmWlkuPzc2q/NNJTk70OB3DxikBEWgFjgGcBVLVUVfdUW20y8G8N+BpoIyKR74zbGGMiZOGm3eTvORQ11ULgbtVQH6AA+JeI5IrIMyJS/a5IV2Bz0PstzrzvEZHrRWS+iMwvKChwL2JjjHFZti+flMQ4zh3cyetQDnMzESQAw4EnVDULOAjcXW2dmh6e1SNmqD6lqiNUdUR6enr4IzXGmAgoq6jk7UVbOWdQJ1omu10zHzo3E8EWYIuqznPev0YgMVRfp3vQ+26A38WYjDHGM5+tKmB3URmTh0VPtRC4mAhUdRuwWUQynFk/AJZVW20mcJXz9NAoYK+qbnUrJmOM8VK2z0+b5omMGRBdNRtuX5vcDLzoPDG0DvixiNwAoKpPArOB8cAaoAj4scvxGGOMJw6WlPPBsm1cNLwbSQnR1YTL1USgqj5gRLXZTwYtV+DnbsZgjDHR4P1l2yguq/RsXOK6RFdaMsaYJio710/XNs04oUdbr0M5giUCY4xx2c4DJcxds5NJmV2I83AAmtpYIjDGGJfNyvNTUen9ADS1sURgjDEuy8nzM7BTKhmdUr0OpUaWCIwxxkUbCw+Su2lPVN4krmKJwBhjXJTjC7SRnRhljciCWSIwxhiXqCrZvnxG9m5H1zbNvA6nVpYIjDHGJUvy97Gu4GDU3iSuYonAGGNckuPLJzFeGH989PQ0WhNLBMYY44KKSmVmnp8zMjrQpnmS1+HUyRKBMca44Ot1hezYX8LkKBqApjaWCIwxxgXZufm0TE7g7OM6eh3KUVkiMMaYMCsuq+DdJdsYO7gTKYnxXodzVJYIjDEmzD5ZsYP9JeVMyYr+aiGwRGCMMWGX7csnrWUyp/RN8zqUkFgiMMaYMNpbVMYnKwqYOKwz8VHY02hNLBEYY0wYvbNkK6UVlVHfiCyYJQJjjAmjbF8+vdNaMLRba69DCZklAmOMCZOtew8xb/0uJmd2QaRxVAuBJQJjjAmbt/L8qMLkRlQtBJYIjDEmbLJz/Qzr1preaS28DqVeLBEYY0wYrN6+n2Vb9zW6qwGwRGCMMWGR4/MTJzBhWGevQ6k3SwTGGNNAqkpOXj6n9kujQ2qK1+HUmyUCY4xpoIWbdrN516FGWS0ElgiMMabBsnP9JCfEMXZw9Pc0WhNLBMYY0wBlFZW8vXgrZw/qSGpKotfhHBNLBMYY0wBzV+9k18HSRtWlRHUJbu5cRDYA+4EKoFxVR1RbfgaQA6x3Zr2hqg+4GZMxxoRTti+f1s0SOX1AutehHDNXE4HjTFXdWcfyz1V1QgTiMMaYsDpYUs77S7czJasrSQmNt4Kl8UZujDEe+2DZdg6VVTClEYxLXBe3E4EC74vIAhG5vpZ1ThaRPBF5R0QG17SCiFwvIvNFZH5BQYF70RpjTD1k+/Lp0jqFE3u18zqUBnE7EZyqqsOB84Cfi8iYassXAj1VdRjwGJBd005U9SlVHaGqI9LTG289nDGm6Sg8UMLnq3cyKbMrcY1kAJrahJQIRKSDiFwgIj8XkZ+IyEgROeq2qup3fu4A3gRGVlu+T1UPONOzgUQRaRxjuxljYtrbi7dSUamNZlziutR5MheRM0XkPeBtAt/qOwODgHuBxSLyOxFpVcu2LUQktWoaOBdYUm2dTuJ02i0iI514ChtWJGOMcV92bj4ZHVMZ2KnGU2CjcrSnhsYD16nqpuoLRCQBmACcA7xew7YdgTed83wCMF1V3xWRGwBU9UngYuBGESkHDgFTVVWPtTDGGBMJmwqLWLhpD3eNy/A6lLCoMxGo6p11LCunljp9Z/k6YFgN858Mmn4ceDykSI0xJkrk+PIBmDSs8VcLQej3CG4VkVYS8KyILBSRc90Ozhhjoo2qku3LZ2SvdnRr29zrcMIi1KeGfqKq+wjU86cDPwb+7FpUxhgTpZb697G24CCTm8BN4iqhJoKqZ6PGA/9S1bygecYYEzNyfPkkxAnjhzS+AWhqE2oiWCAi7xNIBO85TwNVuheWMcZEn4pKZWaenzMy0mnbIsnrcMIm1ERwLXA3cKKqFgFJBKqHjDEeKi2vJMeXzycrd3gdSkyYt66Q7ftKGu0ANLUJqdM5Va10HvEc4zw2WmWRO2EZY+qyt6iM6d9s4vkv17N9XwlJ8XFk//xUBnVp/M+0R7Mcn58WSfGcfVzjHICmNiElAhF5DhgKLOW7KiEF3nApLmNMDTbvKuLZuet5Zf5mikorGN0vjfsnDub+mUu5dUYub908mpTEeK/DbJKKyyqYvWQrY4d0ollS0/odh9oN9ShVHeRqJMaYWuVu2s3Tn6/j3SXbiBNh0rAu/PS0PoevAFomJ3DVc9/wp9nL+d3kIR5H2zTNWbmD/cXlTa5aCEJPBF+JyCBVXeZqNMaYwyoqlQ+WbeeZz9cxf+NuUlMSuH5MX645pRedWqd8b90xA9K5dnRvnp27njMyOnDmwA4eRd10Zef6SWuZxKl923sdStiFmgheIJAMtgElBB4dVVUd6lpkxsSootJyXluwhefmrmdDYRHd2jbjvgmDuPTE7rRMrv0je+fYDL5Ys5M7X8vjnVvHkJ6aHMGom7a9h8r4eOUOrhjZg4T4pjeMS6iJ4DngR8Bi7LFRY1yxY38x//5yI/+dt5E9RWVkdm/DP8YOZOzgjiGdfFIS43n08iwmPjaXu17L47lrTsTp68s00HtLtlFaXsmUrKZXLQShJ4JNqjrT1UiMiVErt+3nmc/XkePzU1ZZybmDOnLdaX04oWfbep/IB3RM5Vfjj+P+mUv5z9cbuerkXu4EHWOyffn0bN+cYd1aex2KK0JNBCtEZDrwFoGqIQBU1Z4aMuYYqCpfrCnkqc/X8dmqAlIS47jsxO78ZHRveqe1aNC+rzq5J3NW7uDBt5czqk97BnRMDVPUsWnb3mK+WlfIzWf1b7JXWKEmgmYEEkBwR3P2+Kgx9VRaXslbeX6e/nwdK7btJ61lMnecO4AfntQzbC1VRYSHLh7GuGmfcctLueTcdCrJCU3rccdIeivPjyqNflziuoTaoMxaERvTAHuLynjxm4288OUGtu8rYUDHljx00VAmZ3Vx5SSdnprMQxcP5doX5vPwuyu5d4I9/X2scvLyGdqtNX3SW3odimvqTAQici/wf6q6q5blZwHNVXWWG8EZ09jV1ADsLxcN5fQB6a5XM/zguI78aFRPnpm7ntMz0jmtv433XV9rdhxgSf4+ftPEE+nRrggWA2+JSDGBgeYLgBSgP5AJfAj80dUIjWmEjmgAltmFn47uE/EuIH41/ji+WlfIL17J493bxtCuCXWUFgk5vnziBCYObTo9jdbkaCOU5QA5ItIfOJXAmMX7gP8C16vqIfdDNKZxqN4ArFUdDcAipVlSPI9MzWTKP77g7tcX8c8fndBkb3iGm6qS4/NzSt80OrTy5u8XKaHeI1gNrHY5FmMapZoagN0/cRCXjuhOizoagEXK4C6tuWvsQB6cvZyXv93M1JE9vA6pUVi4aQ+bdhVx81n9vA7Fdd7/lxrTSDW0AVgkXTu6N3NW7eB3by1jZO92TfrGZ7jM9OWTnBDHuCGdvA7FdZYIjKmncDYAi5S4OOGvl2Qy7pHPuHWGj9dvPIWkhOhKVtGkrKKSWYu2cvZxHUlNSfQ6HNdZIjAmBG42AIuUTq1T+POFQ7nhvwuY9uEq7ho30OuQotbcNTspPFjKpCbcdiBYqOMRDACeADqq6hARGQpMUtU/uBqdMR6LRAOwSBo3pBNTT+zOE5+uZcyAdEb1aXo9aYZDTm4+rVISOCMjNh65DfWK4GngTuCfAKq6yOlywhKBaZIi3QAskn4zYRDz1u/i9pd9vHPrGFo3b/pVH/VRVFrO+8u2Mzmz8f+tQxVqImiuqt9Uq/8sdyEeYzzlZQOwSGmRnMC0yzK56Ikv+VX2Yh6/PKvJlC0cPli2naLSiiY5AE1tQk0EO0WkL4H+hRCRi4GtrkVlTIQt3LSbZ6KgAVikDOvehv93zgAefm8lZ2V04KITunkdUtTI8fnp3DqFkb3aeR1KxISaCH4OPAUMFJF8YD1wpWtRGRMBVQ3Anv58HQuipAFYJN1wel8+XVXAfTlLGNGrLT3bN46b3m7adbCUz1YVcO3o3sTFxc5VUqgNytYBZ4tICyBOVfe7G5Yx7qlqAPbs3PVsLCyie7voagAWKfFxwt8uHcZ5j3zObS/7ePV/To669g+R9vYiP+WVGlPVQhD6U0NtgKuAXkBCVX2iqt5ylO02APuBCqBcVUdUWy7AI8B4oAi4RlUX1qsExoRox75iXvhqAy/O23S4Adgvxw3k3EHR1wAsUrq1bc6DFxzPLS/l8tjHa/h/5wzwOiRPZfv8DOjYkuM6x9YYDqF+/ZkNfM2xDVV5pqrurGXZeQQ6sOsPnETgEdWT6rl/Y+rUGBuARdKkYV2Ys2IHj328mjED0jihZ+zUjQfbvKuIBRt3c+fYjJj7vwg1EaSo6u0uHH8y8G9VVeBrEWkjIp1V1W5EmwZRVeau2cnTn6//XgOwa0f3plcjaQAWSb+bPJhvN+7itpd9zL7ltJhoTVvdzDw/EEiMsSbURPAfEbkOmMX3h6qscZyCIAq8LyIK/FNVn6q2vCuwOej9Fmfe9xKBiFwPXA/Qo4d1mGVqV1ZRyUxf02kAFimpKYlMuyyTS578ivtnLuVvl2Z6HVJEqSrZufmM6NmW7u2aex1OxIWaCEqBh4Ff4zxC6vzsc5TtTlVVv4h0AD4QkRWq+lnQ8pquv/SIGYEE8hTAiBEjjlhuDAQ+zDdNX8h7S7c3qQZgkXJCz3bcfFZ/HvloNWdkdIipb8bLtu5j9Y4D/H7KEK9D8USoieB2oF8ddf01UlW/83OHiLwJjASCE8EWoHvQ+26Avz7HMKbK819u4L2l27lrXAY3nt435up5w+Hms/rx2eoCfv3mYk7o2ZaubZp5HVJEzPT5SYgTzj++aQ9AU5tQH5VYSuCpnpCJSAsRSa2aJjDw/ZJqq80ErpKAUcBeuz9gjsWiLXv44+zlnH1cR0sCDZAQH8cjl2VRWan8v5d9VFQ2/QvwykplZp6f0wekx+wIbqEmggrAJyL/FJFHq15H2aYjMFdE8oBvgLdV9V0RuUFEbnDWmQ2sA9YQ6M/oZ8dQBhPj9heXcfNLuaS3TOZ/LxlqSaCBerRvzgOTh/DN+l08+elar8Nx3bz1u9i6tzhmehqtSahVQ9nOK2ROI7RhNcx/MmhaCbRaNuaYqCr3vLGYLbsP8fL1o2jTPDa/0YXbhcO78snKHfz9g1WM7pfGsO5tvA7JNTm+fJonxXPOoI5eh+KZUFsWv+B2IMYcixnfbmbWoq3cOTaDETHUN4zbRIQHpxzPwo27ue1lH7NuHt0kW12XlFcwe/FWxg7uRPOkple+UNVZNSQirzg/F4vIouqvyIRoTM1WbNvHb2cu5bT+adx4el+vw2lyWjdP5G+XZbKh8CC/n7XM63BcMWdlAfuKy5kcw9VCcPQrgludnxPcDsSY+igqLeem6bm0apbI3y7NjKkOwiJpVJ/23Hh6X/5vzlrOyEhn3JCm9VRNji+f9i2SGN0vzetQPFXnFUHQEzw/U9WNwS/sxq7x0P05S1lbcIBpl2WSnprsdThN2m1nD+D4rq25+43FbNtb7HU4YbOvuIwPl+9gwtDOMdvXVJVQS39ODfPOC2cgxoTqjYVbeHXBFm4+sx+nxvg3uUhISohj2tRMSsoq+cWrPiqbyCOl7y7ZRml5JZOzYqun0Zoc7R7BjSKyGMiodn9gPWD3CEzErS04wL3ZSxjZux23/KC/1+HEjL7pLblv4iC+WFPIc1+s9zqcsMjx5dOzfXOymvATUaE62j2C6cA7wJ+Au4Pm7w+hnyFjwqq4rIKbpueSnBDHo1OzYv5yPtKmntidT1bs4KF3V3Jy3/YM7tLa65CO2Y59xXy5tpCbz+xn7U44+j2Cvaq6QVUvr3aPwJKAibgH317O8q37+NulmTExgli0ERH+fNFQ2jRP5NYZPg6VVngd0jGbmedHFSbF2AA0tbGvVKZRmL14K//5eiPXj+nDmQM7eB1OzGrXIom/XjqMNTsO8Kd3lnsdzjHL8fkZ0rUV/Tq09DqUqGCJwES9zbuK+OVri8js3oY7zs3wOpyYd1r/dH46ujf//mojHy3f7nU49ba24ACL8/cyxa4GDrNEYKJaaXklN72UCwKPXZ5FUoL9y0aDO8dlMLBTKne9toiC/SVH3yCK5OTmIwITY6ib7aOxT5WJag+/t4K8zXt46KKhMTlgSLRKTojn0cuzOFBSzp2v5RHoNiz6qSo5eX5O6duejq3sPlMVSwQman20fDtPf76eq07uyXkx2k98NBvQMZVfn38cc1YW8O+vNnodTkh8m/ewsbCIyVYt9D2WCExU2rr3EL94NY9BnVvxq/HHeR2OqcWPRvXkzIx0Hpy9nFXb93sdzlHl+PwkJcQxbkgnr0OJKpYITNQpr6jk1pd8lJZX8vgVWaQk2lCT0UpEeOjiYbRKSeCWl3IpLoveR0rLKyqZtcjPDwZ2oFVKotfhRBVLBCbqPPLRar7ZsIs/XnA8fdLt8b5ol56azMMXD2PFtv08/N5Kr8Op1dw1O9l5oNSqhWpgicBElbmrd/L4J2u4dEQ3plgfMI3GmQM7cNXJPXl27no+W1XgdTg1munz0yolgTMHpnsdStSxRGCiRsH+Em572Uff9Jb8dtJgr8Mx9fSr8cfRr0NL7ng1j10HS70O53sOlVbw3tJtjD++M8kJVtVYnSUCExWqBkvfX1zGP64YHtOjRTVWKYnxPDI1kz1FZfzy9UVR9UjpB8u3c7C0IqbHJa6LJQITFZ74dC1z1+zkd5MGk9Ep1etwzDEa3KU1d43L4INl25nx7WavwzksJzefTq1SGNW7vdehRCVLBMZz327YxV/fX8mkYV247MTuXodjGugnp/ZmdL80HnhrGWsLDngdDrsPlvLpqgImZXaxkexqYYnAeGr3wVJueSmX7u2a8+AFQ6xL4CYgLk7466XDSE6M47YZgceAvfT24q2UV2rMj0tcF0sExjOqyh2v5lF4oJR/XDGcVHu2u8no2CqFP184lMX5e/n7h6s8jSXHl0+/Di0Z1LmVp3FEM0sExjPPzl3PRyt28KvxAxnStfEOcmJqNm5IJy4f2Z0nP13LV2sLPYlhy+4ivt2wmymZXexqsw6WCIwn8jbv4S/vruDcQR25+pReXodjXPKbCYPo3b4Ft7/iY29RWcSPn+PzA1gjsqOwRGAibl9xGTe9tJAOqSk8fPEw+6bWhDVPSmDa1EwK9pfwqzcXR/yR0pk+Pyf0bGs91x6FJQITUarK3a8vwr+nmDgDsCsAABFwSURBVEcvz6J1c7sv0NQN7daG288dwNuLt/L6wvyIHXf51n2s3L6fKXaT+KgsEZiIenHeJmYv3sadYzM4oWdbr8MxEfI/Y/pyUu923J+zhI2FByNyzGxfPvFxwnjrwvyoXE8EIhIvIrkiMquGZdeISIGI+JzXT92Ox3hn+dZ9PDBrGWMGpHP9aX28DsdEUHyc8PfLMomPE2572Ud5hbuPlFZWKm/5/Izpn0b7lsmuHqspiMQVwa1AXaNcv6yqmc7rmQjEYzxwsKScn09fSJtmifzt0mHWsCcGdWnTjAcvOJ7cTXt47OM1rh7rmw278O8tto4LQ+RqIhCRbsD5gJ3gY9xvcpawYedBHpmaRZp9Q4tZE4d14cLhXXns49Us2LjLtePk+Pw0T4rnnEEdXTtGU+L2FcE04C6gruvAi0RkkYi8JiI19i8gIteLyHwRmV9QEJ1d3JravbZgC28szOeWH/Tn5L7W10us+92kwXRt24xbZwQ6GQy30vJKZi/eyrmDOlrnhSFyLRGIyARgh6ouqGO1t4BeqjoU+BB4oaaVVPUpVR2hqiPS060v8cZkzY4D/CZ7CaP6tOPms/p7HY6JAqkpiUy7LIute4u5P2dp2Pc/Z+UO9h4qs7YD9eDmFcGpwCQR2QDMAM4Skf8Gr6Cqhapa4rx9GjjBxXhMhBWXVXDT9IU0S4rnkalZxNt9AeM4oWdbbj6rH2/k5pPjC+8jpTk+P+1aJDG6f1pY99uUuZYIVPUeVe2mqr2AqcDHqnpl8DoiEvxc1yTqvqlsGpkHZi1jxbb9/O3SYXRsleJ1OCbK3HRmP4b3aMO92UvYsrsoLPvcX1zGh8u3M2FoZxLj7en4UEX8NyUiD4jIJOftLSKyVETygFuAayIdj3HHrEV+ps/bxP+c3oczMjp4HY6JQgnxcUy7LAtVuP3lPCoqG97q+L2l2ykpr7RqoXqKSCJQ1TmqOsGZvk9VZzrT96jqYFUdpqpnquqKSMRj3LWx8CD3vL6YrB5tuOPcDK/DMVGsR/vmPDB5MN9s2MWTn65t8P5yfPl0b9eM4T3ahCG62GHXTiasSsoruGl6LiLw2OVZdnlujuqCrK5MHNaFv3+wCt/mPce8nx37i/lizU4mD+tq/VfVk31KTVj95Z2VLM7fy8OXDKNbW+voyxydiPCHKUPo2CqF22bkcrCk/Jj281beVioVpmRZ30L1ZYnAhM0Hy7bz3BfrueaUXowd3MnrcEwj0tppcb5xVxEPvLXsmPaR48tncJdW9OtgY17XlyUCExb5ew5xx6t5DOnainvGD/Q6HNMIndSnPT87oy8vz9/Mu0u21mvbdQUHWLRlL1PsJvExsURgGqysopJbXsqlolJ5/PLhJCfEex2SaaRuO3sAQ7u15u43FrNtb3HI2+X4/IgEurAw9WeJwDTY3z9YxYKNu3nwgiH0SmvhdTimEUuMj2PaZZmUlFXyi1d9VIbwSKmqkuPLZ1Tv9nRqbe1VjoUlAtMgn60q4IlP1zL1xO727LYJiz7pLbl/4iC+WFPIs3PXH3X9vC172VBYZDeJG8ASgTlmO/YVc/srPvp3aMn9Ewd7HY5pQi47sTtjB3fkofdWsNS/t851c3z5JMXHMW6IDUBzrCwRmGNSUanc9rKPAyXl/OOK4TRLsvsCJnxEhD9fOJR2LZK4dYaPQ6UVNa5XXlHJW3lbOWtgB1o3s2FPj5UlAnNM/vHJGr5cW8gDk4bQv6M9rmfCr22LJP56SSZrdhzgj7Nr7obsy7WF7DxQwmQbl7hBLBGYepu3rpBpH65iSmYXLhnRzetwTBM2un8a153Wm/98vZGPlm8/Ynm2L5/UlATOHGj9WTWEJQJTL4UHSrhlRi4927fgDxccb035jevuGJvBcZ1bcddri9ix/7tHSg+VVvDekm2cN6QTKYlWNdkQlghMyCorlV+8msfug2U8fkUWLZNt9CfjvuSEeB6dmsmBknLufHURqoFHSj9asZ2DpRXWiCwMLBGYkD0zdx1zVhZw74TjGNyltdfhmBjSv2Mq955/HJ+uKuCFLzcAkJ3rp2OrZE7qY8OfNpQlAhOS3E27eejdlYwb3IkfjerpdTgmBl05qidnDezAH99Zwbx1hXy6agcTh3axke/CwBKBOaq9RWXcND2XTq1T+MvFQ+2+gPGEiPDQxUNplZLAVc99Q1mFMiXLqoXCwRKBqZOq8svXF7F9XzGPXZ5lz2obT6W1TObhS4ZRUl5J3/QWDO7SyuuQmgS722fq9N+vN/Lu0m38avxAsnq09TocYzgzowN/uvB4urVtZlenYWKJwNRqqX8vv5+1nDMz0vnp6D5eh2PMYZeP7OF1CE2KVQ2ZGh0oKeem6bm0bZHIXy/NJM5uyBnTZNkVgTmCqnLvm4vZWHiQ6deNol2LJK9DMsa4yK4IzBFeXbCFbJ+f284ewCh7RtuYJs8Sgfme1dv3c1/OEk7p256fn9nP63CMMRFgicAcdqi0gp9PX0iLpASmXZZpDXWMiRF2j8Ac9ru3lrJq+wH+/ZORdGhlQ/4ZEyvsisAAgVGeZny7mZ+d0ZcxA9K9DscYE0GWCAzrdx7kV28s5oSebbn9nAFeh2OMiTBLBDGupLyCm6YvJCE+jkcvzyIh3v4ljIk1rn/qRSReRHJFZFYNy5JF5GURWSMi80Skl9vxmO/70+wVLPXv438vGUbXNs28DscY44FIfP27Fah5wFG4Ftitqv2AvwN/iUA8xvHe0m08/+UGfnJqb84Z1NHrcIwxHnE1EYhIN+B84JlaVpkMvOBMvwb8QKwXqYjYsruIO1/N4/iurfnleRleh2OM8ZDbVwTTgLuAylqWdwU2A6hqObAXOKIpq4hcLyLzRWR+QUGBW7HGjLKKSm5+KZdKhcevyCI5wcZ7NSaWuZYIRGQCsENVF9S1Wg3z9IgZqk+p6ghVHZGebo82NtRf319F7qY9/Pmi4+nZvoXX4RhjPObmFcGpwCQR2QDMAM4Skf9WW2cL0B1ARBKA1sAuF2OKeXNW7uDJT9dy+cgeTBjaxetwjDFRwLVEoKr3qGo3Ve0FTAU+VtUrq602E7jamb7YWeeIKwITHtv3FXP7K3lkdEzl/omDvA7HGBMlIt7FhIg8AMxX1ZnAs8B/RGQNgSuBqZGOJ1ZUVCq3zsjlUGkF//hhFimJdl/AGBMQkUSgqnOAOc70fUHzi4FLIhFDrHvs49V8vW4X/3vJMPp1SPU6HGNMFLFmpDHgy7U7eeSj1VyY1ZWLT+jmdTjGmChjiaCJ23mghNtm+OjdvgW/nzLE63CMMVHIuqFuwiorldtfyWPPoTKe//FIWiTbn9sYcyQ7M0RYeUUlxeWVFJdVOK8apsurz69tWSUl5RUcKq1hvrNuWYXy+ylDGNSllddFN8ZEqZhPBOUVlRwKOtGWVDuhFpdVOMsrKC53TrDVTsyHSispLq9wltV2Mg9Ml1ce29OxIpCSEE9KYhzNEuNJSYwnOTHwPiUhntSUBFKc+SmJcYen+6S1sPsCxpg6xUwimLNyB7+ftezwt+iGnpjjBFIS44NOynGHT9QpifG0apZ4+CSdkhT/vWWHfyY42zn7aFZtWUpi3OGTfVJ8HNYNkzHGDTGTCFo1S2Rgp1YkB32jPnyiTgw+6caTkhBHs6T4752QD5/snXmJ8WInZmNMkxAziWB4j7YM/2Fbr8MwxpioY4+PGmNMjLNEYIwxMc4SgTHGxDhLBMYYE+MsERhjTIyzRGCMMTHOEoExxsQ4SwTGGBPjpLGNDCkiBcDGY9w8DdgZxnAaAytzbLAyx4aGlLmnqqbXtKDRJYKGEJH5qjrC6zgiycocG6zMscGtMlvVkDHGxDhLBMYYE+NiLRE85XUAHrAyxwYrc2xwpcwxdY/AGGPMkWLtisAYY0w1lgiMMSbGNZlEICLjRGSliKwRkbtrWP53EfE5r1Uisido2dUistp5XR3ZyI9dA8tcEbRsZmQjP3YhlLmHiHwiIrkiskhExgctu8fZbqWIjI1s5MfuWMssIr1E5FDQ3/nJyEdffyGUt6eIfOSUdY6IdAta1lQ/y3WVueGfZVVt9C8gHlgL9AGSgDxgUB3r3ww850y3A9Y5P9s60229LpObZXbeH/C6DG6UmcDNtBud6UHAhqDpPCAZ6O3sJ97rMrlc5l7AEq/L4EJ5XwWudqbPAv7jTDfZz3JtZXbeN/iz3FSuCEYCa1R1naqWAjOAyXWsfznwkjM9FvhAVXep6m7gA2Ccq9GGR0PK3FiFUmYFWjnTrQG/Mz0ZmKGqJaq6Hljj7C/aNaTMjVEo5R0EfORMfxK0vCl/lmsrc1g0lUTQFdgc9H6LM+8IItKTwDfCj+u7bZRpSJkBUkRkvoh8LSJT3AszrEIp82+BK0VkCzCbwJVQqNtGo4aUGaC3U2X0qYic5mqk4RFKefOAi5zpC4BUEWkf4rbRqCFlhjB8lptKIpAa5tX2XOxU4DVVrTiGbaNJQ8oM0EMDTdWvAKaJSN9wB+iCUMp8OfC8qnYDxgP/EZG4ELeNRg0p81YCf+cs4HZguoi0IrqFUt47gNNFJBc4HcgHykPcNho1pMwQhs9yU0kEW4DuQe+7Ufvl8VS+X0VSn22jSUPKjKr6nZ/rgDlAVvhDDLtQynwt8AqAqn4FpBDoqKsp/51rLLNTDVbozF9AoB56gOsRN8xRy6uqflW90Elwv3bm7Q1l2yjVkDKH57Ps9Y2SMN1sSSBwY6g3391sGVzDehnABpyGdPrdDab1BG4utXWm23ldJpfL3BZIdqbTgNXUcaM5Wl6hlBl4B7jGmT7O+UAJMJjv3yxeR+O4WdyQMqdXlZHAjcj8aP/fDrG8aUCcM/0g8IAz3WQ/y3WUOSyfZc9/CWH8ZY4HVhH41vNrZ94DwKSgdX4L/LmGbX9C4ObhGuDHXpfF7TIDpwCLnX+4xcC1XpclXGUmcFPtC6dsPuDcoG1/7Wy3EjjP67K4XWYCdcpLnfkLgYlelyVM5b3YOeGtAp6pOhE6y5rkZ7m2Mofrs2xdTBhjTIxrKvcIjDHGHCNLBMYYE+MsERhjTIyzRGCMMTHOEoExxsQ4SwQmJohIGxH5mTN9hojMcuEY14jI4/XcZoOIpNUw/7cickf4ojOmdpYITKxoA/ysPhuISLxLsRgTVSwRmFjxZ6CviPiAh4GWIvKaiKwQkRdFRODwN/T7RGQucImI9BWRd0VkgYh8LiIDnfUuEZElIpInIp8FHaeLs/5qEXmoaqaIXC4ii51t/lJTgCLya6dP+g8JtAivmn+LiCxz+qKfEf5fjYl1CV4HYEyE3A0MUdVMETkDyCHQ7YSfQKvcU4G5zrrFqjoaQEQ+Am5Q1dUichLwfwT6g78PGKuq+SLSJug4mQT6eikBVorIY0AF8BfgBGA38L6ITFHV7KqNROQEAn1CZRH4XC4EFgTF3ltVS6ody5iwsCsCE6u+UdUtqlpJoFuGXkHLXgYQkZYEmvC/6lxJ/BPo7KzzBfC8iFxHYGCRKh+p6l5VLQaWAT2BE4E5qlqgquXAi8CYavGcBrypqkWqug8IHmlqEfCiiFzJdz1OGhM2dkVgYlVJ0HQF3/8sHHR+xgF7VDWz+saqeoNzhXA+4BORqnVq2m9N3QzXpLb+Xs4nkDgmAb8RkcFOQjEmLOyKwMSK/UBqfTZwvpmvF5FLACRgmDPdV1Xnqep9wE6+341wdfMI9CWf5tyAvhz4tNo6nwEXiEgzEUkFJjrHiQO6q+onwF0Ebnq3rE85jDkauyIwMUFVC0XkCxFZAhwCtoe46Q+BJ0TkXiCRwDCCecDDItKfwLf9j5x5R1w5OMfeKiL3EBhiUIDZqppTbZ2FIvIygWqqjcDnzqJ44L8i0trZ9u+quifUchsTCut91BhjYpxVDRljTIyzRGCMMTHOEoExxsQ4SwTGGBPjLBEYY0yMs0RgjDExzhKBMcbEuP8P/vsLvHiU4s0AAAAASUVORK5CYII=\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "#Compute benchmarks with a fingerprint counts collection.\n",
- "thresholds = [0.7, 0.75, 0.8, 0.85, 0.9, 0.95]\n",
- "times = []\n",
- "repetitions = 5\n",
- "for t in thresholds: \n",
- " print(\"Measuring performance for similarity threshold {}.\".format(t))\n",
- " temp_times = []\n",
- " for r in range(repetitions):\n",
- " start = time.time()\n",
- " for m in db.molecules.find():\n",
- " mol = Chem.Mol(m['rdmol'])\n",
- " _ = similarity.similaritySearch(mol, db, t)\n",
- " end = time.time()\n",
- " temp_times.append(end - start)\n",
- " times.append([t, mean(temp_times)])\n",
- "print(times)\n",
- "x_list = [v[0] for v in times]\n",
- "y_list = [v[1]*1000 for v in times]\n",
- "plt.xlabel('thresholds')\n",
- "plt.ylabel('time (ms)')\n",
- "plt.title('Without Aggregation')\n",
- "plt.plot(x_list, y_list)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 45,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Measuring performance for similarity threshold 0.7.\n",
- "Measuring performance for similarity threshold 0.75.\n",
- "Measuring performance for similarity threshold 0.8.\n",
- "Measuring performance for similarity threshold 0.85.\n",
- "Measuring performance for similarity threshold 0.9.\n",
- "Measuring performance for similarity threshold 0.95.\n",
- "[[0.7, 0.009987068176269532], [0.75, 0.0059474468231201175], [0.8, 0.005334234237670899], [0.85, 0.0038384437561035157], [0.9, 0.0040286540985107425], [0.95, 0.0037296295166015627]]\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "[]"
- ]
- },
- "execution_count": 45,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "times = []\n",
- "for t in thresholds: \n",
- " print(\"Measuring performance for similarity threshold {}.\".format(t))\n",
- " temp_times = []\n",
- " for r in range(repetitions):\n",
- " start = time.time()\n",
- " for m in db.molecules.find():\n",
- " mol = Chem.Mol(m['rdmol'])\n",
- " _ = similarity.similaritySearchAggregate(mol, db, t)\n",
- " end = time.time()\n",
- " temp_times.append(end - start)\n",
- " times.append([t, mean(temp_times)])\n",
- "print(times)\n",
- "x_list = [v[0] for v in times]\n",
- "y_list = [v[1]*1000 for v in times]\n",
- "plt.xlabel('thresholds')\n",
- "plt.ylabel('time (ms)')\n",
- "plt.title('Without Aggregation')\n",
- "plt.plot(x_list, y_list)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "py37_rdkit_beta",
- "language": "python",
- "name": "py37_rdkit_beta"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.7"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/docs/notebooks/Similarity and Substructure Search.ipynb b/docs/notebooks/Similarity and Substructure Search.ipynb
index c4263b5..33285bc 100644
--- a/docs/notebooks/Similarity and Substructure Search.ipynb
+++ b/docs/notebooks/Similarity and Substructure Search.ipynb
@@ -6,18 +6,19 @@
"source": [
"# Similarity and Substructure Search\n",
"\n",
- "Last updated: 7/27/20\n",
+ "Last updated: 8/11/20\n",
"\n",
"Methods for similarity and substructure search are included in the `mongordkit.Search` module."
]
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from mongordkit.Search import similarity, substructure, utils\n",
+ "from mongordkit import Search\n",
"from mongordkit.Database import create, write\n",
"from rdkit import Chem\n",
"import pymongo"
@@ -29,28 +30,70 @@
"source": [
"## Reset Cells\n",
"\n",
- "Run these cells to reset the local MongoDB instance used in this notebook."
+ "Run these cells to reset the MongoDB database used in this notebook."
]
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": 2,
"metadata": {},
+ "outputs": [],
+ "source": [
+ "client = pymongo.MongoClient()\n",
+ "client.drop_database('demo_db')\n",
+ "demo_db = client.demo_db\n",
+ "\n",
+ "# Disable rdkit warnings\n",
+ "rdkit.RDLogger.DisableLog('rdApp.*')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Preparing for Search\n",
+ "Adequately preparing the database for searching requires adding a variety of fingerprints and hashes. You can easily perform all of the setup work required for similarity and substructure search by calling the method `Search.PrepareForSearch`. Generally, workflow will follow straight from the following two lines into search calls:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "scrolled": true
+ },
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "['TestDatabase', 'admin', 'config', 'db', 'local']\n",
- "['admin', 'config', 'db', 'local']\n"
+ "populating mongodb collection with compounds from SDF...\n",
+ "200 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "Preparing database and collections for search...\n",
+ "Added pattern fps, morgan fps, and support for LSH.\n"
]
}
],
"source": [
- "client = pymongo.MongoClient()\n",
- "print(client.list_database_names())\n",
- "client.drop_database('TestDatabase')\n",
- "print(client.list_database_names())"
+ "write.WriteFromSDF(demo_db.molecules, '../../data/test_data/first_200.props.sdf')\n",
+ "Search.PrepareForSearch(demo_db, demo_db.molecules, demo_db.mfp_counts, demo_db.permutations)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "However, the rest of this notebook will explicitly note the addition of fingerprints and hashes in an effort to better communicate how the code actually works. Let's reset the database again so that we can insert the hashes step by step without any issues."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "client.drop_database('demo_db')\n",
+ "demo_db = client.demo_db"
]
},
{
@@ -59,29 +102,221 @@
"source": [
"## Similarity Search\n",
"\n",
- "`mongordkit.Search.similarity` supports similarity search best on a database prepared by `mongordkit.Database.write`. Users can also use any database that has a `molecules` collection where each document in that collection has the following fields:\n",
+ "`mongordkit.Search.similarity` supports similarity search best on a MongoDB collection prepared by `mongordkit.Database.write`. For the general level of similarity search, users can also use any collection that has documents with the following fields:\n",
"- `'rdmol': binary pickle object`\n",
- "- `'smiles': some SMILES string`"
+ "- `'index': a unique identifier for each molecule`\n",
+ "- `'fingerprints': {a nested document that can be blank at the start}'`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "Let's run through an example of similarity search. First, we'll have to set up our database:"
+ "Let's run through an example of similarity search. First, we'll write into the database 200 molecules from a data file included in the `mongordkit` package. We will use default write settings."
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "metadata": {},
+ "execution_count": 5,
+ "metadata": {
+ "scrolled": true
+ },
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "populating mongodb collection with compounds from chembl...\n",
- "200 molecules successfully imported\n"
+ "populating mongodb collection with compounds from SDF...\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:23] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:23] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:23] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:23] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:23] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:23] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:23] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:38] WARNING: Accepted unusual valence(s): Cu(4); Metal was disconnected\n",
+ "RDKit WARNING: [15:43:38] WARNING: Accepted unusual valence(s): Cu(4); Metal was disconnected\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:38] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Accepted unusual valence(s): Cu(4); Metal was disconnected; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Accepted unusual valence(s): Cu(4); Metal was disconnected; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:39] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:39] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:39] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Charges were rearranged; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Charges were rearranged; Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:39] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:39] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:39] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:39] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:40] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:40] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:40] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:40] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:40] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:40] WARNING: Charges were rearranged\n",
+ "RDKit WARNING: [15:43:40] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:40] WARNING: Omitted undefined stereo\n",
+ "RDKit WARNING: [15:43:40] WARNING: Omitted undefined stereo\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "200 molecules successfully imported\n",
+ "0 duplicates skipped\n"
]
},
{
@@ -90,90 +325,90 @@
"200"
]
},
- "execution_count": 4,
+ "execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
- "TestDB = create.createFromHostPort('TestDatabase', host='localhost', port=27017)\n",
- "write.writeFromSDF(TestDB, '../../data/test_data/first_200.props.sdf', 'test')"
+ "write.WriteFromSDF(demo_db.molecules, '../../data/test_data/first_200.props.sdf')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "`similarity.SimSearchNaive` will directly loop through the database and display results. However, this implementation is extremely slow for any decently-sized database. Instead, `similarity` supports precalculating the following kinds of fingerprints for screening: \n",
- "- Morgan (length 1048)\n",
+ "`similarity.SimSearchNaive` will directly loop through the database and display results. This is good for purposes of verifying accuracy. However, this implementation is extremely slow for any decently-sized database. Instead, `similarity` supports precalculating the following kinds of fingerprints for screening: \n",
+ "- Morgan (default radius 2, length 2048)\n",
"\n",
- "through `similarity.addMorganFingerprints`. For each document in a passed in database's `molecules` collection, this method creates a nested field that contains `{morgan_fp: {bits: }, {count: }}`. Note that `addMorganFingerprints` also creates indices on `morgan_fp[bits]` and `morgan_fp[count]` to speed search. "
+ "through `similarity.AddMorganFingerprints`. For each document in a passed in collection, this method adds the nested field `{morgan_fp: {bits: }, {count: }}` to the document's `fingerprint` field. `AddMorganFingerprints` also creates indices on `morgan_fp[bits]` and `morgan_fp[count]` to speed search. "
]
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
- "similarity.addMorganFingerprints(TestDB, radius=2, length=1024)"
+ "similarity.AddMorganFingerprints(demo_db.molecules, demo_db.mfp_counts)"
]
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "{'bits': [33,\n",
- " 56,\n",
- " 84,\n",
- " 130,\n",
- " 313,\n",
+ "{'bits': [84,\n",
" 314,\n",
" 356,\n",
" 547,\n",
" 650,\n",
- " 698,\n",
- " 744,\n",
" 747,\n",
- " 849,\n",
- " 853,\n",
- " 967],\n",
- " 'count': 15}"
+ " 967,\n",
+ " 1057,\n",
+ " 1080,\n",
+ " 1154,\n",
+ " 1337,\n",
+ " 1380,\n",
+ " 1722,\n",
+ " 1768,\n",
+ " 1873,\n",
+ " 1877],\n",
+ " 'count': 16}"
]
},
- "execution_count": 6,
+ "execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
- "TestDB.molecules.find_one()['morgan_fp']"
+ "demo_db.molecules.find_one()['fingerprints']['morgan_fp']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "From here, we can directly perform similarity search. `similarity` provides two methods that take advantage of fingerprint screening: `similaritySearch` and `similaritySearchAggregate`. The latter shifts much of the computation into the MongoDB server by using an aggregation pipeline and may improve performance when working with performant or sharded MongoDB servers. "
+ "From here, we can directly perform similarity search. `similarity` provides two methods that take advantage of fingerprint screening: `similaritySearch` and `similaritySearchAggregate`. The latter shifts much of the computation into the MongoDB server by using an aggregation pipeline and can dramatically improve performance when working with sharded MongoDB servers."
]
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "similaritySearch: [[0.35294117647058826, 'c1ccc(P(c2ccccc2)c2ccccc2)cc1'], [0.4117647058823529, 'Cc1ccc(S)cc1'], [0.35, 'CC(O)(c1ccccc1)c1ccccc1']]\n",
+ "similaritySearch: [[0.4117647058823529, 'WLHCBQAPPJAULW-UHFFFAOYSA-N']]\n",
"\n",
"\n",
- "similaritySearchAggregate: [[0.35294117647058826, 'c1ccc(P(c2ccccc2)c2ccccc2)cc1'], [0.4117647058823529, 'Cc1ccc(S)cc1'], [0.35, 'CC(O)(c1ccccc1)c1ccccc1']]\n"
+ "similaritySearchAggregate: [[0.4117647058823529, 'WLHCBQAPPJAULW-UHFFFAOYSA-N']]\n"
]
}
],
@@ -181,46 +416,108 @@
"q_mol = Chem.MolFromSmiles('Cc1ccccc1')\n",
"\n",
"# Perform a similarity search on TestDB for q_mol with a Tanimoto threshold of 0.4. \n",
- "results1 = similarity.similaritySearch(q_mol, TestDB, 0.35)\n",
+ "results1 = similarity.SimSearch(q_mol, demo_db.molecules, demo_db.mfp_counts, 0.4)\n",
"\n",
"# Do the same thing, but use the MongoDB Aggregation Pipeline. \n",
- "results2 = similarity.similaritySearchAggregate(q_mol, TestDB, 0.35)\n",
+ "results2 = similarity.SimSearchAggregate(q_mol, demo_db.molecules, demo_db.mfp_counts, 0.4)\n",
"\n",
"print('similaritySearch: {}'.format(results1))\n",
"print('\\n')\n",
"print('similaritySearchAggregate: {}'.format(results2))"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that the search returns only the index for the molecule, which in this case is the inchikey; users should find it easy to go from the index to the full molecule document by way of a quick search. This also makes it easier for users to retrieve molecules when indices represent multiple tautomers or isomers in the collection.\n",
+ "\n",
+ "`SimSearch` and `SimSearchAggregate` both make use of the conventional fingerprint screening method. `similarity` also supports searching using Locality Sensitive Hashing, as developed by ChemBL in an excellent [blog post](http://chembl.blogspot.com/2015/08/lsh-based-similarity-search-in-mongodb.html). The method here is called `SimSearchLSH` and requires a little bit more setup work:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Generate 100 different permutations of length 2048 and save them in demo_db.permutations as separate documents.\n",
+ "similarity.AddRandPermutations(demo_db.permutations)\n",
+ "\n",
+ "# Add locality-sensitive hash values to each documents in demo_db.molecules by splitting the 100 different permutations\n",
+ "# in demo_db.permutations into 25 different buckets. \n",
+ "similarity.AddLocalityHashes(demo_db.molecules, demo_db.permutations, 25)\n",
+ "\n",
+ "# Create 25 different collections in db_demo each store a subset of hash values for molecules in demo_db.molecules.\n",
+ "similarity.AddHashCollections(demo_db, demo_db.molecules)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now let's try a search using the query molecule from earlier:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "similaritySearchLSH: []\n"
+ ]
+ }
+ ],
+ "source": [
+ "q_mol = Chem.MolFromSmiles('Cc1ccccc1')\n",
+ "\n",
+ "results3 = similarity.SimSearchLSH(q_mol, demo_db, demo_db.molecules, \n",
+ " demo_db.permutations, demo_db.mfp_counts, threshold=0.8)\n",
+ "\n",
+ "print('similaritySearchLSH: {}'.format(results3))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The LSH algorithm relies on random permutations using the `numpy` module, so it yields non-deterministic results. This means that LSH is well-suited for *scanning* datasets (its performance on large datasets is faster than either similarity search method), but is less accurate than regular similarity search, especially below thresholds of 0.7. Specific notes on benchmarks can be found in \"Benchmarking Similarity Search.\""
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Substructure Search\n",
"\n",
- "Likewise, `mongordkit.Search.substructure` supports substructure search best on databases prepared by `write`. Database requirements are identical to those for similarity search: a `molecules` collection whose documents have `rdmol` and `smiles` fields. \n",
+ "`mongordkit.Search.substructure` supports substructure search best on collections prepared by `write`. Requirements are identical to those for similarity search: a `molecules` collection whose documents have `rdmol` and `index` fields. \n",
"\n",
"`substructure.SubSearchNaive` provides a fingerprint-less, slower implementation of substructure search suitable for very small databases:"
]
},
{
"cell_type": "code",
- "execution_count": 27,
+ "execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "['c1ccc(-c2ccccc2OCCOc2ccccc2-c2ccccc2)cc1',\n",
- " 'COc1ccc(Cc2ccc(OC)cc2)cc1',\n",
- " 'COc1cc([N+](=O)[O-])c(N)c([N+](=O)[O-])c1',\n",
- " 'COc1ccc(/C=N/O)cc1',\n",
- " 'Cc1nc2ccccc2c(Oc2ccccc2)c1-c1ccccc1',\n",
- " 'O/N=C/c1ccc2c(c1)OCO2',\n",
- " 'COc1ccc(CC#N)cc1',\n",
- " 'COc1ccc(C(C)(C)C#N)cc1']"
+ "['RUTYZGCHBCCSKD-UHFFFAOYSA-N',\n",
+ " 'WECJUPODCKXNQK-UHFFFAOYSA-N',\n",
+ " 'GZZJZWYIOOPHOV-UHFFFAOYSA-N',\n",
+ " 'FXOSHPAYNZBSFO-RMKNXTFCSA-N',\n",
+ " 'KWLUBKHLCNCFQI-UHFFFAOYSA-N',\n",
+ " 'VDAJDWUTRXNYMU-RUDMXATFSA-N',\n",
+ " 'PACGLQCRGWFBJH-UHFFFAOYSA-N',\n",
+ " 'CDCRUVGWQJYTFO-UHFFFAOYSA-N']"
]
},
- "execution_count": 27,
+ "execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
@@ -229,7 +526,7 @@
"q_mol = Chem.MolFromSmiles('C1=CC=CC=C1OC')\n",
"\n",
"# Perform a substructure search for q_mol on TestDB. \n",
- "substructure.SubSearchNaive(q_mol, TestDB, chirality=False)"
+ "substructure.SubSearchNaive(q_mol, demo_db.molecules, chirality=False)"
]
},
{
@@ -241,31 +538,44 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": 13,
"metadata": {},
"outputs": [
{
- "ename": "NameError",
- "evalue": "name 'substructure' is not defined",
- "output_type": "error",
- "traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
- "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0msubstructure\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAddPatternFingerprints\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mTestDB\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmolecules\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTestDB\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmorgan_fp_counts\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlength\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0msubstructure\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mSubSearch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mq_mol\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTestDB\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mchirality\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;31mNameError\u001b[0m: name 'substructure' is not defined"
- ]
+ "data": {
+ "text/plain": [
+ "['RUTYZGCHBCCSKD-UHFFFAOYSA-N',\n",
+ " 'WECJUPODCKXNQK-UHFFFAOYSA-N',\n",
+ " 'GZZJZWYIOOPHOV-UHFFFAOYSA-N',\n",
+ " 'FXOSHPAYNZBSFO-RMKNXTFCSA-N',\n",
+ " 'KWLUBKHLCNCFQI-UHFFFAOYSA-N',\n",
+ " 'VDAJDWUTRXNYMU-RUDMXATFSA-N',\n",
+ " 'PACGLQCRGWFBJH-UHFFFAOYSA-N',\n",
+ " 'CDCRUVGWQJYTFO-UHFFFAOYSA-N']"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
}
],
"source": [
- "substructure.AddPatternFingerprints(TestDB.molecules, TestDB.mfp_counts, length=None)\n",
- "substructure.SubSearch(q_mol, TestDB, chirality=False)"
+ "substructure.AddPatternFingerprints(demo_db.molecules)\n",
+ "substructure.SubSearch(q_mol, demo_db.molecules, chirality=False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Substructure Searching using Locality Sensitive Hashing"
+ "## `.Search` contents"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "mongordkit.Search.**PrepareForSearch**(db (*MongoDB database for hash information*), mol_collection (*MongoDB collection*), count_collection (*MongoDB collection*), perm_collection (*MongoDB collection*)) --> None"
]
},
{
@@ -274,13 +584,22 @@
"source": [
"## `.similarity` Contents\n",
"\n",
+ "### Constants:\n",
+ "- DEFAULT_THRESHOLD = 0.8\n",
+ "- DEFAULT_MORGAN_RADIUS = 2\n",
+ "- DEFAULT_MORGAN_LENGTH = 2048\n",
+ "- DEFAULT_BIT_N = 2048\n",
+ "- DEFAULT_BUCKET_N = 25\n",
+ "- DEFAULT_PERM_LEN = 2048\n",
+ "- DEFAULT_PERM_N = 100\n",
+ "\n",
"mongordkit.Search.similarity.**AddMorganFingerprints**(mol_collection (*MongoDB collection*), count_collection (*MongoDB collection*), radius=2 (*int: radius of Morgan fingerprint*), length=2048 (*int: length of Morgan fingerprint bit vector*)) --> None\n",
"\n",
- "mongordkit.Search.similarity.**SimSearchNaive**(mol (*rdmol object*), mol_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, smiles]*\n",
+ "mongordkit.Search.similarity.**SimSearchNaive**(mol (*rdmol object*), mol_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, index]*\n",
"\n",
- "mongordkit.Search.similarity.**SimSearch**(mol (*rdmol object*), mol_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, smiles]*\n",
+ "mongordkit.Search.similarity.**SimSearch**(mol (*rdmol object*), mol_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, index]*\n",
"\n",
- "mongordkit.Search.similarity.**SimSearchAggregate**(mol (*rdmol object*), mol_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, smiles]*\n",
+ "mongordkit.Search.similarity.**SimSearchAggregate**(mol (*rdmol object*), mol_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, index]*\n",
"\n",
"mongordkit.Search.similarity.**AddRandPermutations**(perm_collection (*MongoDB collection*), len=2048 (*int: length corresponding to length of fingerprint bit vectors*), num=100 (*int: number of permutations*)) --> None\n",
"\n",
@@ -288,7 +607,7 @@
"\n",
"mongordkit.Search.similarity.**AddHashCollections**(db (*MongoDB database*), mol_collection (*MongoDB collection*)) --> None\n",
"\n",
- "mongordkit.Search.similarity.**SimSearchLSH**(mol (*rdmol object*), db (*MongoDB database containing hash collections*), mol_collection (*MongoDB collection*), perm_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, smiles]*"
+ "mongordkit.Search.similarity.**SimSearchLSH**(mol (*rdmol object*), db (*MongoDB database containing hash collections*), mol_collection (*MongoDB collection*), perm_collection (*MongoDB collection*), count_collection (*MongoDB collection*), threshold=0.8 (*Tanimoto threshold between 0 and 1, float*)) --> *list: results with format [tanimoto, index]*"
]
},
{
@@ -297,7 +616,7 @@
"source": [
"## `.substructure` Contents\n",
"\n",
- "mongordkit.Search.substructure.**AddPatternFingerprints**(db, length=2048 (*int: length of Pattern fingerprint bit vector*)) --> None\n",
+ "mongordkit.Search.substructure.**AddPatternFingerprints**(mol_collection (MongoDB collection), length=2048 (*int: length of Pattern fingerprint bit vector*)) --> None\n",
"\n",
"mongordkit.Search.similarity.**SubSearchNaive**(pattern (*rdmol object*), db, chirality=False (*boolean: include chirality in search or not*)) --> *list: results with format [smiles]*\n",
"\n",
diff --git a/docs/notebooks/Substructure Benchmarking.ipynb b/docs/notebooks/Substructure Benchmarking.ipynb
index 26f2eb8..53a5ef7 100644
--- a/docs/notebooks/Substructure Benchmarking.ipynb
+++ b/docs/notebooks/Substructure Benchmarking.ipynb
@@ -15,7 +15,8 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Imports"
+ "## Setup Work\n",
+ "### Imports"
]
},
{
@@ -38,105 +39,98 @@
"import numpy as np\n",
"from os import sys\n",
"import pandas as pd\n",
- "from statistics import mean, median\n",
"from IPython.display import display, HTML\n",
"\n",
"from mongordkit.Database import write\n",
"from mongordkit.Search import similarity\n",
- "from mongordkit.Search import substructure"
+ "from mongordkit.Search import substructure\n",
+ "from mongordkit import Search"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Database Setup\n",
- "Here we set up a database called `test` that will hold our molecules. We will construct 1 collection called `molecules_100K` to hold the first 100,000 molecules in the ChEMBL_27 dataset and a collection called `molecules_1M` to hold the first 1,000,000 molecules in the ChEMBL_27 dataset."
+ "### Database Setup\n",
+ "Here we set up a database called `test` that will hold our molecules. We will construct a collection called `molecules_100K` to hold the first 100,000 molecules in the ChEMBL_27 dataset and a collection called `molecules_1M` to hold the first 1,000,000 molecules in the ChEMBL_27 dataset. If you have already run search or similarity benchmarks from `mongo-rdkit` on your local MongoDB instance, these should have been set up already."
]
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Initialize the client that will connect to the database.\n",
"client = pymongo.MongoClient()\n",
- "db = client.test"
+ "db = client.test\n",
+ "chembl = '../../../chembl_27.sdf'\n",
+ "\n",
+ "# Disable rdkit warnings\n",
+ "rdkit.RDLogger.DisableLog('rdApp.*')"
]
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "populating mongodb collection with compounds from chembl...\n"
- ]
- },
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "RDKit WARNING: [15:22:20] Warning: conflicting stereochemistry at atom 11 ignored.\n",
- "RDKit WARNING: [15:45:12] Warning: conflicting stereochemistry at atom 14 ignored.\n",
- "RDKit WARNING: [16:15:11] Warning: conflicting stereochemistry at atom 10 ignored.\n",
- "RDKit WARNING: [16:15:11] Warning: conflicting stereochemistry at atom 10 ignored.\n",
- "RDKit WARNING: [16:15:40] Warning: conflicting stereochemistry at atom 10 ignored.\n",
- "RDKit WARNING: [16:15:40] Warning: conflicting stereochemistry at atom 10 ignored.\n",
- "RDKit WARNING: [16:26:44] Warning: conflicting stereochemistry at atom 6 ignored.\n",
- "RDKit WARNING: [16:26:44] Warning: conflicting stereochemistry at atom 6 ignored.\n"
- ]
- },
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "101001 molecules successfully imported\n"
+ "populating mongodb collection with compounds from SDF...\n",
+ "100000 molecules successfully imported\n",
+ "1 duplicates skipped\n"
]
},
{
"data": {
"text/plain": [
- "101001"
+ "100000"
]
},
- "execution_count": 15,
+ "execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
- "# Write the first 100,000 compounds to molecules_100K. \n",
- "write.writeFromSDF(db.molecules_100K, '../../../chembl_27.sdf', 'test', reg_option='standard_setting', \n",
- " index_option='inchikey', chunk_size=1000, limit=100000)"
+ "# If necessary, write the first 100,000 compounds to molecules_100K.\n",
+ "if db.molecules_100K.count_documents({}) != 100000:\n",
+ " write.WriteFromSDF(db.molecules_100K, chembl, chunk_size=1000, limit=100000)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "populating mongodb collection with compounds from SDF...\n"
+ ]
+ }
+ ],
"source": [
- "# Write the first 1,000,000 compounds to molecules_1M.\n",
- "write.writeFromSDF(db.molecules_1M, '../../../chembl27_sdf', 'test', reg_option='standard_setting', \n",
- " index_option='inchikey', chunk_size=1000, limit=1000000)"
+ "# If necessary, write the first 1,000,000 compounds to molecules_1M.\n",
+ "if db.molecules_1M.count_documents({}) != 1000000:\n",
+ " write.WriteFromSDF(db.molecules_1M, chembl, chunk_size=1000, limit=1000000)"
]
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "In molecules_100K: 101000 documents\n",
- "In molecules_1M: 0 documents\n"
+ "In molecules_100K: 100000 documents\n",
+ "In molecules_1M: 180512 documents\n"
]
}
],
@@ -146,11 +140,22 @@
"print(f\"In molecules_1M: {db.molecules_1M.count_documents({})} documents\")"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Next, we have to prepare all of the documents in our collections for search by adding in fingerprints.\n",
+ "substructure.AddPatternFingerprints(db.molecules_100K)\n",
+ "substructure.AddPatternFingerprints(db.molecules_1M)"
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Query Set Setup\n",
+ "### Query Set Setup\n",
"For our queries, we'll use three sets of patterns identified by Greg Landrum in one of his [blog posts](http://rdkit.blogspot.com/2013/11/fingerprint-based-substructure.html) on substructure searching and discussed in this [mailing list](http://www.mail-archive.com/rdkit-discuss@lists.sourceforge.net/msg02066.html) and this [presentation](http://www.hinxton.wellcome.ac.uk/advancedcourses/MIOSS%20Greg%20Landrum.pdf). They are: \n",
"- Fragments: 500 diverse molecules taken from the ZINC Fragments set\n",
"- Leads: 500 diverse molecules taken from the ZINC Lead-like set\n",
@@ -159,7 +164,7 @@
},
{
"cell_type": "code",
- "execution_count": 16,
+ "execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
@@ -184,12 +189,12 @@
"### Naive Substructure Search\n",
"`substructure.SubSearchNaive` is a search that simply loops through the dataset and checks for a substructure match on each molecule. This method is not directly benchmarked here because searching through a single molecule takes upward of 5 seconds; this means that it is far too slow to feel directly interactive.\n",
"### Substructure Search with Fingerprint Screening\n",
- "Instead, we will benchmark the standard `SubSearch`, which makes use of fingerprint screening to dramatically increase efficiency. First, we want to see what kinds of times we are dealing with. For each of our query sets, we will search all of their elements against `molecules_100K` and `molecules_1M`, then return the median and mean query times in seconds. "
+ "Instead, we will benchmark the standard `SubSearch`, which makes use of fingerprint screening to dramatically increase efficiency. For each of our query sets, we will search all of their elements against `molecules_100K` and `molecules_1M`, then return the median and mean query times in seconds. "
]
},
{
"cell_type": "code",
- "execution_count": 18,
+ "execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
@@ -199,34 +204,118 @@
" start = time.time()\n",
" substructure.SubSearch(pattern, dataset)\n",
" end = time.time()\n",
- " results.append(end - start)"
+ " results.append(end - start)\n",
+ " return results"
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 12,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " mean | \n",
+ " median | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " fragments | \n",
+ " 0.062740 | \n",
+ " 0.062074 | \n",
+ "
\n",
+ " \n",
+ " leads | \n",
+ " 0.062592 | \n",
+ " 0.062289 | \n",
+ "
\n",
+ " \n",
+ " pieces | \n",
+ " 0.062739 | \n",
+ " 0.061950 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " mean median\n",
+ "fragments 0.062740 0.062074\n",
+ "leads 0.062592 0.062289\n",
+ "pieces 0.062739 0.061950"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"# Benchmark for search of all three query sets against 100K and 1M.\n",
- "# This should take around five minutes; these calls can be split up if necessary.\n",
+ "# This should take around five minutes; these calls commented out if necessary.\n",
"frag_times_100K = benchmark_query_set(fragments, db.molecules_100K)\n",
- "frag_times_1M = benchmark_query_set(fragments, db.molecules_1M)\n",
"lead_times_100K = benchmark_query_set(leads, db.molecules_100K)\n",
- "lead_times_1M = benchmark_query_set(leads, db.molecules_1M)\n",
"pieces_times_100K = benchmark_query_set(pieces, db.molecules_100K)\n",
- "pieces_times_1M = benchmark_query_set(pieces, db.molecules_1M)\n",
"\n",
- "results = [frag_times_100K, frag_times_1M, lead_times_100K, lead_times_1M, pieces_times_100K, pieces_times_1M]\n",
+ "results = [frag_times_100K, lead_times_100K, pieces_times_100K]\n",
+ "means_100K = [np.mean(times) for times in results]\n",
+ "medians_100K = [np.median(times) for times in results]\n",
"\n",
- "means = [mean(times) for times in results]\n",
- "medians = [median(times) for times in results]\n",
+ "data = {'mean (100K)': means, 'median (100K)': medians}\n",
+ "df = pd.DataFrame(data, index =['fragments', 'leads', 'pieces']) \n",
+ "df"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Benchmark for search of all three query sets against 1M. \n",
+ "# This should take around five minutes; these calls can be commented out if necessary.\n",
+ "frag_times_1M = benchmark_query_set(fragments, db.molecules_1M)\n",
+ "lead_times_1M = benchmark_query_set(leads, db.molecules_1M)\n",
+ "pieces_times_1M = benchmark_query_set(pieces, db.molecules_1M)\n",
"\n",
- "data = {'mean': means, 'median': medians}\n",
+ "results = [frag_times_1M, lead_times_1M, pieces_times_1M]\n",
+ "means_1M = [np.mean(times) for times in results]\n",
+ "medians_1M = [np.median(times) for times in results]\n",
+ "\n",
+ "data = {'mean (1M)': means, 'median (1M)': medians}\n",
"df = pd.DataFrame(data, index =['fragments', 'leads', 'pieces']) \n",
"df"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Discussion\n",
+ "\n",
+ "A median search time of less than 70ms indicates decent performance, certainly fast enough to have interactive search performance on large datasets with single molecules (the traditional UI benchmark for instant feedback being 100ms). "
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
diff --git a/docs/notebooks/Write and Registration Benchmarking.ipynb b/docs/notebooks/Write and Registration Benchmarking.ipynb
new file mode 100644
index 0000000..a849c11
--- /dev/null
+++ b/docs/notebooks/Write and Registration Benchmarking.ipynb
@@ -0,0 +1,459 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Write and Registration Benchmarks\n",
+ "\n",
+ "These benchmarks were originally run on an early 2015 MacBook Pro with a 2.7 GHz dual-core i5 processor and 8GB of memory. All molecules are written into a data directory stored locally via `--dbpath`.\n",
+ "\n",
+ "They make use of molecules found in the data folder. \n",
+ "\n",
+ "Last updated: 8/24/20 by Christopher Zou\n",
+ "\n",
+ "## Setup Work\n",
+ "### Imports"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mongordkit.Database import write, registration\n",
+ "from rdkit import Chem\n",
+ "import rdkit\n",
+ "import numpy as np\n",
+ "import time\n",
+ "import pymongo\n",
+ "import mongomock\n",
+ "import matplotlib\n",
+ "import matplotlib.pyplot as plt"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Database Setup\n",
+ "Here we set up a database called `test` that will hold our molecules. We will construct a collection called `molecules_write_testing` to benchmark the speed of writing to a collection."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Initialize the client that will connect to the database.\n",
+ "client = pymongo.MongoClient()\n",
+ "db = client.test\n",
+ "db.molecules_write_testing.drop()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Defining Some Useful Variables"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "hash_functions = registration.HASH_FUNCTIONS\n",
+ "first_200_mols = '../../data/test_data/first_200.props.sdf'\n",
+ "chembl = '../../../chembl_27.sdf'\n",
+ "\n",
+ "# Disable RDLogger to reduce system output.\n",
+ "rdkit.RDLogger.DisableLog('rdApp.*')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Benchmarking Write"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We want to know the performance of `write.WriteFromSDF`. To find out, let's write the first 1000-10000 (incrementing by 1000 every time) molecules of a ChEMBL dataset using a scheme that contains all 23 available hashes and take median write times:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "testing 1000\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "1000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "1000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "1000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "1000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "1000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "testing 2000\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "2000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "2000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "2000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "2000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "2000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "testing 3000\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "3000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "3000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "3000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "3000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "3000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "testing 4000\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "4000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "4000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "4000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "4000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "4000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "testing 5000\n",
+ "populating mongodb collection with compounds from SDF...\n",
+ "5000 molecules successfully imported\n",
+ "0 duplicates skipped\n",
+ "populating mongodb collection with compounds from SDF...\n"
+ ]
+ }
+ ],
+ "source": [
+ "repetitions = 5\n",
+ "scheme = registration.MolDocScheme()\n",
+ "scheme.add_all_hashes()\n",
+ "times = []\n",
+ "limits = [1000 + (i * 1000) for i in range(11)]\n",
+ "for number in limits:\n",
+ " temp_times = []\n",
+ " print(f'testing {number}')\n",
+ " for i in range(repetitions):\n",
+ " mol_collection = db.molecules_write_testing\n",
+ " start = time.time()\n",
+ " write.WriteFromSDF(mol_collection, chembl, scheme, limit=number)\n",
+ " end = time.time()\n",
+ " duration = end - start\n",
+ " mol_collection.drop()\n",
+ " temp_times.append(duration)\n",
+ " times.append([number, np.mean(temp_times)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[[2000, 14.187205600738526],\n",
+ " [2100, 14.940004205703735],\n",
+ " [2200, 15.965514373779296],\n",
+ " [2300, 16.910987186431885],\n",
+ " [2400, 23.27452983856201],\n",
+ " [2500, 21.29524955749512],\n",
+ " [2600, 23.414347171783447],\n",
+ " [2700, 25.251486158370973],\n",
+ " [2800, 28.250892400741577],\n",
+ " [2900, 28.316519117355348],\n",
+ " [3000, 32.57432060241699]]"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ "