diff --git a/deployment/codeship_runtests.sh b/deployment/codeship_runtests.sh index 2b48142..7fa7dcd 100755 --- a/deployment/codeship_runtests.sh +++ b/deployment/codeship_runtests.sh @@ -5,7 +5,7 @@ set -e VERSION="${TESTENV}.py${PYVERSION}" -PYTESTFLAGS="-n 6 --durations=20 --junit-xml=/opt/reports/junit.${VERSION}.xml --timeout=1800" +PYTESTFLAGS="-n 5 --durations=20 --junit-xml=/opt/reports/junit.${VERSION}.xml --timeout=1800 --tb=short" if [ "${VERSION}" == "complete.py3" ]; then PYTESTFLAGS="--cov moldesign ${PYTESTFLAGS}" fi diff --git a/deployment/send_test_status.py b/deployment/send_test_status.py index 3ef8eb9..9ad3ab1 100755 --- a/deployment/send_test_status.py +++ b/deployment/send_test_status.py @@ -8,7 +8,15 @@ import github -status = {'0':'success', 'na':'pending'}.get(sys.argv[1], 'failure') +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('exitcode', type=str) +parser.add_argument('msg', type=str) +parser.add_argument('--deployed', action='store_true') +args = parser.parse_args() + +status = {'0':'success', 'na':'pending'}.get(args.exitcode, 'failure') missing_env = [] for key in 'CI_COMMIT_ID TESTENV GITHUB_REPO_TOKEN CI_PROJECT_ID CI_BUILD_ID PYVERSION'.split(): @@ -28,15 +36,22 @@ data = dict(state=status, target_url='https://app.codeship.com/projects/%s/builds/%s' % (projid, buildid), - description=" ".join(sys.argv[2:]).replace("=","").strip(), + description=args.msg.replace("=","").strip(), context='%s/py%s' % (testenv, pyversion)) if missing_env: print("Not sending status update b/c of missing env vars: %s" % ','.join(missing_env)) print(data) + sys.exit(0) + + +g = github.Github(ghtoken) +repo = g.get_repo('autodesk/molecular-design-toolkit') +commit = repo.get_commit(sha) + +if args.deployed: + raise NotImplementedError else: - g = github.Github(ghtoken) - repo = g.get_repo('autodesk/molecular-design-toolkit') - commit = repo.get_commit(sha) commit.create_status(**data) + diff --git a/moldesign/_notebooks/Tutorial 1. Making a molecule.ipynb b/moldesign/_notebooks/Tutorial 1. Making a molecule.ipynb index bb4bc4e..0f274a5 100644 --- a/moldesign/_notebooks/Tutorial 1. Making a molecule.ipynb +++ b/moldesign/_notebooks/Tutorial 1. Making a molecule.ipynb @@ -41,24 +41,9 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T22:51:00.051250Z", - "start_time": "2017-06-13T22:50:52.787639Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PDBFixer could not be imported; using remote docker container\n", - "PySCF not installed; using remote docker container\n", - "Reading configuration from /Users/aaronvirshup/.moldesign/moldesign.yml\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "import moldesign as mdt\n", "import moldesign.units as u" @@ -75,46 +60,29 @@ }, { "cell_type": "code", - "execution_count": 2, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T22:51:00.800832Z", - "start_time": "2017-06-13T22:51:00.052906Z" - } - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "dbda377d29794e23924b9f9dddf33519" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "mdt.configure()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Read in the molecule\n", + "\n", + "This notebook comes with a few molecular files ready to go.\n", + "\n", + "Here, we'll use `mdt.read` function to read one of them:" + ] + }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T22:51:28.668756Z", - "start_time": "2017-06-13T22:51:28.634635Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "WARNING: assigned name Z to unnamed chain object @ 0xffffffe0\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "molecule = mdt.read('data/butane.xyz')" ] @@ -128,34 +96,9 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T22:51:30.086590Z", - "start_time": "2017-06-13T22:51:30.065859Z" - } - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "### Molecule: \"data/butane.xyz\" (14 atoms)\n", - "\n", - "**Mass**: 58.08 amu\n", - "\n", - "**Formula**: C4H10\n", - "\n", - "**Charge**: 0.0 elementary_charge" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "molecule" ] @@ -170,24 +113,9 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T22:51:32.689461Z", - "start_time": "2017-06-13T22:51:32.290529Z" - } - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "7a62a7d439ed45b19b99a085af614966" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "molecule.draw()" ] @@ -203,23 +131,9 @@ }, { "cell_type": "code", - "execution_count": 6, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T22:52:05.261475Z", - "start_time": "2017-06-13T22:51:53.979512Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connecting to docker host at... done\n", - "\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "molecule.set_energy_model(mdt.models.RHF, basis='sto-3g')\n", "properties = molecule.calculate()" @@ -227,23 +141,9 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T22:52:19.601172Z", - "start_time": "2017-06-13T22:52:19.586505Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "odict_keys(['positions', 'mulliken', 'dipole_moment', 'potential_energy', 'wfn'])\n", - "Energy: -4230.377472534813 eV\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(properties.keys())\n", "print('Energy: ', properties['potential_energy'])" @@ -251,31 +151,9 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T22:52:24.861897Z", - "start_time": "2017-06-13T22:52:24.729774Z" - } - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "75f66ab1b65541068f5e0c993248f72d" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "molecule.draw_orbitals()" ] @@ -289,76 +167,9 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T22:55:05.764438Z", - "start_time": "2017-06-13T22:52:48.200731Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Starting geometry optimization: SciPy/bfgs with analytical gradients\n", - "\n", - "\n", - "\n", - "\n", - "Step 2/20, ΔE=-3.564e-02 eV, RMS ∇E=7.323e-02, max ∇E=1.953e-01 eV / ang\n", - "\n", - "\n", - "Step 4/20, ΔE=-3.934e-02 eV, RMS ∇E=8.911e-02, max ∇E=1.964e-01 eV / ang\n", - "\n", - "\n", - "Step 6/20, ΔE=-4.062e-02 eV, RMS ∇E=8.891e-02, max ∇E=2.332e-01 eV / ang\n", - "\n", - "\n", - "Step 8/20, ΔE=-4.238e-02 eV, RMS ∇E=9.219e-02, max ∇E=2.997e-01 eV / ang\n", - "\n", - "\n", - "\n", - "Step 10/20, ΔE=-4.424e-02 eV, RMS ∇E=8.321e-02, max ∇E=2.229e-01 eV / ang\n", - "\n", - "\n", - "Step 12/20, ΔE=-4.587e-02 eV, RMS ∇E=5.961e-02, max ∇E=1.663e-01 eV / ang\n" - ] - }, - { - "ename": "KeyboardInterrupt", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNotImplementedError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m~/mycode/molecular-design-toolkit/moldesign/molecules/molecule.py\u001b[0m in \u001b[0;36mminimize\u001b[0;34m(self, assert_converged, **kwargs)\u001b[0m\n\u001b[1;32m 904\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 905\u001b[0;31m \u001b[0mtrajectory\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menergy_model\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mminimize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 906\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mNotImplementedError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/mycode/molecular-design-toolkit/moldesign/models/base.py\u001b[0m in \u001b[0;36mminimize\u001b[0;34m(self, method, **kwargs)\u001b[0m\n\u001b[1;32m 58\u001b[0m \"\"\"\n\u001b[0;32m---> 59\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mNotImplementedError\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 60\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mNotImplementedError\u001b[0m: ", - "\nDuring handling of the above exception, another exception occurred:\n", - "\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[0;32m----> 1\u001b[0;31m \u001b[0mmintraj\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmolecule\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mminimize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m~/mycode/molecular-design-toolkit/moldesign/molecules/molecule.py\u001b[0m in \u001b[0;36mminimize\u001b[0;34m(self, assert_converged, **kwargs)\u001b[0m\n\u001b[1;32m 905\u001b[0m \u001b[0mtrajectory\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menergy_model\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mminimize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 906\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mNotImplementedError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 907\u001b[0;31m \u001b[0mtrajectory\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmdt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mminimize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 908\u001b[0m print('Reduced energy from %s to %s' % (trajectory.potential_energy[0],\n\u001b[1;32m 909\u001b[0m trajectory.potential_energy[-1]))\n", - "\u001b[0;32m~/mycode/molecular-design-toolkit/moldesign/min/base.py\u001b[0m in \u001b[0;36masfn\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 173\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0masfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 174\u001b[0m \u001b[0mobj\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcls\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 175\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mobj\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 176\u001b[0m \u001b[0masfn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__name__\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnewname\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 177\u001b[0m \u001b[0masfn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__doc__\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcls\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__doc__\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/mycode/molecular-design-toolkit/moldesign/min/base.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 117\u001b[0m \u001b[0mmoldesign\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mTrajectory\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mthe\u001b[0m \u001b[0mminimization\u001b[0m \u001b[0mtrajectory\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 118\u001b[0m \"\"\"\n\u001b[0;32m--> 119\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\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 120\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtraj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_frames\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtraj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mframes\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[0mminimization_step\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcurrent_step\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 121\u001b[0m self.traj.new_frame(minimization_step=self.current_step,\n", - "\u001b[0;32m~/mycode/molecular-design-toolkit/moldesign/min/smart.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 56\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mabs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mforces\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmax\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m<=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgd_threshold\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 57\u001b[0m \u001b[0mspmin\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_make_quadratic_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 58\u001b[0;31m \u001b[0mspmin\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\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 59\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtraj\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mspmin\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtraj\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 60\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcurrent_step\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mspmin\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcurrent_step\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/mycode/molecular-design-toolkit/moldesign/min/scipy.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 66\u001b[0m \u001b[0mcallback\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcallback\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 67\u001b[0m \u001b[0moptions\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0moptions\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 68\u001b[0;31m constraints=self._make_constraints())\n\u001b[0m\u001b[1;32m 69\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 70\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtraj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minfo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/.pyenv/versions/miniconda3-4.1.11/lib/python3.5/site-packages/scipy/optimize/_minimize.py\u001b[0m in \u001b[0;36mminimize\u001b[0;34m(fun, x0, args, method, jac, hess, hessp, bounds, constraints, tol, callback, options)\u001b[0m\n\u001b[1;32m 442\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0m_minimize_cg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfun\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mjac\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcallback\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0moptions\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 443\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mmeth\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'bfgs'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 444\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_minimize_bfgs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfun\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mjac\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcallback\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0moptions\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 445\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mmeth\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'newton-cg'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 446\u001b[0m return _minimize_newtoncg(fun, x0, args, jac, hess, hessp, callback,\n", - "\u001b[0;32m~/.pyenv/versions/miniconda3-4.1.11/lib/python3.5/site-packages/scipy/optimize/optimize.py\u001b[0m in \u001b[0;36m_minimize_bfgs\u001b[0;34m(fun, x0, args, jac, callback, gtol, norm, eps, maxiter, disp, return_all, **unknown_options)\u001b[0m\n\u001b[1;32m 932\u001b[0m \u001b[0malpha_k\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mold_fval\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mold_old_fval\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgfkp1\u001b[0m \u001b[0;34m=\u001b[0m\u001b[0;31m \u001b[0m\u001b[0;31m\\\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 933\u001b[0m _line_search_wolfe12(f, myfprime, xk, pk, gfk,\n\u001b[0;32m--> 934\u001b[0;31m old_fval, old_old_fval, amin=1e-100, amax=1e100)\n\u001b[0m\u001b[1;32m 935\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0m_LineSearchError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 936\u001b[0m \u001b[0;31m# Line search failed to find a better solution.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/.pyenv/versions/miniconda3-4.1.11/lib/python3.5/site-packages/scipy/optimize/optimize.py\u001b[0m in \u001b[0;36m_line_search_wolfe12\u001b[0;34m(f, fprime, xk, pk, gfk, old_fval, old_old_fval, **kwargs)\u001b[0m\n\u001b[1;32m 763\u001b[0m ret = line_search_wolfe1(f, fprime, xk, pk, gfk,\n\u001b[1;32m 764\u001b[0m \u001b[0mold_fval\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mold_old_fval\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 765\u001b[0;31m **kwargs)\n\u001b[0m\u001b[1;32m 766\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 767\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mret\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/.pyenv/versions/miniconda3-4.1.11/lib/python3.5/site-packages/scipy/optimize/linesearch.py\u001b[0m in \u001b[0;36mline_search_wolfe1\u001b[0;34m(f, fprime, xk, pk, gfk, old_fval, old_old_fval, args, c1, c2, amax, amin, xtol)\u001b[0m\n\u001b[1;32m 99\u001b[0m stp, fval, old_fval = scalar_search_wolfe1(\n\u001b[1;32m 100\u001b[0m \u001b[0mphi\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mderphi\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mold_fval\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mold_old_fval\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mderphi0\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 101\u001b[0;31m c1=c1, c2=c2, amax=amax, amin=amin, xtol=xtol)\n\u001b[0m\u001b[1;32m 102\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 103\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mstp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfval\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mold_fval\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgval\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/.pyenv/versions/miniconda3-4.1.11/lib/python3.5/site-packages/scipy/optimize/linesearch.py\u001b[0m in \u001b[0;36mscalar_search_wolfe1\u001b[0;34m(phi, derphi, phi0, old_phi0, derphi0, c1, c2, amax, amin, xtol)\u001b[0m\n\u001b[1;32m 172\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34mb'FG'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 173\u001b[0m \u001b[0malpha1\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mstp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 174\u001b[0;31m \u001b[0mphi1\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mphi\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstp\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 175\u001b[0m \u001b[0mderphi1\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mderphi\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstp\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 176\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/.pyenv/versions/miniconda3-4.1.11/lib/python3.5/site-packages/scipy/optimize/linesearch.py\u001b[0m in \u001b[0;36mphi\u001b[0;34m(s)\u001b[0m\n\u001b[1;32m 85\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mphi\u001b[0m\u001b[0;34m(\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[1;32m 86\u001b[0m \u001b[0mfc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 87\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mxk\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mpk\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 88\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 89\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mderphi\u001b[0m\u001b[0;34m(\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~/.pyenv/versions/miniconda3-4.1.11/lib/python3.5/site-packages/scipy/optimize/optimize.py\u001b[0m in \u001b[0;36mfunction_wrapper\u001b[0;34m(*wrapper_args)\u001b[0m\n\u001b[1;32m 290\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfunction_wrapper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mwrapper_args\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 291\u001b[0m \u001b[0mncalls\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 292\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mwrapper_args\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0margs\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 293\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 294\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mncalls\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfunction_wrapper\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/mycode/molecular-design-toolkit/moldesign/min/base.py\u001b[0m in \u001b[0;36mobjective\u001b[0;34m(self, vector)\u001b[0m\n\u001b[1;32m 84\u001b[0m \"\"\" Callback function to calculate the objective function\n\u001b[1;32m 85\u001b[0m \"\"\"\n\u001b[0;32m---> 86\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_sync_positions\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvector\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 87\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 88\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmol\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcalculate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrequests\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrequest_list\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/mycode/molecular-design-toolkit/moldesign/min/base.py\u001b[0m in \u001b[0;36m_sync_positions\u001b[0;34m(self, vector)\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0mc\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mvector\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreshape\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmol\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_atoms\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_strip_units\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 70\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmol\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpositions\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mc\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mu\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdefault\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlength\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 71\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 72\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmol\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpositions\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mc\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/.pyenv/versions/miniconda3-4.1.11/lib/python3.5/site-packages/pint/unit.py\u001b[0m in \u001b[0;36m__mul__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 156\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_REGISTRY\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mQuantity\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_units\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 157\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 158\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_REGISTRY\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mQuantity\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_units\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 159\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 160\u001b[0m \u001b[0m__rmul__\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m__mul__\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/.pyenv/versions/miniconda3-4.1.11/lib/python3.5/site-packages/pint/quantity.py\u001b[0m in \u001b[0;36m__new__\u001b[0;34m(cls, value, units)\u001b[0m\n\u001b[1;32m 79\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0munits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mUnitsContainer\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mUnitDefinition\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 80\u001b[0m \u001b[0minst\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mobject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__new__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 81\u001b[0;31m \u001b[0minst\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_magnitude\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_to_magnitude\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minst\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mforce_ndarray\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 82\u001b[0m \u001b[0minst\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_units\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0munits\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 83\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0munits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstring_types\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: " - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "mintraj = molecule.minimize()" ] @@ -366,13 +177,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T22:55:05.765575Z", - "start_time": "2017-06-13T22:52:48.758Z" - }, - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "mintraj.draw_orbitals()" @@ -388,13 +193,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2016-10-04T13:07:32.961839", - "start_time": "2016-10-04T13:07:32.944565" - }, - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "molecule.write('my_first_molecule.xyz')" @@ -403,13 +202,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2016-10-04T13:08:02.884927", - "start_time": "2016-10-04T13:08:02.771624" - }, - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "mintraj.write('my_first_minimization.P.gz')" @@ -426,13 +219,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2016-10-04T13:08:28.722958", - "start_time": "2016-10-04T13:08:28.355748" - }, - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "mdt.widgets.GeometryBuilder(molecule)" @@ -441,13 +228,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2016-10-04T13:12:19.842912", - "start_time": "2016-10-04T13:12:19.526464" - }, - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "molecule.calculate_potential_energy()" @@ -471,69 +252,8 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" - }, - "widgets": { - "state": { - "334b8ec582894a36a33fa4be313ec47d": { - "views": [ - { - "cell_index": 15 - } - ] - }, - "604fabc98d544980bd89e7d636df34c9": { - "views": [ - { - "cell_index": 17 - } - ] - }, - "7f6ae2966d60485e8250636bcbc725f2": { - "views": [ - { - "cell_index": 18 - } - ] - }, - "99d21866d28e4ca38be668702860a767": { - "views": [ - { - "cell_index": 5 - } - ] - }, - "9f57824b674b4dd7aa2029fbe753a008": { - "views": [ - { - "cell_index": 23 - } - ] - }, - "ca42bb8124f14d3fafa56350b169126b": { - "views": [ - { - "cell_index": 13 - } - ] - }, - "dcaae883c6214541bd4299ed6acbcfb6": { - "views": [ - { - "cell_index": 24 - } - ] - }, - "de1d119df77a4e0793270cbba9799789": { - "views": [ - { - "cell_index": 11 - } - ] - } - }, - "version": "1.2.0" } }, "nbformat": 4, "nbformat_minor": 1 -} +} \ No newline at end of file diff --git a/moldesign/_notebooks/Tutorial 2. Biochemical basics.ipynb b/moldesign/_notebooks/Tutorial 2. Biochemical basics.ipynb index 8338d77..e3309dd 100644 --- a/moldesign/_notebooks/Tutorial 2. Biochemical basics.ipynb +++ b/moldesign/_notebooks/Tutorial 2. Biochemical basics.ipynb @@ -5,7 +5,7 @@ "metadata": {}, "source": [ "\n", - "About      Forum      Issues      Tutorials      Documentation\n", + "About      Forum      Issues      Tutorials      Documentation\n", "\n", "![Molecular Design Toolkit](img/Top.png)\n", "
\n", @@ -18,24 +18,9 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:28:40.807225Z", - "start_time": "2017-06-13T23:28:33.490754Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PDBFixer could not be imported; using remote docker container\n", - "PySCF not installed; using remote docker container\n", - "Reading configuration from /Users/aaronvirshup/.moldesign/moldesign.yml\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "import moldesign as mdt\n", "from moldesign import units as u\n", @@ -71,31 +56,9 @@ }, { "cell_type": "code", - "execution_count": 2, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:28:41.845273Z", - "start_time": "2017-06-13T23:28:40.812863Z" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Widget Javascript not detected. It may not be installed or enabled properly.\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d183e61f8d16492a83f7a48cac90c046" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "one_yu8 = mdt.from_pdb('1YU8')\n", "one_yu8.draw()" @@ -103,46 +66,9 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:28:41.876811Z", - "start_time": "2017-06-13T23:28:41.846624Z" - } - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "### Molecule: \"1YU8\" (600 atoms)\n", - "\n", - "HIGH-RESOLUTION CRYSTAL STRUCTURES OF VILLIN HEADPIECE AND MUTANTS WITH REDUCED F-ACTIN BINDING ACTIVITY.\n", - "\n", - "**Mass**: 8150.00 amu\n", - "\n", - "**Formula**: C325N85O189S1\n", - "\n", - "**Charge**: 0.0 elementary_charge\n", - "\n", - "### Residues\n", - "\n", - "| chain | protein | dna | rna | unknown | water | solvent |\n", - "|-|-|-|-|-|-|-|-|\n", - "| X | 64 | | | | 92 | ||\n", - "\n", - "### Biopolymer chains\n", - "\n", - "**X**: PTKLETFPLDVLVNTAAEDLPRGVDPSAKENHLSDEDFKAVFGMTRSAFANLPLWKQQNLKKEKGLF" - ], - "text/plain": [ - "<1YU8 (Molecule), 600 atoms>" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "one_yu8" ] @@ -156,14 +82,8 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:28:52.606104Z", - "start_time": "2017-06-13T23:28:52.272130Z" - }, - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "headpiece = mdt.Molecule([res for res in one_yu8.residues if res.type == 'protein'])" @@ -171,24 +91,9 @@ }, { "cell_type": "code", - "execution_count": 7, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:29:42.137749Z", - "start_time": "2017-06-13T23:29:35.782660Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connecting to docker host at... done\n", - "\n", - "Forcefield assignment: Success\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "ff = mdt.forcefields.DefaultAmber()\n", "protein = ff.create_prepped_molecule(headpiece)" @@ -203,14 +108,8 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:29:47.198661Z", - "start_time": "2017-06-13T23:29:47.184144Z" - }, - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "protein.set_energy_model(mdt.models.OpenMMPotential, implicit_solvent='obc')" @@ -218,47 +117,18 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:29:52.005417Z", - "start_time": "2017-06-13T23:29:47.993471Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Created OpenMM kernel (Platform: OpenCL)\n", - "Reduced energy from -99.2682075377658 eV to -110.70757228527638 eV\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "mintraj = protein.minimize()" ] }, { "cell_type": "code", - "execution_count": 10, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:29:53.689080Z", - "start_time": "2017-06-13T23:29:53.020229Z" - } - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "555167d7b2ec4215b781f4bc21eb8ff9" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "mintraj.draw(display=False)" ] @@ -272,14 +142,8 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:30:09.795801Z", - "start_time": "2017-06-13T23:30:09.781744Z" - }, - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "protein.set_integrator(mdt.integrators.OpenMMLangevin,\n", @@ -290,87 +154,18 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:31:01.506024Z", - "start_time": "2017-06-13T23:30:10.531366Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Created OpenMM kernel (Platform: OpenCL)\n", - "time / fs potential / eV kinetic / eV T / kelvin\n", - "0.0 -110.707571364464 0.0 0.0\n", - "1998.0000000000014 -81.75115507028912 29.124189005182803 267.0661446907951\n", - "3995.9999999997813 -76.46193567913342 32.901638023671026 301.70500608378194\n", - "5993.999999999562 -76.46793927932444 33.63816605760306 308.45890067098964\n", - "7991.999999999341 -77.50788540420635 34.74905400282512 318.64564134421965\n", - "9990.000000000004 -75.63011217387931 33.46861699668099 306.9041513171676\n", - "11988.000000000671 -77.7501635867188 32.45585512702218 297.6172177661084\n", - "13986.000000001339 -77.46865308426227 33.0564295626339 303.12443031338756\n", - "15984.000000002006 -77.00760933732988 32.79459844886624 300.72346299026026\n", - "17982.00000000091 -77.1127820400727 33.841874478280005 310.32688822393277\n", - "19979.999999999804 -78.10567548222586 34.14605519269467 313.1161975047357\n", - "21977.999999998694 -75.42900480872319 34.439133630453824 315.80369992584724\n", - "23975.999999997588 -76.55756649660458 32.76507632927039 300.4527478585133\n", - "25973.99999999648 -78.79508664629053 33.19174148225794 304.36522821846467\n", - "27971.999999995372 -76.29059917302453 32.95141897637996 302.1614928589723\n", - "29969.999999994267 -76.914069326234 34.15895876083294 313.23452204228175\n", - "31967.999999993157 -76.67546010839622 36.2282115621162 332.2093806359753\n", - "33965.99999999554 -78.94194051942326 32.71065006239817 299.9536639780697\n", - "35963.999999997985 -78.00472029899505 35.14767971334471 322.30100258583747\n", - "37962.00000000042 -78.78203729997847 36.46221727919934 334.3551916209912\n", - "39960.00000000287 -76.1389904637887 35.40092653414118 324.6232527289497\n", - "41958.00000000531 -78.02616884257824 33.37581138001133 306.05313231556687\n", - "43956.00000000775 -76.40665284224501 34.63853785062389 317.6322183545121\n", - "45954.00000001019 -78.98412829434963 33.957248858222336 311.384860716894\n", - "47952.00000001263 -77.47871406832775 34.168218365769384 313.3194317711431\n", - "49950.000000015076 -76.89508087800016 34.58906016801684 317.17851253836903\n", - "49998.00000001513 -77.07563364825529 33.5694583033563 307.8288568598545\n", - "49998.00000001513 -77.07563364825529 33.5694583033563 307.8288568598545\n", - "Done - integrated \"Molecule: Unnamed molecule from OpenMM copy\" from 0.0 fs to 49998.000000015134 fs\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "traj = protein.run(50*u.ps)" ] }, { "cell_type": "code", - "execution_count": 13, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:31:09.441051Z", - "start_time": "2017-06-13T23:31:01.507595Z" - } - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d5c2f89df5234b14b0f9093898d0247f" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "IOPub data rate exceeded.\n", - "The notebook server will temporarily stop sending output\n", - "to the client in order to avoid crashing it.\n", - "To change this limit, set the config variable\n", - "`--NotebookApp.iopub_data_rate_limit`.\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "traj.draw()" ] @@ -385,35 +180,9 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:31:26.217029Z", - "start_time": "2017-06-13T23:31:25.959304Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\nmwAAAABJRU5ErkJggg==\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "plot(traj.time, traj.kinetic_energy)\n", "xlabel('time / %s' % u.default.time); ylabel('energy / %s' % u.default.energy)" @@ -421,83 +190,18 @@ }, { "cell_type": "code", - "execution_count": 21, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:32:10.805142Z", - "start_time": "2017-06-13T23:32:10.755491Z" - } - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "### Molecule: \"Unnamed molecule from OpenMM copy\" (1013 atoms)\n", - "\n", - "**Mass**: 7187.10 amu\n", - "\n", - "**Formula**: C325H505N85O97S1\n", - "\n", - "**Charge**: 0.0 elementary_charge\n", - "\n", - "**Potential model**: \n", - "\n", - "**Integrator**: \n", - "\n", - "### Residues\n", - "\n", - "| chain | protein | dna | rna | unknown | water | solvent |\n", - "|-|-|-|-|-|-|-|-|\n", - "| X | 64 | | | | | ||\n", - "\n", - "### Biopolymer chains\n", - "\n", - "**X**: LETFPLDVLVNTAAEDLPRGVDPSAKENHLSDEDFKAVFGMTRSAFANLPLWKQQNLKKEKGLF" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "protein" ] }, { "cell_type": "code", - "execution_count": 25, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:33:29.192069Z", - "start_time": "2017-06-13T23:33:29.007736Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYgAAAEWCAYAAAB8LwAVAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvXlYZHeV//86QEEVW9EL0EDvW9Ld2XezaoxbjEYdjXHX\ncYzROMbR8ec66izx6ziOGsfRqOM+TjQa48R1EjVm0WydpNML6aShd+iGApodCqg6vz/uvVDQBVRB\nFdRyXs/D07du3Xvrc+miTp3tfURVMQzDMIypFCz2AgzDMIzMxAyEYRiGERczEIZhGEZczEAYhmEY\ncTEDYRiGYcTFDIRhGIYRFzMQRloRke+JyL+425eJyLMxzx0UkatS/TpzPP9PIvI3qVhLNrzuTIhI\nv4isX+x1GIuPGQhjwVDVB1X1lMVehzFBPAOlquWqun+x1mRkDmYgjJxHRIoWew2GkY2YgTBSioic\nLSJPikifiPwE8Mc893wROTrllLNEZKeI9IjIT0Qk9vhrRGSHiHSLyF9E5IxkXkdEPiIix4HvisgS\nEfmViIRE5IS7vTLBe7pARB5213FMRL4qIsUxz6uI3Cgi+9xj/lNExH2uUET+XUQ6ROSAiLzPPT6u\n0RKRvxaRZ9w1/p+IrJnmuN+KyPum7HtaRF4jDl8SkXYR6RWRXSJyWpxr3AJcBnzVDSt9NeZ+Nrrb\n3xORr7mv1y8ifxaRFSLyZXeNe0Xk7Jhr1ovIne7v+YCIvD+R37GRmZiBMFKG+6H5C+CHwFLgp8Bf\nzXLadcBLgXXAGcDb3WudDXwHeDewDPgGcLeIlCT4Oivc59YAN+C817/rPl4NDAFfTfDWIsDfAcuB\n5wEvBN475ZhrgPPde7gOeIm7/13Ay4CzgHOAV033IiJyLfBx4DVANfAgcPs0h98OvCHm3K3uvf0a\neDFwObAZCLrr6Zx6AVX9hPsa73PDSu+beozLdcAnce4/DDwMPOk+/hnwRXcNBcAvgaeBBpzf0wdE\n5CVxrmlkAWYgjFRyEeADvqyqo6r6M+DxWc75iqq2qmoXzofLWe7+G4BvqOqjqhpR1e/jfDhdlODr\nRIFPq2pYVYdUtVNV71TVQVXtA24BrkjkplT1CVV9RFXHVPUgjrGaeu7nVLVbVQ8D98Xcx3XArap6\nVFVPAJ+b4aVuBP6fqj6jqmPAZ3E8rHhexF1TnnsT8HNVDQOjQAVwKiDu9Y4lcq/TcJf7Oxh2X3dY\nVX+gqhHgJ4DnQZwPVKvqP6nqiJvH+BZw/Txe21hEzEAYqaQeaNHJCpCHZjnneMz2IFDubq8BPuSG\nbLpFpBtY5b5GIq8Tcj/QABCRUhH5hogcEpFe4AGgSkQKZ7spEdnshqSOu+d+FufbcyL3UQ8ciXku\ndnsqa4BbY+63CxCcb+OTcI3cr5n48H0D8CP3uT/ieEf/CbSLyDdFpHK2+5yBtpjtoTiPY//P6qf8\nn30cqJ3HaxuLiBkII5UcAxq8+LvL6jle6whwi6pWxfyUqurtCb7OVJniDwGnABeqaiVOCAacD+DZ\n+DqwF9jknvvxBM/DXWtsrmPVDMceAd495Z4DqvqXaY6/HXiDiDwPJwdzn/eEqn5FVc8FtuKEmj48\nzTVSKed8BDgwZf0Vqnp1Cl/DWEDMQBip5GFgDHi/iPhE5DXABXO81reAG0XkQjfpWiYiLxeRijm+\nTgXOt91uEVkKfDqJtVQAvUC/iJwKvCeJc+8AbhaRBhGpAj4yw7G3AR8TkW0AIhIUkdfNcPxvcL61\n/xPwE1WNuued7/7efMAAMIwTcotHG5CqnofHgD63OCDgJuhPE5HzU3R9Y4ExA2GkDFUdwUmwvh0n\nPPJ64OdzvNZ2nATvV4ETQJN73bm+zpeBANABPAL8Lonl/D3wRqAPx3D9JIlzvwXcA+wEnsL5UB/D\nSXxPQlXvAv4V+LEbytqNk+COi5tv+DlwFfA/MU9Vuq97Aif01gn82zSXuRV4rVuR9JUk7iveeiI4\nyfqzgAM4v+v/wkmUG1mI2MAgw1g4RORlwG2qGrd81TAyCfMgDCONuKGWq0WkSEQacEJbdy32ugwj\nEcyDMIw0IiKlwP04JadDOJVHN6tq76IuzDASwAyEYRiGEZe0hZhExC8ij7nt/3tE5B/d/etE5FER\naRJHWqHY3V/iPm5yn1+brrUZhmEYs5M2D8KtUS9T1X633O4h4Gbggzgdnz8WkduAp1X16yLyXuAM\nVb1RRK4HXq2qr5/pNZYvX65r165Ny/oNwzBylSeeeKJDVatnOy5tKpdul2u/+9Dn/ihwJU7JIMD3\ngc/gNCJd626Do+/yVRERncGCrV27lu3bt6d87YZhGLmMiMymcACkuYrJbZTZAbQD9wLNQLerMwNw\nlAkZgQZcGQL3+R4ckbap17xBRLaLyPZQKJTO5RuGYeQ1aTUQrsjaWThSAxfgVHLM95rfVNXzVPW8\n6upZPSTDMAxjjixIH4SqduPoxDwPRyDNC22tBFrc7RZcnRr3+SBxJIoNwzCMhSGdVUzVrvYMIhIA\nXgQ8g2MoXuse9jbgf93tu93HuM//cab8g2EYhpFe0jmKsQ74viunXADcoaq/EpFGHK2Zf8HRpvm2\ne/y3gR+KSBOOvo5pyBuGYSwi6axi2snEIJHY/fuJo7zpavfPpFxpGIZhLCCmxWQYhmHExQyEYRgZ\nQySq/OTxw4xFphtfYSwkZiAMw8gYHtnfyUfu3MVfmq2AMRMwA2EYRsbQ1uuMET8xOLLIKzHADIRh\nGBlEqC8MQPfg6CKvxAAzEIZhZBDtZiAyCjMQhmFkDOMexJCFmDIBMxCGYWQMnoHoMQ8iIzADYRhG\nxtDe5ySpu4fMQGQCZiAMw8gYJpLUFmLKBMxAGIaREQyPRugddkbFmAeRGZiBMAwjI+jod7yH4qIC\ny0FkCGYgDMPICLwS1w3V5XQPjWJq/4uPGQjDMDICL/+wqaacSFTpD4/NcoaRbsxAGIaREXgGYnNt\nOWDNcpmAGQjDMDKC9r4wIrC+2jEQPZaoXnTMQBiGkRGE+sIsKytmWVkxYB5EJmAGwjCMjCDUF6a6\nwk9VqWsgTG5j0TEDYRhGRhDqG6a6ooSqUh9gHsR0DI9G+Mb9zYyMpX+okhkIwzAyglBfmOryEoIB\nx0BYDiI+f3q2nf/3273sONKd9tcyA2EYxqKjqoT6w9RUluD3FeL3FZiBmIbm0AAAowswltUMhGEY\ni0734CijEaW6vASAqkCx6TFNQ3OoHzADYRhGnhByZTaqK1wDUeqzHMQ0eB7EWCT9neZmIAzDWHS8\nJjnPQAQDPhPsi4Oqst/1IMai5kEYhpEHeHMgamI8CBPsO5lQf5g+V/F21DwIwzDygakeRFWg2Pog\n4rDfDS8BRKJmIAzDyANCfWH8vgLKS4oACFoOIi5eghosSW0YRp7Q3hempsKPiABODiI8FmV4NLLI\nK8ssYj2IMfMgDMPIBxyZjZLxx9ZNHZ/mUD+1lc7vacw8CMMw8gGvi9qjKmB6TPHYHxpgc20FYElq\nwzDyBK+L2sM8iJMZHo1w5MQgp7gGwpLUhmHkPOGxCN2Do5M8CE+PyQzEBIc6B1GFzStcDyKb+yBE\nZJWI3CcijSKyR0RudvefKSIPi8guEfmliFTGnPMxEWkSkWdF5CXpWlsu0DUwwvGe4cVehmHMm45+\nJ4wULwfRYyGmcbwKplNdA5HtndRjwIdUdStwEXCTiGwF/gv4qKqeDtwFfBjAfe56YBvwUuBrIlKY\nxvVlNZ/8xS5u+OH2xV6GYcybqT0QwMRMCPMgxvE6qDfWOBP3sjpJrarHVPVJd7sPeAZoADYDD7iH\n3Qv8lbt9LfBjVQ2r6gGgCbggXevLdna39NLc3o9q+r9FGEY6ae/1uqj94/vKigspKhCT24ihOTRA\nfdBPaXERvkJhNFdyECKyFjgbeBTYg2MMAF4HrHK3G4AjMacddfcZU/CSVQMjEXrdtnvDyFamCvUB\niIgJ9k1hf6ifDa738KYL13Dmyqq0v2baDYSIlAN3Ah9Q1V7gr4H3isgTQAWQVJBRRG4Qke0isj0U\nCqV+wVnAgY4BPMfhWM/Q4i7GMOZJqC+MCCwrL560PxjwWQ7CRVVpDg2wfnkZAJ955TZeetqKtL9u\nWg2EiPhwjMOPVPXnAKq6V1VfrKrnArcDze7hLUx4EwAr3X2TUNVvqup5qnpedXV1OpefsTS1T7Tb\nt3abgTCym/a+MEtLi/EVTv44qiotNg/CJdQXpj88Nu5BLBTprGIS4NvAM6r6xZj9Ne6/BcAngdvc\np+4GrheREhFZB2wCHkvX+rKZyQbCKpmM7GZqF7VHVcBCTB5NboJ6/fIcMRDAJcBbgCtFZIf7czXw\nBhF5DtgLtALfBVDVPcAdQCPwO+AmVTUhljg0hfpZuSRAUYGYB2FkPdMZiGCpz8aOungaTBtqyhb0\ndYvSdWFVfQiQaZ6+dZpzbgFuSdeacoXm9n4211agaiEmI/sJ9YVZX33yB5+NHZ2gOdRPaXEhKyr9\nsx+cQqyTOsuIRJX9HQNsrCmnoSpAqzXLGVmMqk4fYir1MTASYWQs/fX+mc7+0ADrq8vG1W4XCjMQ\nWcaRrkFGxqJsrC6nrspvHoSR1fQOjTESiU6S2fCY6Ka2MFNzqH/B8w9gBiLr8BLUG2rKqa8K0NY7\nvCCiXYaRDsZHjcYJnXh6TPle6jo8GqGle4gN1WYgjFloimm3rw/6GY0oHW6jkWFkG+MyG3E9CJPb\ngIm+p3h5mnRjBiLLaGrvp7qihGDAR31VALBEtZG9xOui9qgyRVcgpoLJPAhjNpra+9novlHqgp6B\nsES1kZ3EE+rzGJ8Jkec5CE/Fdd1y8yCMGVBVmtv7x9UcG8yDMLKc9r4wJUUFVPpPrrj3psrle5J6\nf6ifhqoAgeKFF7c2A5FFhPrC9IXHxg1EZaCIsuJCWk2PychSvBLXeOWbFf4iRKAnz3shmt0S18XA\nDEQW4VUweQZCRKirCpgHYWQt0/VAABQUCMGAL69DTKrqqLguQv4BzEBkFU1TBoYA1FcFOGbNckaW\n0t43TM00BgJMj6mtN8zASIQN5kEYs9HU3k95SdGkP6j6oDXLGdnLTB4EQLC0OK89CG+KnHkQxqw0\ntTsDQ2LjtfVVATr6RxgeNV1DI7sYGYtyYnCU6vLp9YWqAr68zkF4FUzrzUAYsxFb4upRF3T+uI5b\nmMnIMrwGz5rKGUJMpfmdg2gODVBWXEjtDL+jdGIGIkvoHR6lvS88Kf8AMaWuVslkZBkzdVF75HsO\nojnUz/rq8gUX6fMwA5ElTK1g8pjopjYPwsguZmqS8wgGfPQOj+at3tj+0MCiJajBDETWMJ2BWOGG\nmCxRbWQb7YkYiNJiVKFvOP+8iKERR6RvsfIPYAYia2hu76e4sIBVSwKT9vt9hSwvL+aYhZiMLMPz\nIJbPEmKC/NRjOtCxeBpMHmYgsoSm9n7WLS+jqPDk/7K6YIAWCzEZWUaof5glpT6Ki6b/GMpnPaaJ\nCiYLMRmz0BTqPym85FFf5eeYhZiMLKO9d+YeCIgxEHlY6ro/NIDI4oj0eZiByAKGRyMc6RpkwzQG\noi7oyG2o5mciz8hOQv1haipmnrEczGPBvmZXpM/vW3iRPg8zEFnAwc4BonpygtqjoSrAwEiE3uGx\nBV6ZYcyd2bqoIdaDyD8Dsb9j8TSYPMxAZAHjFUzTvFlscJCRbagq7QkYiGCeJqkdkb7FU3H1MAOR\nBTS19yMyfbKqrspx062SycgWeofHGBmLzijUB+ArLKC8pIjuPJtLfbx3mMGRSOZ7ECKyQURK3O3n\ni8j7RaQq/UszPJra+1m5ZPpYpNdNbZVMRraQSJOcRzDgoyfPPIjmdqfENRs8iDuBiIhsBL4JrAL+\nJ62rMiYRT4MpluXlJRQViIWYjKwhEZkNj3zUY9rfMXNYeaFIxEBEVXUMeDXwH6r6YaAuvcsyPCJR\nZX/HwLQJaoDCAmFF0Epdjeyhvc/xdmcS6vOoKvXlXZlrsyvtn4iHlU4SMRCjIvIG4G3Ar9x9vvQt\nyYjl6IlBRsaiMxoIcBLVpsdkZAsTHsTMZa7gzKbOPw/C0WBaLJE+j0QMxDuA5wG3qOoBEVkH/DC9\nyzI8ptNgmkp90G+KrkbWEOoPU1xYQGWgaNZjg6X5mIPoX1QNJo9ZDYSqNgIfAZ50Hx9Q1X9N98IM\nh4kS14oZj6uvCnC8ZzhvVS+N7CLkdlEn8g25yp1LnS+NoIMjY7T2DC+qiqtHIlVMrwB2AL9zH58l\nInene2GGQ1N7P8vLSwiWzhzVq6sKMBbV8SEshpHJhPpn74HwqCr1EYkq/eH8aATdH/IqmLLAgwA+\nA1wAdAOo6g5gfRrXZMTgaDDN/k2iwe2FaLFEtZEFJNJF7VHlym3kS7Pc/gxQcfVIKEmtqj1T9kXT\nsRhjMqrqlLjOkn8AR48JrJt6ocmXsEeqSaSL2sPznvNFj6nZbYxds6x0sZeSkIHYIyJvBApFZJOI\n/AfwlzSvy8D5ltU3PJZQLbQnt3HMKpkWhP7wGB+8Ywdn//O9eZdAnS+jkShdAyOzdlF75NtMiP0d\nA6xaUrqoIn0eiRiIvwW2AWGcBrke4AOznSQiq0TkPhFpFJE9InKzu/8sEXlERHaIyHYRucDdLyLy\nFRFpEpGdInLO3G8rN2gKeRVMMyeoASr9RZSXFFmIaQF4+kg3L//Kg/z8yRa6B0c50Dmw2EvKKjr7\nnZ6GxHMQbogpT+Q2nAqmxU9QQ2JVTIOq+gngClU9X1U/qaqJfE0dAz6kqluBi4CbRGQr8HngH1X1\nLOBT7mOAlwGb3J8bgK8nfzu5RXOCJa4AIkJd0G96TGkkGlW+/qdm/urrf2F0LMqnX7EVgLZe89qS\nwWuSS6SLGvJL0TUaVQ50DGRE/gESq2K6WEQagb3u4zNF5Guznaeqx1TVK43tA54BGgAFKt3DgkCr\nu30t8AN1eASoEpG87thucrspaxPoNgVrlksnbb3DvOU7j/Kvv9vLi7fV8tubL+eaM+rHnzMSx2uS\nq6mcvUkOJhRd8yEHcax3mKHRSMZ4ELN3qcCXgJcAdwOo6tMicnkyLyIia4GzgUdxwlP/JyJfwDFQ\nF7uHNQBHYk476u47NuVaN+B4GKxevTqZZWQdTaH+pLop66v87GmdWk9gzJc/PNPGh3+2k8GRMT73\nmtN5/fmrEBGiUaWoQMxAJEkyQn3gzF33+wrywkDsd8PKWeNBAKjqkSm7Iom+gIiU4wj+fUBVe4H3\nAH+nqquAvwO+nei13LV8U1XPU9Xzqqurkzk162hq7592ilw86oMBOvpHGB5N+L/HmIHh0QifuXsP\n7/z+dmor/fzqby/l+gtWjxvsggKhpqKE4z3We5IM7a6BWF5enPA5VYHivNBj8sLK2eRBHBGRiwEV\nER9wM064aFbc4+8EfqSqP3d3v829BsBPgf9yt1twlGI9Vrr78pLe4VHaesMJ5R886txKpuM9w6xd\nxDm2ucC+tj7+9van2Hu8j3dcspaPvPTUuFUlNZX+8Zi6kRihvjDBgI+SosSrdBzBvjzwIDoGqPAX\nJZyfSTeJeBA3AjfhhHtagLPcxzMiztesbwPPqOoXY55qBa5wt68E9rnbdwNvdauZLgJ6VHVSeCmf\naJ5lilw86t1mOeuFmDuqyo8ePcQrvvoQ7X1hvvP28/j0K7ZNW3K4otJvIaYkCfWFEy5x9QgG8kPy\nuznkaDAttkifx4wehIgUAm9R1TfN4dqXAG8BdonIDnffx4F3AbeKSBEwjJtPAH4DXA00AYM4IoF5\nS6IifbFMDA4yAzEXeoZG+cjPdvK7Pce5dONyvnjdmbMmUmsrS/hLc8cCrTA3SEZmw6Oq1MfBjsE0\nrShz2B8a4Hkbli32MsaZ0UCoasRtkvtSshdW1YeA6czguXGOVxLwTPKFplA/xYUFrF6aeDfliqA3\netS+0c6FL937HPc+08bHXnYq77psPQUFs3+Lqw366R0eY2gkQqB48RubsoH2vmHOWb0kqXMcye/u\nNK0otbR0D3Goc4CLNyxP6ryB8BjHeoYzJkENiYWYHhKRr4rIZSJyjveT9pXlOc3t/axdXkpRYeJj\nw0uKClleXmIhpjmgqvz+mTZecEo1775iQ0LGAaC2wjHKFmZKDFWdU4gpm3IQn/3NM7zpvx7lD8+0\nJXXegXENpszJHyby6XMWTif1PwH/7v58IZ2LyjTaeoc5+5/u4bpvPMwPHzlE5wIopiaqwTSV+io/\nreZBJE1zaICjJ4a44pSapM6rrTQDkQz94TGGR6NJh5gqAz7CY9GMr9CLRJWH9nWgCu+//SmePd6X\n8LnNIa+CKYs8CFV9QZyfKxdicZnC9oMnODE4SsuJIf7hF7u54LN/4K3feYyfbj+Sltrs4dEIh7sG\n5zSPtj4YMA9iDvzp2XYAnr85udLpFUHng+64GYiEaE+yB8IjW7qpd7f00DM0ysdediqlJUX8zQ8e\np2sgsfLc5tAABRki0ucxa5mriHwwzu4e4AlX+jvn2dPaQ1GB8Me/v4L9oQF++XQrv9zZyod/tpNP\n3LWbK06p5pVn1vPCLTWUFidSOTwzBzsHiCpJ9UB41FX5eXBfCFXNmEqIbOD+50JsrClnVRI5H5jo\nBm7vtV6IRBjvoq5IrIvaY1zye2hkPNeWiTy4LwTAa89dyQXrlvL6bz7Ce/77CX74zgspLpr5+3hz\nqJ9VS0uTKv9NN4l8mp3n/vzSfXwNsBO4UUR+qqqfn/bMHKHxWC8ba8opKSpkS10lW+oq+fBLTmHH\nkW5++fQxfrWzlXsb2wj4Crlqay2vOKOOK06pnvN/dHO7E4ucS4ipoSrAwEiE3qGxWYcMGQ6DI2M8\nur+Ltz5vTdLnVpQUUVpcaCGmBEm2i9ojWzyIB/Z1cFpDJcvKS1hWXsLn/+oMPvCTHXz67t189tWn\nz/ilbX8oczSYPBIxECuBc1S1H0BEPg38GrgceIIJsb2cZU9rL5dtmlyRICKcvXoJZ69ewidevoXH\nD3bxy6db+c2uY/zy6VYq/EX8++vO5MXbViT9ek2uHvxc3iz1MaWuZiAS4+HmTkYiUZ6fZP4BnPdB\nbaXfQkwJMh5iSrIRLJgFkt/94TGePHSCd10+MU/tVWc38FxbH1/7UzOn1Fbw9kvWxT3XEenr55IM\nKnGFxJLUNThS3x6jQK2qDk3Zn5O09w0T6guzrT447TGFBcJF65dxy6tP57FPXMX33nE+DVUBPn7X\nbnqHk39DN4X6WbkkMCc9+LrxUlfLQyTKn54NUVpcyPnrkiu99KitLLEQU4KE+sL4CmXcI0iUqvGh\nQZkrt/FIcydjUeWyjZO/TP79i0/hRVtr+edfPzMegppKa88Qw6PROYWV00kiBuJHwKMi8mnXe/gz\n8D8iUgY0pnV1GUBjay8AW+sqZznSwVdYwPNPqeHzrz2DzoEwX/n9vtlPmkJTe/+cXU2vWc4S1Ymh\nqtz3bDsXb1g255CgeRCJE+oLU11eknR+bHwmRAZ7EA81deD3FXDu2slfNAoKhC+9/iw21ZRz04+e\nHBfki6XZm0OdYRI5iVQx/TNOt3O3+3Ojqv6Tqg7MscM6q2g85hqI+sQMhMcZK6t4/Xmr+N5fDtLU\nnnipWySq7A/1z6mCCWB5eQm+QrFS1wSZa3lrLLWu3IaNH52d9r7hpPMPAGXFhRQVSEbLbTywL8RF\n6+N/0SgvKeJbbz2PosIC/ub720+aQjiu4pqFHgSAH+hV1VuBQyISP5CWg+xp7WXlksB4DDQZPvyS\nUygtLuQzdzcm/OHRcmKI8Fh0TglqcL6trAj6zYNIkLmWt8ZSW+knPBbNCznq+RJKYhZ1LCKS0c1y\nR08Msj80wGWbpn8frVpaym1vPpcjJwZ53+1PMhaJjj/XHOqn0l/EsrLEFW4XgkQGBn0a+AjwMXeX\nD/jvdC4qk3imtZdtSXoPHsvKS/jgizbzUFMH/7fneELnNIUcb2OuBgKgLhiw2dQJcv9zITZUlyVd\n3hqLN9CpzfIQs9LRH6Y6yRJXj2DAl7E5iIf2OXpcl2+aWV7jgnVL+ZdXncaD+zq45TcTotj7QwNs\nqMkckT6PRDyIVwOvBAYAVLUVmH1Icg4wEB7jQOcAW+umT1DPxpsvWsOpKyr45189w9DI7F2gcxHp\nm0pDVcAE+xLAK2+dS/VSLCusmzohxiJROgdG5uRBgJOHyFQP4sF9HdRWliT0d/v681fzzkvX8d0/\nH+T2xw4Drorr8swKL0FiBmLEFdJTADc5nRfsPd6LKnP2IACKCgv4zCu30dI9xG33N896fFN7P8vL\ni8eTcnOhvsqJiUeiFhOfiYny1vkNnvLkNixRPTOdAyOoJt8D4VEVyMwQUySq/Lm5g8s2VSfsAXz8\n6i1csbmaf/jFbv64t4223jAbajLvozURA3GHiHwDZ0b0u4DfA99K77Iygz1uBdO2hrkbCICL1i/j\nmjPquO3+Zo50zSxZPJ8KJo+6YICxqI43JRnx+dOzIQK+Qi5Yt3Re1/E+8NrNQMzIRBf13AxEsNSX\nkXme3S09dA+OntQrNROFBcJ/vPFs1iwr5cYfPgmQnR6Eqn4B+BnOZLhTgE+p6n+ke2GZwJ6WXpaU\n+sZDCPPhEy/fQoEI//Lr6SuDVXXOIn2x2FyI2VFV/vRcO5dsnHt5q4ffV8iSUp95ELPgTd6buweR\nmWNHvd6GSzcmJ+9d6ffx7bedPy4TvzFLPQhU9V5V/bCq/r2q3pvuRWUKjcd62VYfTEniqC4Y4H1X\nbuT/9rRN2ywT6g/TOzw2bwNRV2XNcrOxv2OAI13zK2+NxSl1NY9tJkJz7KL2qCr1MTASYWQsOvvB\nC0isvEayrF1exrfeeh5/dc5K1i7LIgMhIn0i0jvdz0IucjEYjUR59nhf0v0PM/E3l61jzbJSPnP3\nnrhv8lQkqGFCbsNKXafnT886Rno+5a2x1Nro0VmZqw6Tx0Q3deaEmTx5jUs3zv19dMG6pfz7dWcm\nNftloZh2RapaoaqVwK3AR3FmUq/EKXn98sIsb/FoDvUzEonOK0E9lZKiQj51zVaaQwN8/y8H47zm\n3EX6YqljRcG2AAAgAElEQVT0+ygvKaLVSl2n5U/Pts+7vDWW2soSMxCzEOoLU+kvmpOEDEzoMWVS\nqeuj+x15jdnKW7OVREzWK1X1a6rap6q9qvp14Np0L2yx2dOSnMRGorxwSy1XnlrDrX/Yd1JSs7m9\nn/KSopTkPOqrrFluOlJV3hrLiko/ob6wVY7NQPscm+Q8MlFu48F98eU1coVEDMSAiLxJRApFpEBE\n3oTbE5HLNB7rxe8rSMt0p09ds5WRsSif++3eSfudCqaylOQ86qsCNpt6GlJV3hpLTaWfqDqNYEZ8\n5tpF7VGVgYquM8lr5AKJGIg3AtcBbe7P69x9Oc2e1h5OWVFJYYKziZNh7fIy/uaydfz8qRaeONQ1\nvj8VJa4edTZZblpSVd4aSzpGj0ajyj/8Yve4YGS2E+oPJz0oKJbxmRAZkoPw5DWSrV7KJhIpcz2o\nqteq6nJVrVbVV6nqwQVY26KhqjTOQ2IjEW56wUZWVPr51P/uIRJV+oZHOd47nDKxroYqP50DIxk/\nw3eh8cpb56PeGg8vLHg8hV5bS/cQP3zkEHc/3Zqyay4Wqkp773w9CC/ElBk5iHF5jRQVOmQimZc2\nzwCOnhiid3gs5fmHWMpKivj4y7ewp7WXHz9+OGUJao+6oFPJZGGmyXjlrakML0GMHlMKmxMPdjrv\nCa+6LZsZGIkwNBqZl4Go8BchAr0Z4kE82OTIa2zKMAXWVGIGIg6exHc6PQiAV5xRx4XrlvKF/3uW\n7QedUFOqDISVusZnvLw1hQlqcIQZCwuEthQa5EOdTtd9vPkB2cZ8u6jBUSoOBnwZEWKKRJU/NyUn\nr5GNzNQH8TzJ5TufgT2tvRQInLoivQZCRPjMK7fRMzTKF+99Dl+hsCZFZZf1brOcGYjJpLq81aOw\nQKguT22p6yHXgzjUNZhxzWHJ4lXszceDgMzRY5qLvEY2MpMH8VbgCRH5sYi8XUSSH66cpTS29rK+\nuny8BT6dbKmr5C0XrWFwJMLaZWUpa5ZZEfQMhIWYPIZGIjx6ILXlrbHUBv0pDjE5HkQkquPGIlsJ\n9c+vSc4jWFqcER6Ep4ZwSQ4nqGHmRrn3qOo5wGeAJcD3RORhEfmsiFwuIrlZ1wU0tvakPbwUywdf\ndArLyopT2rVdUlRIdUWJyW3E8PD+DkbGUlveGkttRUlKQ0yHOwfHZ4w3Z3mYaSLENL8en6qAj54M\nSFI/sK+DbfWVLJ+jbEi2kEgV015V/ZKqvhS4EngIp9T10XQvbjE4MTBCa89wWhPUUwmW+vjV+y/l\nH1+5LaXXrQ/6TbAvhvv2pr68NZbaSj9tfakxEKrKoa4BXnCq4+14RQzZSntfmKICGe9lmCtVpYuf\ng+gPj/HU4RMzTo/LFYqSOVhVh4DfuD85yUSCeu5DguaCV3WUSuqrAuzLgQqYVJCu8tZYVgT9dA+O\nMjwambOchEd7X5jh0Shb6iqpC/ppzvL/x1BfmOXlJRTMs68oE3IQj+7vZDSSu/IasVgV0xT2tPYA\npDTcs1h4zXKJzsPOZdJV3hpLzfhciPnnIQ52OB7D2mWlbKwppykHQkw1lfMPxwQDPnqHRxdV0iTX\n5TViMQMxhcbWXuqCfpZm2PDwuVBf5WdwJJJR6peLRbrKW2PxCgNSMRfCK3Fds7SMDdXlNLf3Z7Wh\nb+8Lz1nmO5ZgaTGq0De8eO/pB/aFuHBd7sprxJKQgRCRNSJylbsdEJGcnUm9p7V3QfMP6WSiF8Iq\nmf70bDvr01DeGksq5TYOdQ1QVCDUV/nZUF3GwEgkq+dNzFeHyWOx9ZhauofYHxrI+fJWj1kNhDtm\n9GfAN9xdK4FfJHDeKhG5T0QaRWSPiNzs7v+JiOxwfw6KyI6Ycz4mIk0i8qyIvGRutzR3hkcjNIf6\nF7SCKZ1Ys5zDeHnr5vR5D5BaA3Gwc5CVSwIUFRaMy69kQkd19+BI0hVVkajSNZAiA7HIekwPueWt\nuSyvEUsiSeqbgAtwq5ZUdZ+IJPKXNgZ8SFWfdD2OJ0TkXlV9vXeAiPw70ONubwWuB7YB9cDvRWSz\nqi6YmNDe431ENTfyDzDRLJfvpa7pLm/1cGYdFKTEQBzuHGSNO2Fsoyvg2Bzq59JF/OZ6z57jfPyu\nXfQMjfL9v76AizcktpbO/jBRnV8Xtce4gVikUtcH9uW+vEYsiYSYwqo6/r8hIkXArMFQVT2mqk+6\n233AMzhDh7zrCI5K7O3urmuBH6tqWFUPAE04hmnB8FQzF7qCKV0sLyvBVyi05HmIKR3qrfEQkZSM\nHlVVDnYOsHaZEw6rriihoqRo0XoheoZG+eAdO7jhh09QU+FnzbIy3v3DJ3iurS+h89vnOUkulqAr\n2LcYebV8kdeIJREDcb+IfBwIiMiLgJ8Cv0zmRURkLXA2k3snLgPaVHWf+7gBOBLz/FFiDErMtW4Q\nke0isj0Uij/bea7sae2hwl/EyiWpLzldDAoKhLpgIK89CFXlT8+GuHjDsnmXniZCbaV/3knqE4Oj\n9A2Psdr1IESE9TXli2IgHtwX4qVffoD/3dHK+6/cyC9uuoTv//UFBHyFvP07jyXkLaWqixpiPYiF\nNxD5Iq8RSyIG4qNACNgFvBunB+KTib6AiJQDdwIfUNVYYfs3MOE9JIyqflNVz1PV86qrUxsyaDzm\nJKhz6dtBXTC/J8sd6BjgcNdg2sNLHrWV/pMmBSaLJ6vheRDghJkWMgcxODLGP/xiN2/59mOUFhdy\n53su5oMvPoXiogIaqgJ85+3n0zM0yju++zj94bEZr5WqLmqYGDu6GAYiX+Q1Ykmkkzqqqt9S1dep\n6mvd7YTq7UTEh2McfqSqP4/ZXwS8BvhJzOEtwKqYxyvdfQtCJKrsPdaXM+Elj4aqQF5XMd23AOWt\nsayoLOF47/C8SlLHS1xjDMSGmjLaesMLUt65/WAXL7v1Qf770UO889J1/Pr9l3HWqqpJx5zWEORr\nbz6XZ9v6eO+PnmQ0Mr2YoGcgUiFL4SssoLykiO5FmEv9YJ7Ia8Qyk5rrLhHZOd3PbBd2cwzfBp5R\n1S9OefoqYK+qHo3ZdzdwvYiUiMg6YBPwWPK3NDcOdAwwNBrJmQS1R12VE/LI11nJC1HeGkttpZ/h\n0Si9wzN/q56JQ52DiMDKJTEGwk1U70+j5MbwaIT/99tneN03HiYSVW5/10X8wzVbpw3NXbG5ms++\n+jQeeC7EJ+/aPa1RDPWFqSgpSpn4ZTDgo2eBPYj+8BhP5om8RiwzVTFdM89rXwK8BdgVU8r6cVX9\nDU610qTwkqruEZE7gEacCqibFrKCyeugzpUSV4/6qgCRqNLeN5wWOY9MxitvffOFaxbsNWvcUtf2\n3uHxcEiyHOocoD4YmPTBvDGm1PXMKd/mU8Hulh4+eMcOnmvr5w0XrOYTL99CecnsRY6vP381LSeG\n+Mofm2hYEuD9L9x00jGhvjDVKeii9lgMPSZPXiOf8g8wg4FQ1UPzubCqPgTEDear6tun2X8LcMt8\nXneuNB7rpbiwIGUDezKF2Ga5fDMQC1XeGsv46NHeYTbVzq2f9GDnAKuneDyrl5ZSVCApT1SPRqJ8\n7b5m/uOP+1haVsx333E+L0gyHPd3L9rM0e4hvnjvc9RXBXjtuSsnPd/eN5ySLmqPqlLfgpe5jstr\nrMl9eY1YEmmU6xOR3ik/R0TkLhFZvxCLXAgaW3vZvKIcX4rmMWQK9cH8a5brGx7l3sY2vnH//gUp\nb41lfPToPEpdD3cNsnb5ZAPhKyxgzbLSlBuIT961my/9/jlefkYd9/zd5UkbB3CqrD73mjO4dONy\nPnrnzvFZzR6p6qL2qAos/EwIT15jISrhMolEGuW+jFNy+j84HsH1wAbgSeA7wPPTtbiFQlVpbO3l\nhVsWJpG5kORDs9xYJMrTR7t5cF8HD+3r4Kkj3USiSsBXyHufv2FB/6jn203dHx6jo3+E1UvLTnpu\nQ3V5SmW/VZU/7G3jmjPquPX6s+d1reKiAr725nO47raHufG/n+CnNz6PLa5kTaoNRLB0YXMQnrzG\nGy9YvWCvmSkkYiBeqapnxjz+pojsUNWPuP0RWU9bb5jOgZGcq2ACqPD7qCgpyqlKJqeRbJCH9oV4\nYF8HjzR30hceQwTOaAhy4xXruXRjNeesqVpwQTW/r5BgwDdnAxGvxNVjY005f9zbzmgkmhJP9+iJ\nITr6R7gwRR5Wpd/Hd99xPq/+z7/wju8+zl03XUyl38fASCQlJa4eVe5calVdkJL0fJPXiCURAzEo\nItfh6DEBvBbw3v05URqTSxLf8aivCuREiOnh5k7+d0cLD+7rGB+EtHJJgGvOrOPSjdVcvGEZSzJA\nhXdFpZ/jc5wsN1HiGt+DGIsqh7sGx6ua5sOOI90AnLUqdXH1umCA777jfF5328O847uP82+vdb5b\npjTEVOojElX6w2NU+Oc3gCgRHnguv+Q1YknEQLwJuBX4Go5BeAR4s4gEgPelcW0LhiexsSVHVFyn\nUlflpzXLQ0w/efwwH/35LspLirh4wzJufP4GLtu4nDXLSjOusbGmsmTOs6kPuh7E6jgehCfa19ze\nnzIDUVJUwKl1qRVn3lJXydfffA7v+O7jvOdHTwApNhCu3Eb34GjaDcTIWJQHngvxstNXZNz7bCGY\n1UCo6n7gFdM8/VBql7M47GntZe2y0oTK+rKR+qoAO4/2LPYy5sx3HjrAP/2qkSs2V3Pbm89NWT19\nuqit9NPU3jH7gXE43DnI8vKSuO/FDdWOV9EU6ufF81qhw44j3ZzWEExLYcZlm6r53F+dwd//9Gkg\nNUJ9HkFXbqNnaHRSZ206eGS/E7588dYVaX6lzGTWT0QRqQbeBayNPV5V/zp9y1pYGo/1cnpD7uUf\nPBqqAnQNjKRkFOZCoqr8531NfOGe53jpthXc+oazsmJIy4pKP+19YSJRpTDJEZsHOwcmdVDHUuH3\nUVtZQnP7/BPVo5Eou1t6ePNF6esRee25K2nrHeYb9zenVN9sIWdC3NN4nICvcFFVdBeTRL4y/y/w\nIPB7YMEa1xaK3uFRDncN8vrz0/1dZPGocyedtXYPsT4FoYmFQFX51989y233N/Oasxv4/GvPoChL\nSpBrK0uIRJXOgXDSydnDnYNctGHZtM87lUzzL3Xde6yP8Fj0JAmNVHPTCzZy4xUbkjaUM1FV6oaY\n0iy3EY0q9za2ccXm6qz6YpVKEjEQpar6kbSvZJF4xs0/5GqCGiaa5Y71DGeFgYhGlc/8cg8/ePgQ\nb7pwNf987WnzHna/kIyXuvYkZyCGRyO09gyzJk6Jq8eG6nJ+saNl3hU8Tx05AZB2AwGk1DjAwim6\n7mzpoa03zIu31ab1dTKZRL6S/UpErk77ShaJPeMzIHLYQLjNci1ZUMk0Fony/925kx88fIgbLl/P\nv7wqu4wDzL0X4kiXU8E0tUkulo015fQNj40L4M2VHYe7WV5enJXS9p6ESbpnQtzbeJzCAuHKU3Ov\nPypREjEQN+MYiSG3i7pPRHpnPStLaDzWy/LykpTWaWcatcESROBYhvdCjIxFufnHO/jZE0f5u6s2\n87GXnZqVlSMrghNyG8kwU4mrh1e91DTPMNOOI92ctaoqK3+/fl8hfl9B2g3EPXvauGDt0vGQVj6S\niNx3haoWqGpAVSvdxznzdXtPa29Oew8AJUWFLC8vyeheiOHRCO/+4XZ+vesYn3z5Fm6+alNWfngB\nLCsrpkBIei6EV+K6Zgbl2Q01jvGYT0d1z+Ao+zsGFiS8lC6qAsVp1WM60DHAvvb+vA4vQWI5CERk\nCY789vjXbFV9IF2LWihGxqI0tfctqJjbYlFfFcjYXoj+8Bjv+v52HjnQyWdffTpvvDC7JQ2KCgtY\nXl6StB7T4a5BKv1F4zH2eKyo9FNWXEjzPIYH7Tia+ga5hcYR7EufB3Fv43EAXrTVDMSMiMjf4ISZ\nVgI7gIuAh4Er07u09PNcWx+jEc15DwKgocrPs8cTmyG8kPQMjvL27z3GzqM9fOm6s3jV2SdNmc1K\nVgSTHz16sHOQNcvKZvScRIQN8xw/uuNwtyNLsip7S7uDgfRKft+zp42tdZWTZnLkI4nmIM4HDqnq\nC3BmS3endVULROMxt4IpRzuoY6kPOpPlomkcHPTYgS4e3Bdi+8Eudrf00NTeT0v3EJ39YQZHxk56\n7Y7+MG/41iPsaenlP994Ts4YB3DGayabpD40Qw9ELBuqy+fnQRw5wcbqcioXQKYiXVSlUbAv1Bfm\nicMn8j68BImFmIZVdVhEEJESVd0rIqekfWULQGNrL6XFhaydISmYK2yqLWdoNMKhrkHWLU/9/e46\n2sN133h41uOKiwoIuEnGoZEII5Eo33rbeVyRY0JoK4IlPHGoK+HjRyNRWk4M8Yoz6mc9dkN1GXc9\n1cJAeIyyJLv/VZUdR7q5akt2f/g5kt/p+Z76h2faUCVvu6djSeTddVREqoBfAPeKyAlgXsOEMoXG\n1l621FVmXRnlXDjN7RTf1dKTFgPx5GGnrv5bbz2PkqIChkcjDI1GCI9GGXK3vX3DIxGGR6OMRqO8\n6cLVnLtm4eY1LBS1FX5ODI4SHosk1P3d2j3EWFTjajBNxRtqtT80wOkrkwsTHe4a5MTgKGetzt4E\nNaQ3B3FvYxsrlwTYkmKNqmwkES2mV7ubnxGR+4Ag8Lu0rmoBiEaVxmO9vOac3AlrzMTm2gqKiwrY\n3dLDK8+c/Vtqsuxq6WF5eTFXbanJ2uqjVFIb9EaPhhOah+2VuCbizXqlrs2h/qQNxISCa3YbiMqA\nj/BYNOXyMQPhMR5s6uBNF6629zGJ5SDGUdX7VfVuVV3YeX9p4HDXIP3hsbzIP4AzkWzLigp2t6RH\ntG/X0R5ObwjaH5VLss1y3hyIRHIQa5aVUTjH8aNPHe4m4CvklDmOQ80U0tVN/cBzIUbGohZecskO\ncZs04CWoc3FI0HSc1hBkd0sPqqlNVA+NRNjX3pfTgofJkuzo0YOdg/h9BQmpnhYXFbBmaSlNc0hU\n7zjSzekNwazRtZqOccnvFOsx3dvYRlWpj/PXZm8JcCrJ7nfJPNjT2kNRgbCpNvO1iVLFaQ1BeofH\nOOxKOqSKxmM9RBVOX5ndYYtUsqIyuW7qQ52DrJ2lxDWW9XMQ7QuPRWhs7c36/AOkx4MYjUT5w952\nrjy1JusNaKrI299CY2svG2vK80ql8fSYRHUq2eXOmjAPYoJgwEdxUUHC3dSHOgdYnUCuwmNDTRkH\nOwYZi0QTPueZY32MRNKv4LoQBNMg+f34gS56hkYtvBRD3hqIPa29Oa3gGo/NtRUUFxak3EDsbOmh\nuqJkPKxiOA1tKyoTa5aLumNE1yZRXbahupyRSJSjJxLvjt9xeOEUXNNN1fjQoNSFmO5pbKOkqIDL\nN+fn7Id45KWBCPWFae8L502C2qO4qIBT0pCo3t1iCep41FaWJJSkbusbJjwWTcqD8Epdk8lD7DjS\nTU1Fyfh8kGxmfCZEijwIVWf2w2Wbqiktzs3JknMhLw1EPiaoPZxEdW/KEtWDI2M0tfdbeCkOtZX+\nhJLUBzsSL3H12LB8otQ1UbJZwXUqZcWFFBVIyuQ29rT20tI9xIvzXHtpKnlpICr9RbzyzPq88yDA\nyRP0DI1ypCs1wn2Nrb1OgtoMxEk4BmJ4VmN8uCvxElePYKmP5eUlCRuIEwMjHOwczIkENTghvFQ2\ny93T2EaBwAu35O/sh3jkpS919uolnL06P8vYYhPViXTtzsZOL0GdZMNWPlBbWcLgSIS+8NiMukcH\nOwfxFUrSoZ+NNWUJh5gmFFxzw0CAk6hOVQ7inj3HOW/NUpaVWx4tlrz0IPKZzSvK8RVKyhLVu1t6\nqKkoGW8MMybwfiezVTId7hxk1ZLSpEsrnfnUAwmFC8cVXHOoFLmqtDglHsSRrkH2Hu8zcb44mIHI\nM0qKClOaqN7Z0sMZ5j3EZaKbeuY8xMHOgTl5cxuqy+kZGqVzYPZv0TuOdLO5poLyJMX9MpmqQGpC\nTPc0tgE2+yEeZiDykNMbguxunX9H9UB4jOZQ/7gQoDGZ8Wa5nuk9CFUdb5JLlg0JVjKpKk8f7c6p\n8BI4eZhUjB29Z89xTqmtmHHUa75iBiIPOa0hSPfgaFI19PHY09qLKuZBTEONJ7fRN72B6BoYoT88\nllSJq4dX6jpbovpg5yDdOaDgOpVUjB09MTDC4we7LLw0DWYg8pDT3PLe+YaZvDyGeRDxKS0uosJf\nRNsMHsRBT8V1efIGoq7ST8BXSHP7zPOpdxzJnQa5WKpKfQyMRBgZS7ybfCp/2NtO1GY/TIsZiDzk\nlBUVFBXMP1G962g3Kyr91FRYgno6VszSCzFR4pp8eKOgQFhfXTarB7HjcDelxYVsznIF16lMdFPP\nPcx0z57j1AX9nNaQfyXviZA2AyEiq0TkPhFpFJE9InJzzHN/KyJ73f2fj9n/MRFpEpFnReQl6Vpb\nvuP3OR8W8zYQLT3mPcxC7SxyGwc7BhGBlUsCc7r+xpryWXMQO450c8bKIIU5NhjL02Oaa6nr0EiE\nB/aFeNHW2pxoHkwH6fQgxoAPqepW4CLgJhHZKiIvAK4FzlTVbcAXAERkK3A9sA14KfA1EckfJb0F\n5vR5Sn/3h8fY3zFg+YdZqK30z1jmeqhzgPpgIKGpc/HYUF1OS/cQQyORuM8Pj0ZoPNbLWatyr+9n\nvnIbDzV1MDxqsx9mIm0GQlWPqeqT7nYf8AzQALwH+Jyqht3n2t1TrgV+rKphVT0ANAEXpGt9+c5p\nK4OcGBylpXtuieo9LT2odVDPSm1lCe19YaLR+Ib4UNdgUh3UU/Gmy+3viO9FNB7rZTSiOZd/AKfM\nFeZuIO7Zc5wKfxEXrs+9kbepYkFyECKyFjgbeBTYDFwmIo+KyP0icr57WANwJOa0o+6+qde6QUS2\ni8j2UCiU3oXnMN4H+1wT1ZagTowVQT9jUZ22V+FQ5+C8yis31DjnThdmeuqw00F9do5VMEHMTIg5\n5CAiUR2f/eCz2Q/TkvbfjIiUA3cCH1DVXhx5j6U4YacPA3dIEgFAVf2mqp6nqudVV1enZc35wKnz\nTFTvaumhLuinOoEJaPmMl8CPp+raOzxK18AIa+fhQaxdVkaBQHMofiXTjiPd1AX9OdnpPj5Vbg6l\nrk8cOkHXwIiFl2YhrQZCRHw4xuFHqvpzd/dR4Ofq8BgQBZYDLcCqmNNXuvuMNOD3FbKptoJdLb1z\nOt+bQW3MzMTo0ZMNxGG3xHU+ISa/r5BVS0unrWTaceREToaXACr8RYhA7xw8iHv2HKe4sIArTrEv\nmTORziomAb4NPKOqX4x56hfAC9xjNgPFQAdwN3C9iJSIyDpgE/BYutZnwOkNlXNKVPcNj7K/Y8AM\nRAKsCE4vt3Gwc+4lrrFsqC6nOU6IqbM/zJGuoZw1EAUFQjDgSzrEpKrc09jGxRuX5ZT0SDpIpwdx\nCfAW4EoR2eH+XA18B1gvIruBHwNvc72JPcAdQCPwO+AmVY1fmmGkhNMbgnQNjHBshkaueOx2vQ5T\ncJ2d5eUliMSfTX3I9SDm0kUdy8aacvZ3DBCZkgjfcST3FFynMhc9pmfb+jjcNWjhpQRIm/lU1YeA\n6XILb57mnFuAW9K1JmMyp8VIf9dXJV6H7yW2zYOYHV9hAcvLS+KWuh7qHKC6ooSyeX6L3VBdxshY\nlJYTQ5NE/3Yc6aawQHLakAdLi5P2IO7d04YIXLXVZj/MhqXv85gtdZUUFkjSlUw7W3poqAqYdn6C\nTDd61BHpm/9MDq/UdWoeYseRbjbXVuT0CM2qgI+eJJLUHf1h7trRwtmrqkwBIAHMQOQxfl8hm2rK\nk65k2t3SY9IESbCi0s/xODmIQ52DrF46fwVRz0DElrpGozo+YjSXqSpNPAdx3952XvrlBzh6Yoh3\nX7EhzSvLDcxA5DmnJdlR3Ts8yoGOgZwaPJNuauJ0Uw+PRjjeO5wSD2JJWTHLyooneRD7OwboGx7j\n7Fw3EAnkIIZGInzqf3fzju89zvLyEu5+3yW8ZJvlHxLBDESec3pDkI7+kRn1gmLZbQ1ySVNb4adz\nYITw2ETNxeEuN0GdAgMB3nS5CQMxnqDOwQa5WIKlxfQOj56UoPfY3dLDK776ED94+BDvvHQdv7jp\nEk5dYd5vopiByHPGE9VHEwszecdZgjpxVgSdXE2obyLMdLDDKXGdy6CgeGyoKZvULLfjyAnKS4rG\nw0+5SjDgQ9UpvY4lElVuu7+ZV3/tz/QNj/LDd17AP1yzFb/P5N2SwQxEnrO1rpICSVxyY5eboF5a\nVpzmleUONZUnd1N7HsR8muRi2VBdTtfACF2upEeuKrhOJZ4eU0v3EG/6r0f43G/3ctWWWn538+Vc\ntska4uZC7pY3GAkRKC5kU03i0t+7bAZ10qyIM5v6YOcAwYBvXJF0vmyImS5XWhxk77E+brh8fUqu\nnclM1WO6++lWPnHXLqJR5d9eewavPXelSXnPAzMQBqc1BLn/uRCqOuMfU8/gKIc6B7nuvFXTHmOc\nTG0cDyJVJa4eG71S1/Z+BBiLKmevzj2J76l4BuJI1yDf/8tB7nqqhbNXV/Hl159lM6ZTgBkIg9Mb\nKrnzyaO09YbHpSHisbvV8TLMg0iOJaU+igsLJhUCHOoc5MwUVhjVVwUoKSqgqb2f/vAYkNsd1B5B\nV7Dvg3fsIKrwgas28b4XbKTIFFpTghkIY7zTdldLz4wGYlziu94MRDKICDWVJbS7IabRSJSW7iGu\nPas+Za9RWCCsdyuZjvUO01AVyAul3eryEnyFQn1VgC+9/izOyQOvaSExA2GwJSZR/aKttdMet+to\nD6uWBlhiCeqkqa30c9zVvGo5MUQkqvPWYJrKhuoydh7tIRLVnC9v9QiW+vjN+y+jviowb8kS42TM\nDzMoLXbKIWerZNrVYhLfc2VFpZ+2PsdAeCqua5enNka+obqcw12DtHQP5XyDXCybaivMOKQJMxAG\n4I6olp4AAArgSURBVPQ1zFTJ1D04wuGuQU5vyJ8PnlRSU1lCm+tBHErBHIh4bKyZ6HnIh/yDkX7M\nQBiAU8nU3heOqzoKMRLf5kHMiRWVfgZGIvSHxzjUOUhpcSHVKRY79JriigrEOt2NlGAGwgAmJ6rj\nsbPFkW4wAzE3YktdD3UOsHppacrr89dXlyECp9ZVWMewkRLMQBiA01EtMr2B2N3Sw+qlpQTdunMj\nOcYNRM8wh7oGUx5eAked99zVS3jhqdMXGhhGMlhmxwCgrGTmRPXOoz0prdvPN7zZ1K09wxzuHOSF\np6ZnWM3P3nNxWq5r5CfmQRjjTJeoPjEwwtETQxZemgeeB7HzaDcjkah1+RpZgRkIY5zTGoK09YZp\n75ucqPaMxhlmIOZMWUkRFSVFPHagC0h9BZNhpAMzEMY4nocwNczkGYhtZiDmRU1lCXuP9wFmIIzs\nwAyEMc62ejdRfbR30v5dR3tYu6yUYMAS1PPBkzHxFQp1wcAir8YwZscMhDFOWUkR65eXjYvyeexq\n6bG6+hRQW+EYiFVLS3N+ToORG5iBMCbhzaj26BoYoaV7yBRcU4A3OGhNijWYDCNdmIEwJnF6Q5Bj\nPcN09DvKo7tsBnXKWOGWuloFk5EtmIEwJjE+o9o1DLuOdk/ab8wdr9Q1lYOCDCOdmIEwJrGtvhKA\n3UddA9HSw7rlZVT6LUE9X7yxoFttnoaRJVgntTGJCr+P9cvLYjyIHs5bu3SRV5UbbK6t4M8fvZKG\nKqtgMrID8yCMk/AS1R39YVp7hq2DOoWYcTCyCTMQxkmc3hCktWeY+58NOY+tgskw8hIzEMZJeAnp\n2x87DEzkJQzDyC/MQBgnsa3BMQjbD51gfXUZFZagNoy8xAyEcRKVfh/r3HnJln8wjPwlbQZCRFaJ\nyH0i0igie0TkZnf/Z0SkRUR2uD9Xx5zzMRFpEpFnReQl6VqbMTtemMkMhGHkL+kscx0DPqSqT4pI\nBfCEiNzrPvclVf1C7MEishW4HtgG1AO/F5HNqhpJ4xqNaTitvpJfPt1qBsIw8pi0GQhVPQYcc7f7\nROQZoGGGU64FfqyqYeCAiDQBFwAPp2uNxvS8+uwGugZGOGfNksVeimEYi8SC5CBEZC1wNvCou+t9\nIrJTRL4jIt4nUANwJOa0o8QxKCJyg4hsF5HtoVAojavOb2oq/Xzs6i34Ci1NZRj5Str/+kWkHLgT\n+ICq9gJfBzYAZ+F4GP+ezPVU9Zuqep6qnlddXZ3y9RqGYRgOaTUQIuLDMQ4/UtWfA6hqm6pGVDUK\nfAsnjATQAqyKOX2lu88wDMNYBNJZxSTAt4FnVPWLMfvrYg57NbDb3b4buF5ESkRkHbAJeCxd6zMM\nwzBmJp1VTJcAbwF2icgOd9/HgTeIyFmAAgeBdwOo6h4RuQNoxKmAuskqmAzDMBaPdFYxPQTEm6v4\nmxnOuQW4JV1rMgzDMBLHSlQMwzCMuJiBMAzDMOJiBsIwDMOIi6jqYq9hzohICDg0x9OXAx0pXE42\nYPecH9g95wfzuec1qjprI1lWG4j5ICLbVfW8xV7HQmL3nB/YPecHC3HPFmIyDMMw4mIGwjAMw4hL\nPhuIby72AhYBu+f8wO45P0j7PedtDsIwDMOYmXz2IAzDMIwZMANhGIZhxCUvDYSIvNSde90kIh9d\n7PUkiztoqV1EdsfsWyoi94rIPvffJe5+EZGvuPe6U0TOiTnnbe7x+0TkbTH7zxWRXe45X3GVeReN\nGeab5/I9+0XkMRF52r3nf3T3rxORR911/kREit39Je7jJvf5tTHXijvrPVP/DkSkUESeEpFfuY9z\n+p5F5KD73tshItvdfZnx3lbVvPoBCoFmYD1QDDwNbF3sdSV5D5cD5wC7Y/Z9Hviou/1R4F/d7auB\n3+IIJ14EPOruXwrsd/9d4m4vcZ97zD1W3HNftsj3Wwec425XAM8BW3P8ngUod7d9ONMYLwLuAK53\n998GvMfdfi9wm7t9PfATd3ur+x4vAda57/3CTP47AD4I/A/wK/dxTt8zjqr18in7MuK9nY8exAVA\nk6ruV9UR4Mc487CzBlV9AOiasvta4Pvu9veBV8Xs/4E6PAJUiTOT4yXAvarapaongHuBl7rPVarq\nI+q8u34Qc61FQVWPqeqT7nYf4M03z+V7VlXtdx/63B8FrgR+5u6fes/e7+JnwAvdb4rjs95V9QDg\nzXrPyL8DEVkJvBz4L/exkOP3PA0Z8d7ORwOR0OzrLKRWVY+528eBWnd7uvudaf/ROPszApk83zyn\n79kNtewA2nH+4JuBblUdcw+JXef4vbnP9wDLSP53sdh8Gfj/gKj7eBm5f88K3CMiT4jIDe6+jHhv\np3NgkLFIqKqKSM7VL8uU+eaxodRcvGd1BmadJSJVwF3AqYu8pLQiItcA7ar6hIg8f7HXs4Bcqqot\nIlID3Csie2OfXMz3dj56ELk6+7rNdSe9sa7t7v7p7nem/Svj7F9UJM58c3L8nj1UtRu4D3geTkjB\n+2IXu87xe3OfDwKdJP+7WEwuAV4pIgdxwj9XAreS2/eMqra4/7bjfBG4gEx5by92gmahf3C8pv04\nySsvUbVtsdc1h/tYy+Qk9b8xOan1eXf75UxOaj2mE0mtAzgJrSXu9lKNn9S6epHvVXBip1+esj+X\n77kaqHK3A8CDwDXAT5mcsH2vu30TkxO2d7jb25icsN2Pk6zN6L8D4PlMJKlz9p6BMqAiZvsvwEsz\n5b296G+ERfpPuRqnEqYZ+MRir2cO678dOAaM4sQU34kTe/0DsA/4fcybQ4D/dO91F3BezHX+GieB\n1wS8I2b/ecBu95yv4nbcL+L9XooTp90J7HB/rs7xez4DeMq9593Ap9z9690/+Cb3g7PE3e93Hze5\nz6+PudYn3Pt6lpgKlkz+O2CygcjZe3bv7Wn3Z4+3pkx5b5vUhmEYhhGXfMxBGIZhGAlgBsIwDMOI\nixkIwzAMIy5mIAzDMIy4mIEwDMMw4mIGwjAAEakSkffGPK4XkZ/NdM48XssnIv9/e/cOGlUURWH4\nX1rYiq2NGC3EoCNiI2lilcpCjE1EhCCk0EYUBMFCEIRUWlgItjaCKILEKoKEWIgIBhTERyF2ChqI\n2GRZnGMY4wl56MRmfTAwc+bO2XeqPQ/u2s8b68OSXkma7EXdiNVKg4goNlPSQQGw/cn20R7VGgCm\nGuujwCnbgz2qG7EqaRARxVWgr2byj0vapjpvQ9JJSfdqLv8HSaclna0zC55K2lKP65M0UUPXnkha\nKjtpiHJF6wJJlyiN41atv1tlHsSLmvu/s4fvPaIpDSKiuAC8td2xfb7xfD9wBDgAXAHmbO8DpoET\n9ZibwBnb+4FzwI0lag0Cj7sXbF8GngEjtf4YcM12h3Il7MfFm0T0WtJcI1Zm0mUWxaykr8CDuv4S\n2FOTZg8Cd7pSZjct3kTSVuCL7bll6k0DF+t8hLu23/yLNxGxGvkGEbEyP7ruz3c9nqd80NpAmVvQ\n6brtauwzBDxarpjt28Bh4DvwUNKhvzr7iDVIg4goZinjTNfE9jfgvaRhWJgdvLdx6B//P7RI2g68\ns30duE8J74tYV2kQEYDtz8CUpBlJ42vcZgQYlfQrmfO3cZaSNgI7bL9uvXiRY8BMnSjXT4k7j1hX\nSXONWCeSBoDjtsf+97lErEQaRERENOUnpoiIaEqDiIiIpjSIiIhoSoOIiIimNIiIiGhKg4iIiKaf\nMUoXC8rztb8AAAAASUVORK5CYII=\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "myres = protein.chains[0].residues['PHE47']\n", "plot(traj.time, traj.dihedral(myres['CG'], myres['CB']).to(u.degrees))\n", @@ -507,25 +211,9 @@ }, { "cell_type": "code", - "execution_count": 27, - "metadata": { - "ExecuteTime": { - "end_time": "2017-06-13T23:33:37.486802Z", - "start_time": "2017-06-13T23:33:37.302233Z" - } - }, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "plot(traj.time, traj.distance(myres['CG'], myres['CB']))\n", "plt.title('bond length vs time')\n", @@ -535,9 +223,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [] } @@ -563,4 +249,4 @@ }, "nbformat": 4, "nbformat_minor": 1 -} +} \ No newline at end of file diff --git a/moldesign/_tests/helpers.py b/moldesign/_tests/helpers.py index 1fe70c1..74aa0c0 100644 --- a/moldesign/_tests/helpers.py +++ b/moldesign/_tests/helpers.py @@ -62,7 +62,6 @@ def native_str_buffer(*args, **kwargs): return io.StringIO(*args, **kwargs) - class ZeroEnergy(mdt.models.base.EnergyModelBase): """ All 0, all the time """ @@ -84,10 +83,46 @@ def _internet(host="8.8.8.8", port=53, timeout=3): """ try: socket.setdefaulttimeout(timeout) - socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) + try: + socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) + except OSError: + return False return True except Exception as ex: print(ex.message) return False INTERNET_ON = _internet() + + +def assert_something_resembling_minimization_happened(p0, e0, traj, mol): + """ Raises assertion error if results do not correspond to a successful optimization + + Args: + p0 (Array[length]): initial position array + e0 (Scalar[energy]): initial energy + traj (moldesign.Trajectory): trajectory created from minimization + mol (moldesign.Molecule): state of molecule AFTER minimization + + Returns: + + """ + import scipy.optimize.optimize + + assert traj.num_frames > 1 + + assert traj.potential_energy[0] == e0 + assert traj.potential_energy[-1] < e0 + assert traj.potential_energy[-1] == mol.potential_energy + + assert (traj.positions[0] == p0).all() + assert (traj.positions[-1] != p0).any() + assert (traj.positions[-1] == mol.positions).all() + + scipyresult = getattr(traj, 'info', None) + if isinstance(scipyresult, scipy.optimize.optimize.OptimizeResult): + np.testing.assert_allclose(scipyresult.x, + mol.positions.defunits_value().flat) + np.testing.assert_almost_equal(scipyresult.fun, + mol.potential_energy.defunits_value()) + diff --git a/moldesign/_tests/molecule_fixtures.py b/moldesign/_tests/molecule_fixtures.py index 85c8fc4..82d1f7c 100644 --- a/moldesign/_tests/molecule_fixtures.py +++ b/moldesign/_tests/molecule_fixtures.py @@ -47,6 +47,13 @@ def ligand3aid(ligand_residue_3aid): return newmol +@typedfixture('molecule') +def ethylene_waterbox_2na_2cl(): + mol = mdt.from_smiles('C=C') + solvated = mdt.add_water(mol, padding=15.0*u.angstrom, ion_concentration=0.6*u.molar) + return solvated + + @pytest.fixture def random_atoms_from_3aid(pdb3aid): atoms = mdt.molecules.atomcollections.AtomList(random.sample(pdb3aid.atoms, 10)) diff --git a/moldesign/_tests/test_atom_containers.py b/moldesign/_tests/test_atom_containers.py index b1339aa..c1131da 100644 --- a/moldesign/_tests/test_atom_containers.py +++ b/moldesign/_tests/test_atom_containers.py @@ -9,6 +9,7 @@ from moldesign import units as u from . import helpers +from .molecule_fixtures import pdb3aid, ethylene_waterbox_2na_2cl registered_types = {} @@ -234,3 +235,46 @@ def test_get_atoms(protein): assert (atom.name == 'CA' and atom.residue.resname == 'GLY') == ( atom in gly_alpha_carbons) + +def test_get_atoms_keywords(ethylene_waterbox_2na_2cl): + mol = ethylene_waterbox_2na_2cl + with pytest.raises(ValueError): + mol.get_atoms('arglebargle') + + with pytest.raises(ValueError): + mol.get_atoms('ions') # it's "ion" only (for now) + + ions = mol.get_atoms('ion') + assert ions.num_atoms == 4 + for ion in ions: + assert ion.residue.resname in ('NA','CL') and ion.name in ('Na','Cl') + + waters = mol.get_atoms('water') + for w in waters: + assert w.residue.resname == 'HOH' + + solute = mol.get_atoms('unknown') + assert len(solute) == 6 + + assert len(waters) + len(ions) + len(solute) == mol.num_atoms + + +def test_get_residues_in_molecule(pdb3aid): + mol = pdb3aid + waters = mol.get_residues(type='water') + assert set(waters) == set(res for res in mol.residues if res.resname == 'HOH') + + +def test_get_residues_in_chain(pdb3aid): + mol = pdb3aid + chain = mol.chains['A'] + waters = chain.get_residues(type='water') + assert set(waters) == set(res for res in mol.chains['A'].residues if res.resname == 'HOH') + + +def test_get_residues_two_parameters(pdb3aid): + mol = pdb3aid + alas = mol.chains['B'].get_residues(resname='ALA') + assert set(alas) == set(res for res in mol.chains['B'].residues if res.resname == 'ALA') + ala2 = mol.get_residues(resname='ALA', chain='B') + assert set(ala2) == set(alas) diff --git a/moldesign/_tests/test_biopython_xface.py b/moldesign/_tests/test_biopython_xface.py new file mode 100644 index 0000000..4734386 --- /dev/null +++ b/moldesign/_tests/test_biopython_xface.py @@ -0,0 +1,21 @@ + +import pytest +import Bio.PDB +import moldesign as mdt + +from .helpers import get_data_path +from .molecule_fixtures import pdb3aid + + +@pytest.fixture +def biopy_3aid(): + parser = Bio.PDB.PDBParser() + structure = parser.get_structure('3aid', get_data_path('3aid.pdb')) + return structure + + +def test_biopy_to_mdt(biopy_3aid, pdb3aid): + mol = mdt.interfaces.biopython_to_mol(biopy_3aid) + assert mol.num_atoms == pdb3aid.num_atoms + assert mol.num_residues == pdb3aid.num_residues + assert mol.numchains == pdb3aid.num_chains diff --git a/moldesign/_tests/test_dynamics.py b/moldesign/_tests/test_dynamics.py deleted file mode 100644 index 86a06b6..0000000 --- a/moldesign/_tests/test_dynamics.py +++ /dev/null @@ -1,20 +0,0 @@ -import pytest - -import moldesign as mdt -from moldesign import units as u - -def test_shake(): - # probably there's a better way to test this, but it was already written in a notebook - mol = mdt.from_smiles('C=C') - monitor = mdt.DihedralMonitor(mol.atoms[0], mol.atoms[1]) - monitor.value = 90.0*u.degrees - constraint = monitor.constrain() - traj = mdt.Trajectory(mol) - for i in range(50): - oldpos = mol.positions.copy() - monitor.atoms[0].z += 0.05*u.angstrom - monitor.atoms[-1].x -= 0.05*u.angstrom - mdt.geom.shake_positions(mol, oldpos) - assert (mol.positions != oldpos).any() - traj.new_frame() - assert constraint.satisfied() diff --git a/moldesign/_tests/test_imports.py b/moldesign/_tests/test_imports.py index 5074220..77082d8 100644 --- a/moldesign/_tests/test_imports.py +++ b/moldesign/_tests/test_imports.py @@ -2,6 +2,7 @@ import sys +@pytest.mark.tryfirst def test_lazy_imports(): import moldesign diff --git a/moldesign/_tests/test_minimizers.py b/moldesign/_tests/test_minimizers.py index 1fcd802..7f49742 100644 --- a/moldesign/_tests/test_minimizers.py +++ b/moldesign/_tests/test_minimizers.py @@ -1,7 +1,10 @@ -import pytest +import collections +import numpy as np +import pytest import moldesign as mdt +from .helpers import assert_something_resembling_minimization_happened from moldesign import units as u @@ -13,6 +16,17 @@ def harmonic_atom(): return mol +@pytest.fixture(scope='function') +def scrambled(): + mol = mdt.from_smiles('C=C') + mol.set_energy_model(mdt.models.OpenBabelPotential, forcefield='mmff94s') + mol.com = [0, 0, 0] * u.angstrom + _apply_noise(mol, scale=5.0) + e0 = mol.calculate_potential_energy() + p0 = mol.positions.copy() + return mol, e0, p0 + + @pytest.mark.parametrize('MinClass', (mdt.min.GradientDescent, mdt.min.BFGS, mdt.min.SmartMin)) @@ -23,11 +37,82 @@ def test_basic_minimization(harmonic_atom, MinClass): minimizer = MinClass(mol) traj = minimizer() + assert_something_resembling_minimization_happened(p0, e0, traj, mol) + + +@pytest.mark.parametrize('MinClass', (mdt.min.GradientDescent, + mdt.min.BFGS, + mdt.min.SmartMin)) +def test_basic_minimization_remotely(harmonic_atom, MinClass): + mol = harmonic_atom + e0 = mol.calculate_potential_energy() + p0 = mol.positions.copy() + + minimizer = MinClass(mol) + traj = minimizer.runremotely() + + assert_something_resembling_minimization_happened(p0, e0, traj, mol) + + +MINIMIZERS = collections.OrderedDict([('gradient_descent', mdt.min.gradient_descent), + ('leastsqr', mdt.min.sequential_least_squares), + ('bfgs', mdt.min.bfgs), + ('smart', mdt.min.minimize)]) +# (ordered because pytest+xdist needs a definite ordering of parameters) + + +def test_extreme_forces_with_smart_minimizer(scrambled): + mol, e0, p0 = scrambled + + traj = mdt.minimize(mol, nsteps=500) + assert_something_resembling_minimization_happened(p0, e0, traj, mol) - _check_basic_minimization_has_occured(p0, e0, traj) +@pytest.mark.skipif(mdt.models.OpenBabelPotential._CALLS_MDT_IN_DOCKER, + reason='Redundant with regular test') +def test_remote_with_smart_minimizer(scrambled): + mol, e0, p0 = scrambled + + minimizer = mdt.min.SmartMin(mol, nsteps=500) + traj = minimizer.runremotely() + assert traj.mol is mol + assert_something_resembling_minimization_happened(p0, e0, traj, mol) + + +@pytest.mark.skipif(mdt.models.OpenBabelPotential._CALLS_MDT_IN_DOCKER, + reason='Redundant with regular test') +def test_remote_with_smart_minimizer_async(scrambled): + mol, e0, p0 = scrambled + job = mdt.min.minimize(mol, nsteps=500, remote=True, wait=False) + assert (mol.positions == p0).all() # shouldn't have changed yet + + job.wait() + assert (mol.positions != p0).any() # NOW it should have changed yet + traj = job.result + assert traj.mol is mol + assert_something_resembling_minimization_happened(p0, e0, traj, mol) + + +@pytest.mark.skipif(mdt.models.OpenBabelPotential._CALLS_MDT_IN_DOCKER, + reason='Redundant with regular test') +def test_remote_minimization_automatic_if_openbabel_not_installed(scrambled): + mol, e0, p0 = scrambled + + # a bit of an API hack - remote overridden if the model isn't installed locally + mol.energy_model._CALLS_MDT_IN_DOCKER = True # monkey-patch should only affect this instance + job = mdt.min.minimize(mol, nsteps=500, remote=False, wait=False) + assert (mol.positions == p0).all() # shouldn't have changed yet + job.wait() + assert (mol.positions != p0).any() # NOW it should have changed yet + traj = job.result + assert traj.mol is mol + assert_something_resembling_minimization_happened(p0, e0, traj, mol) + + +@pytest.mark.parametrize('minkey',(MINIMIZERS.keys())) +def test_constrained_distance_minimization(minkey): + minimizer = MINIMIZERS[minkey] -def test_constrained_minimization(): mol = mdt.Molecule([mdt.Atom(1), mdt.Atom(2)]) mol.atoms[0].x = 2.0 * u.angstrom mol.atoms[1].x = 3.0 * u.angstrom @@ -38,14 +123,57 @@ def test_constrained_minimization(): p0 = mol.positions.copy() constraint = mol.constrain_distance(mol.atoms[0], mol.atoms[1]) - traj = mol.minimize() # use the built-in logic for this one - _check_basic_minimization_has_occured(p0, e0, traj) + if minkey == 'bfgs': # BFGS expected to fail here + with pytest.raises(mdt.exceptions.NotSupportedError): + minimizer(mol) + return + + traj = minimizer(mol) + + assert_something_resembling_minimization_happened(p0, e0, traj, mol) assert abs(constraint.error()) < 1e-4 * u.angstrom -def _check_basic_minimization_has_occured(p0, e0, traj): - assert traj.potential_energy[0] == e0 - assert traj.potential_energy[-1] < e0 - assert (traj.positions[0] == p0).all() - assert (traj.positions[-1] != p0).any() + +@pytest.mark.parametrize('minkey',(MINIMIZERS.keys())) +def test_constrained_dihedral_minimization(minkey): + minimizer = MINIMIZERS[minkey] + mol = mdt.from_smiles('C=C') + assert mol.atoms[0].atnum == mol.atoms[1].atnum == 6 # make sure we're picking the right atoms + + dihedral = mdt.DihedralMonitor(mol.atoms[0], mol.atoms[1]) + assert dihedral.value == 0.0 + + dihedral.value = 45 * u.degrees + constraint = dihedral.constrain() + + mol.set_energy_model(mdt.models.OpenBabelPotential, forcefield='mmff94s') + e0_1 = mol.calculate_potential_energy() + p0_1 = mol.positions.copy() + + if minkey == 'bfgs': # BFGS expected to fail here + with pytest.raises(mdt.exceptions.NotSupportedError): + minimizer(mol) + return + + traj = minimizer(mol, nsteps=100) + assert_something_resembling_minimization_happened(p0_1, e0_1, traj, mol) + + assert constraint.error() <= 1.0 * u.degree + traj_twists = traj.dihedral(mol.atoms[0], mol.atoms[1]) + assert (abs(traj_twists - 45 * u.degrees) <= 1.0 * u.degree).all() + + e0_2 = mol.potential_energy + p0_2 = mol.positions.copy() + mol.clear_constraints() + traj2 = minimizer(mol, nsteps=100) + + assert_something_resembling_minimization_happened(p0_2, e0_2, traj2, mol) + assert dihedral.value <= 5.0 * u.degrees + + +def _apply_noise(mol, scale=0.05): + noise = np.random.normal(size=(mol.num_atoms, 3), scale=scale) * u.angstrom + mol.positions += noise + diff --git a/moldesign/_tests/test_openmm_xface.py b/moldesign/_tests/test_openmm_xface.py index 8711194..108bffb 100644 --- a/moldesign/_tests/test_openmm_xface.py +++ b/moldesign/_tests/test_openmm_xface.py @@ -13,7 +13,6 @@ parameterize_am1bcc, parameterize_zeros, protein_default_amber_forcefield) - registered_types = {} registered_types.update(obtypes) registered_types.update(ambtypes) @@ -63,9 +62,10 @@ def test_analytical_vs_numerical_forces(objkey, request): @pytest.mark.parametrize('objkey', registered_types['hasmodel']) def test_minimization_reduces_energy(objkey, request): mol = request.getfixturevalue(objkey) - e1 = mol.calculate_potential_energy() + p0 = mol.positions.copy() + e0 = mol.calculate_potential_energy() traj = mol.minimize() - assert mol.calculate_potential_energy() < e1 + helpers.assert_something_resembling_minimization_happened(p0, e0, traj, mol) @pytest.mark.parametrize('objkey', registered_types['hasmodel']) diff --git a/moldesign/_tests/test_pdbfixer_xface.py b/moldesign/_tests/test_pdbfixer_xface.py new file mode 100644 index 0000000..bf3aeb0 --- /dev/null +++ b/moldesign/_tests/test_pdbfixer_xface.py @@ -0,0 +1,136 @@ +from __future__ import print_function, absolute_import, division +from future.builtins import * +from future import standard_library + +standard_library.install_aliases() + +# Copyright 2017 Autodesk Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np + +import moldesign as mdt +from moldesign import units as u + +import pytest + +from .molecule_fixtures import pdb3aid, benzene, pdb1yu8 + + +def test_inchain_residue_mutation_in_protein(pdb3aid): + _mutate_and_check(pdb3aid, 3, 'ALA', + {'C', 'CA', 'CB', 'HA', 'HB1', 'HB2', 'HB3', 'HN2', 'N', 'O'}) + + +def test_nterm_residue_mutate_protein(pdb3aid): + _mutate_and_check(pdb3aid, 0, 'MET', + {'CE', 'HE1', 'C', 'N', 'HA', 'HB2', 'HE3', 'HG2', 'CG', 'CA', 'HG3', + 'O', 'HB3', 'HN2', 'CB', 'HE2'}) + + +def test_cterm_residue_mutate_protein(pdb3aid): + cterm = pdb3aid.chains['A'].c_terminal + _mutate_and_check(pdb3aid, cterm.index, 'LEU', + {'HD11', 'HD23', 'C', 'N', 'HD22', 'HA', 'HB2', 'CG', 'CA', 'CD2', 'HD21', + 'O', 'CD1', 'HD12', 'HB3', 'HN2', 'HD13', 'CB', 'HG'}) + + +def _mutate_and_check(mol, residx, resname, allatoms): + newmol = mdt.mutate_residues(mol, {mol.residues[residx]: resname}) + + assert newmol.num_chains == mol.num_chains + assert mol.num_residues == newmol.num_residues + foundnew = False + + for i, (res, newres) in enumerate(zip(mol.residues, newmol.residues)): + if i == residx: + foundnew = True + assert newres.resname == resname + assert newres.name == resname+str(newres.pdbindex) + atomnames = set(atom.name for atom in newres) + assert len(atomnames) == newres.num_atoms + assert atomnames.issubset(allatoms) + else: + assert res.name == newres.name + assert res.num_atoms == newres.num_atoms + + for oldatom, newatom in zip(res, newres): + assert oldatom.name == newatom.name + assert oldatom.atnum == newatom.atnum + if not foundnew: + assert oldatom.pdbindex == newatom.pdbindex + + +def test_mutate_docstring_dict_example(pdb3aid): + mol = pdb3aid + assert mol.residues[5].resname != 'ALA' + mut = mdt.mutate_residues(mol, {mol.residues[5]: 'ALA'}) # mutate residue 5 to ALA + assert mut.residues[5].resname == 'ALA' + + +def test_mutation_nomenclature_string_only(pdb3aid): + mol = pdb3aid + res25 = mol.get_residues(pdbindex=25) + assert len(res25) == 2 + assert [r.resname for r in res25] == ['ASP', 'ASP'] + + mut = mdt.mutate_residues(mol, 'D25M') # Mutate ALA43 to MET43 + assert mut.get_residues() + mut25 = mut.get_residues(pdbindex=25) + assert len(mut25) == 2 + assert [r.resname for r in mut25] == ['MET', 'MET'] + + +def test_multiple_mutations(pdb3aid): + mol = pdb3aid + mut = mdt.mutate_residues(mol, ['A.2S', 'B.3S']) # Mutate Chain A res 2 and B 3 to SER + assert [r.resname for r in mut.chains['A'].get_residues(pdbindex=2)] == ['SER'] + assert [r.resname for r in mut.chains['B'].get_residues(pdbindex=3)] == ['SER'] + + +def test_solvate_small_molecule_boxsize(benzene): + newmol = mdt.add_water(benzene, min_box_size=15.0*u.angstrom) + assert newmol.num_atoms > 50 # who knows how many? more than benzene though + + +def test_seawater_solvation_small_molecule(benzene): + newmol = mdt.add_water(benzene, + min_box_size=20.0*u.angstrom, + ion_concentration=0.6*u.molar) + assert newmol.num_atoms > 50 # who knows how many? more than benzene though + assert len(newmol.get_atoms(name='Cl')) == 3 # TODO: check that this is correct molarity + assert len(newmol.get_atoms(name='Na')) == 3 # TODO: check that this is correct molarity + + +def test_solvation_alternative_ions(benzene): + newmol = mdt.add_water(benzene, + min_box_size=20.0*u.angstrom, + ion_concentration=0.6*u.molar, + positive_ion='Rb', + negative_ion='I') + assert newmol.num_atoms > 50 # who knows how many? more than benzene though + assert len(newmol.get_atoms(name='Rb')) == 3 # TODO: check that this is correct molarity + assert len(newmol.get_atoms(name='I')) == 3 # TODO: check that this is correct molarity + + +def test_solvate_protein_padding(pdb1yu8): + newmol = mdt.add_water(pdb1yu8, padding=5.0*u.angstrom) + assert newmol.num_atoms > pdb1yu8.num_atoms + + oldmol = mdt.Molecule(newmol.residues[:pdb1yu8.num_residues]) + assert oldmol.same_topology(pdb1yu8, verbose=True) + + np.testing.assert_allclose(pdb1yu8.positions.value_in(u.angstrom), + oldmol.positions.value_in(u.angstrom), + atol=1e-3) diff --git a/moldesign/_tests/test_symmetry.py b/moldesign/_tests/test_symmetry.py index 518af4b..9733660 100644 --- a/moldesign/_tests/test_symmetry.py +++ b/moldesign/_tests/test_symmetry.py @@ -50,6 +50,33 @@ def planar(): return mdt.Molecule([a1, a2, a3], name='planar_h3'), expected, True +@pytest.fixture +def almost_planar(planar): + atoms = planar[0].atoms + atoms.append(mdt.Atom(4)) + atoms[-1].position = [0, 0, 0.005] * u.angstrom + return mdt.Molecule(atoms, name='almost_planar') + + +def test_approximate_symmetry(almost_planar): + mol = almost_planar.copy() + + symmetries = mdt.get_symmetry(almost_planar) + + assert symmetries.rms > 0 + assert len(symmetries.exact) == 1 + assert symmetries.exact[0].symbol == 'C1' + + assert len(symmetries.approximate) == 1 + assert symmetries.approximate[0].symbol == 'Cs' + cs = symmetries.groups['Cs'][0] + + mol.positions = symmetries.get_symmetrized_coords(cs) + newsymm = mdt.get_symmetry(mol) + assert len(newsymm.exact) == 2 + assert len(newsymm.approximate) == 0 + + @pytest.fixture def benz(benzene): # Note: we're counting on the geometry generation algorithm to give proper symmetries diff --git a/moldesign/_tests/test_tools.py b/moldesign/_tests/test_tools.py index d670b9b..e27c279 100644 --- a/moldesign/_tests/test_tools.py +++ b/moldesign/_tests/test_tools.py @@ -50,6 +50,20 @@ def test_ammonium_formal_charge(objkey, request): assert atom.formal_charge == 0 * u.q_e +def test_set_hybridization_and_saturate(): + # Creates just the carbons of ethylene, expects the routine to figure out the rest + atom1 = mdt.Atom(6) + atom2 = mdt.Atom(6) + atom2.x = 1.35 * u.angstrom + atom1.bond_to(atom2, 1) + mol = mdt.Molecule([atom1, atom2]) + newmol = mdt.set_hybridization_and_saturate(mol) + pytest.xfail('This is apparently broken') + assert newmol.num_atoms == 6 + assert newmol.atoms[0].bond_graph[atom1] == 2 + assert len(newmol.get_atoms(atnum=1)) == 4 + + @pytest.fixture def c2_no_hydrogen_from_smiles(): mymol = mdt.from_smiles('[CH0][CH0]') diff --git a/moldesign/compute/compute.py b/moldesign/compute/compute.py index 7a9acf2..7a91528 100644 --- a/moldesign/compute/compute.py +++ b/moldesign/compute/compute.py @@ -88,7 +88,8 @@ def __init__(self, result, updated_object=None): self.updated_object = updated_object -def run_job(job, engine=None, image=None, wait=True, jobname=None, display=True, + +def run_job(job, engine=None, wait=True, jobname=None, display=True, _return_result=False): """ Helper for running jobs. @@ -104,7 +105,6 @@ def run_job(job, engine=None, image=None, wait=True, jobname=None, display=True, Returns: pyccc job object OR function's return value """ - engine = utils.if_not_none(engine, mdt.compute.get_engine()) if engine is None: @@ -112,7 +112,6 @@ def run_job(job, engine=None, image=None, wait=True, jobname=None, display=True, 'moldesign.compute.config') engine.submit(job) - jobname = utils.if_not_none(jobname, job.name) if display: @@ -125,7 +124,42 @@ def run_job(job, engine=None, image=None, wait=True, jobname=None, display=True, return job +@utils.args_from(run_job, only='engine wait jobname display'.split()) +def runremotely(func, args=None, kwargs=None, + jobname=None, engine=None, image=None, wait=True, display=True, + persist_refs=True, when_finished=None): + """ Runs a python command remotely. + Args: + job (pyccc.Job): The job to run + Returns: + pyccc.PythonJob OR object: reference to the job if wait=False, or the function's + return value, if wait=True + """ + import pyccc + + if args is None: + args = [] + if kwargs is None: + kwargs = {} + + if image is None: + image = mdt.compute.config.default_python_image + + if jobname is None: + jobname = func.__name__ + if args: + jobname += str(args[0]) + + call = pyccc.PythonCall(func, *args, **kwargs) + job = pyccc.PythonJob(command=call, image=image, engine=engine, name=jobname, + submit=False, persist_references=persist_refs, + sendsource=False, when_finished=when_finished) + job = run_job(job, wait=wait, display=display) + if wait: + return job.result + else: + return job diff --git a/moldesign/data/biochemical.py b/moldesign/data/biochemical.py index d4ae7a3..01d0c21 100644 --- a/moldesign/data/biochemical.py +++ b/moldesign/data/biochemical.py @@ -35,6 +35,8 @@ GLN="Q", ARG="R", SER="S", THR="T", VAL="V", TRP="W", XAA="X", TYR="Y", GLX="Z") +RESIDUE_CODE_TO_NAME = {v:k for k,v in RESIDUE_ONE_LETTER.items()} + BIOPOLYMER_TYPES = set('dna rna protein'.split()) CHAIN_MONOMER_NAMES = {'dna': 'dna base', @@ -87,8 +89,8 @@ 'MG': 'Mg+2', 'CA': 'Ca+2', 'F': 'F-', - 'Cl': 'Cl-', - 'Br': 'Br-', + 'CL': 'Cl-', + 'BR': 'Br-', 'I': 'I-'} RESTYPES = dict( diff --git a/moldesign/exceptions.py b/moldesign/exceptions.py index aacbb58..1c2fe94 100644 --- a/moldesign/exceptions.py +++ b/moldesign/exceptions.py @@ -17,6 +17,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +class NotSupportedError(Exception): + """ Raised when a given method can't support the requested calculation + """ + class ConvergenceFailure(Exception): """ Raised when an iterative calculation fails to converge """ diff --git a/moldesign/fileio.py b/moldesign/fileio.py index 9dd41f0..85a4d6c 100644 --- a/moldesign/fileio.py +++ b/moldesign/fileio.py @@ -259,15 +259,16 @@ def write_trajectory(traj, filename=None, format=None, overwrite=True): def read_pdb(f, assign_ccd_bonds=True): """ Read a PDB file and return a molecule. - This uses the biopython parser to get the molecular structure, but uses internal parsers - to create bonds and biomolecular assembly data. + This uses ParmEd's parser to get the molecular structure, with additional functionality + to assign Chemical Component Dictionary bonds, detect missing residues, and find + biomolecular assembly information. Note: Users won't typically use this routine; instead, they'll use ``moldesign.read``, which will delegate to this routine when appropriate. Args: - f (filelike): filelike object giving access to the PDB file (must implement seek) + f (filelike): filelike object giving access to the PDB file (must implement readline+seek) assign_ccd_bonds (bool): Use the PDB Chemical Component Dictionary (CCD) to create bond topology (note that bonds from CONECT records will always be created as well) @@ -324,6 +325,13 @@ def read_xyz(f): return mdt.Molecule(tempmol.atoms) +def write_xyz(mol, fileobj): + fileobj.write(u" %d\n%s\n" % (mol.num_atoms, mol.name)) + for atom in mol.atoms: + x, y, z = atom.position.value_in(mdt.units.angstrom) + fileobj.write(u"%s %24.14f %24.14f %24.14f\n" % (atom.element, x, y, z)) + + @utils.exports def from_pdb(pdbcode, usecif=False): """ Import the given molecular geometry from PDB.org @@ -442,7 +450,8 @@ def _get_format(filename, format): 'xyz': read_xyz} WRITERS = {'pdb': write_pdb, - 'mmcif': write_mmcif} + 'mmcif': write_mmcif, + 'xyz': write_xyz} if PY2: bzopener = bz2.BZ2File diff --git a/moldesign/geom/symmetry.py b/moldesign/geom/symmetry.py index 8776b98..72edebe 100644 --- a/moldesign/geom/symmetry.py +++ b/moldesign/geom/symmetry.py @@ -91,16 +91,28 @@ def get_axis(self): class MolecularSymmetry(object): def __init__(self, mol, symbol, rms, - orientation=None, - elems=None, + orientation, elems, _job=None): self.mol = mol self.symbol = symbol self.rms = rms self.orientation = mdt.utils.if_not_none(orientation, mol.positions) self.elems = mdt.utils.if_not_none(elems, []) + self.groups = mdt.utils.Categorizer(lambda x:x.symbol, self.elems) self._job = _job + @property + def exact(self): + """ List[SymmetryElement]: Exact symmetry elements + """ + return [elem for elem in self.elems if elem.max_diff == 0.0] + + @property + def approximate(self): + """ List[SymmetryElement]: Approximate symmetry elements + """ + return [elem for elem in self.elems if elem.max_diff != 0.0] + def __str__(self): return '%d symmetry element%s' % (len(self.elems), 's' if len(self.elems) != 1 else '') diff --git a/moldesign/interfaces/biopython_interface.py b/moldesign/interfaces/biopython_interface.py index bed0910..610c796 100644 --- a/moldesign/interfaces/biopython_interface.py +++ b/moldesign/interfaces/biopython_interface.py @@ -30,62 +30,8 @@ from moldesign.utils import exports -def parse_mmcif(f): - """Parse an mmCIF file (using the Biopython parser) and return a molecule - - Note: - This routine is not currently called by any part of the user-facing API! The - OpenBabel parser appears to give more accurate results for the time being. The - molecules created using this routine will NOT have any bond topology! - - Args: - f (file): file-like object containing the mmCIF file - - Returns: - moldesign.Molecule: parsed molecule - """ - return _parse_file(f, Bio.PDB.MMCIFParser) - - -def parse_pdb(f): - """Parse a PDB file (using the Biopython parser) and return the basic structure - - Note: - This structure will be missing some key data - most notably bonds, but also - any biomolecular assembly information. Therefore, our default parser combines - this routine with a few other methods to create the final Molecule object - - See also: - moldesign.fileio.read_pdb - - Args: - f (file): file-like object containing the PDB file - - Returns: - moldesign.Molecule: parsed molecule - """ - # TODO: this needs to handle strings and streams - # TODO: deal with alternate locations - - return _parse_file(f, Bio.PDB.PDBParser) - - -def _parse_file(f, parser_type): - parser = parser_type() - struc = parser.get_structure('no name', f) - mol = biopy_to_mol(struc) - return mol - - -def get_pdb_seqres(f): - from Bio import SeqIO - chainseqs = list(SeqIO.parse(f, "pdb-seqres")) - seqs = {cs.annotations['chain']: str(cs.seq) for cs in chainseqs} - return seqs - - @exports -def biopy_to_mol(struc): +def biopython_to_mol(struc): """Convert a biopython PDB structure to an MDT molecule. Note: @@ -127,8 +73,7 @@ def biopy_to_mol(struc): newatoms.append(newatom) - return mdt.Molecule(newatoms, - name=struc.get_full_id()[0]) + return mdt.Molecule(newatoms, name=struc.get_full_id()[0]) def get_mmcif_assemblies(fileobj=None, mmcdata=None): diff --git a/moldesign/interfaces/openbabel.py b/moldesign/interfaces/openbabel.py index fe945d2..bcfe850 100644 --- a/moldesign/interfaces/openbabel.py +++ b/moldesign/interfaces/openbabel.py @@ -37,37 +37,6 @@ from ..utils import exports -def read_file(filename, name=None, format=None): - """ Read a molecule from a file - - Note: - Currently only reads the first conformation in a file - - Args: - filename (str): path to file - name (str): name to assign to molecule - format (str): File format: pdb, sdf, mol2, bbll, etc. - - Returns: - moldesign.Molecule: parsed result - """ - # TODO: check for openbabel molecule name? - if format is None: - format = filename.split('.')[-1] - - if force_remote: - with open(filename, 'r') as infile: - mol = read_string(infile.read(), format, name=name) - return mol - else: - pbmol = next(pb.readfile(format=format, filename=filename)) - if name is None: - name = filename - mol = pybel_to_mol(pbmol, name=os.path.basename(name)) - mol.filename = filename - return mol - - def read_stream(filelike, format, name=None): """ Read a molecule from a file-like object @@ -128,25 +97,6 @@ def write_string(mol, format): return str(outstr) -def write_file(mol, filename=None, mode='w', format=None): - """ Write molecule to a file - - Args: - mol (moldesign.Molecule): molecule to write - filename (str): File to write to - mode (str): Writing mode (e.g. 'w' to overwrite, the default, or 'a' to append) - format (str): File format: pdb, sdf, mol2, bbll, etc. - """ - if format is None: - format = filename.split('.')[-1] - outstr = write_string(mol, format) - if filename is None: - return outstr - else: - with open(filename, mode) as wrf: - print(outstr, file=wrf) - - @runsremotely(enable=force_remote) def guess_bond_orders(mol): """Use OpenBabel to guess bond orders using geometry and functional group templates. @@ -181,32 +131,6 @@ def add_hydrogen(mol): return newmol -@runsremotely(enable=force_remote) -def set_protonation(mol, ph=7.4): - """ Adjust protonation according to the OpenBabel pka model. - - This routine will return a copy of the molecule with the new protonation state and adjusted - formal charges - - Args: - mol (moldesign.Molecule): Molecule to adjust - ph (float): assign protonation state for this pH value - - Returns: - moldesign.Molecule: New molecule with adjusted protonation - """ - # TODO: this doesn't appear to do anything for most molecules!!! - # TODO: this renames hydrogens!!! - - pbmol = mol_to_pybel(mol) - pbmol.OBMol.AddHydrogens(False, True, ph) - - newmol = pybel_to_mol(pbmol, reorder_atoms_by_residue=True) - mdt.helpers.assign_unique_hydrogen_names(newmol) - mdt.assign_formal_charges(newmol, ignore_nonzero=False) - return newmol - - @exports def mol_to_pybel(mdtmol): """ Translate a moldesign molecule object into a pybel molecule object. diff --git a/moldesign/interfaces/openmm.py b/moldesign/interfaces/openmm.py index 639f37a..a2e6f47 100644 --- a/moldesign/interfaces/openmm.py +++ b/moldesign/interfaces/openmm.py @@ -347,5 +347,12 @@ def mol_to_topology(mol): @exports def mol_to_modeller(mol): from simtk.openmm import app + if mol.is_small_molecule: + if not mol.residues[0].resname: + mol.residues[0].resname = 'UNL' + mol.residues[0].pdbindex = 1 + if not mol.chains[0].pdbname: + mol.chains[0].pdbname = 'A' + return app.Modeller(mol_to_topology(mol), pint2simtk(mol.positions)) diff --git a/moldesign/interfaces/pdbfixer_interface.py b/moldesign/interfaces/pdbfixer_interface.py index 3c839ac..9db94cd 100644 --- a/moldesign/interfaces/pdbfixer_interface.py +++ b/moldesign/interfaces/pdbfixer_interface.py @@ -1,4 +1,7 @@ from __future__ import print_function, absolute_import, division + +import string + from future.builtins import * from future import standard_library standard_library.install_aliases() @@ -16,9 +19,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from past.builtins import basestring import imp import io +import re + import numpy as np @@ -41,7 +47,7 @@ def mol_to_fixer(mol): import pdbfixer fixer = pdbfixer.PDBFixer( - pdbfile=io.BytesIO(mol.write(format='pdb'))) + pdbfile=io.StringIO(mol.write(format='pdb'))) return fixer @@ -51,45 +57,158 @@ def fixer_to_mol(f): @compute.runsremotely(enable=force_remote) -def add_hydrogen(mol, pH=7.4): - fixer = mol_to_fixer(mol) - fixer.addMissingHydrogens(pH) - return fixer_to_mol(fixer) +def mutate_residues(mol, residue_map): + """ Create a mutant with point mutations (returns a copy - leaves the original unchanged) + Mutations may be specified in one of two ways: + 1) As a dictionary mapping residue objects to the 3-letter name of the amino acid they + will be mutated into: ``{mol.residues[3]: 'ALA'}`` + 2) As a list of mutation strings: ``['A43M', '332S', 'B.53N']`` (see below) -@compute.runsremotely(enable=force_remote) -def mutate(mol, mutationmap): + Mutation strings have the form: + ``[chain name .][initial amino acid code](residue index)(mutant amino acid code)`` + If the chain name is omited, the mutation will be applied to all chains (if possible). + The initial amino acid code may also be omitted. + + Examples: + >>> mutate_residues(mol, {mol.residues[5]: 'ALA'}) # mutate residue 5 to ALA + >>> mutate_residues(mol, 'A43M') # In all chains with ALA43 mutate it to MET43 + >>> mutate_residues(mol, ['A.332S', 'B.120S']) # Mutate Chain A res 332 and B 120 to SER + >>> mutate_residues(mol, ['B.C53N']) # Mutate Cysteine 53 in chain B to Asparagine + + Args: + mol (moldesign.Molecule): molecule to create mutant from + residue_map (Dict[moldesign.Residue:str] OR List[str]): list of mutations (see above for + allowed formats) + + Returns: + moldesign.Molecule: the mutant + """ fixer = mol_to_fixer(mol) chain_mutations = {} - for res in mutationmap: - chain_mutations.setdefault(res.chain.pdbname, {})[res] = mutationmap[res] + mutation_strs = [] + if not hasattr(residue_map, 'items'): + residue_map = _mut_strs_to_map(mol, residue_map) + + if not residue_map: + raise ValueError("No mutations specified!") + + for res, newres in residue_map.items(): + chain_mutations.setdefault(res.chain.pdbname, {})[res] = residue_map[res] + mutation_strs.append(_mutation_as_str(res, newres)) for chainid, mutations in chain_mutations.items(): mutstrings = ['%s-%d-%s' % (res.resname, res.pdbindex, newname) for res, newname in mutations.items()] - print('Applying mutations to chain %s: %s' % (chainid, ', '.join(mutstrings))) - fixer.applyMutations(mutations, chainid) - return fixer_to_mol(fixer) + fixer.applyMutations(mutstrings, chainid) + temp_mutant = fixer_to_mol(fixer) + _pdbfixer_chainnames_to_letter(temp_mutant) + + # PDBFixer reorders atoms, so to keep things consistent, we'll graft the mutated residues + # into an MDT structure + assert temp_mutant.num_residues == mol.num_residues # shouldn't change number of residues + residues_to_copy = [] + for oldres, mutant_res in zip(mol.residues, temp_mutant.residues): + if oldres in residue_map: + residues_to_copy.append(mutant_res) + mutant_res.mol = None + mutant_res.chain = oldres.chain + for atom in mutant_res.atoms: + atom.chain = oldres.chain + else: + residues_to_copy.append(oldres) + + metadata = {'origin': mol.metadata.copy(), + 'mutations': mutation_strs} + + return mdt.Molecule(residues_to_copy, + name='Mutant of "%s"' % mol, + metadata=metadata) + + +def _pdbfixer_chainnames_to_letter(pdbfixermol): + for chain in pdbfixermol.chains: + try: + if chain.name.isdigit(): + chain.name = string.ascii_uppercase[int(chain.name)-1] + except (ValueError, TypeError, IndexError): + continue # not worth crashing over + + +def _mutation_as_str(res, newres): + """ Create mutation string for storage as metadata. + + Note that this will include the name of the chain, if available, as a prefix: + + Examples: + >>> res = mdt.Residue(resname='ALA', pdbindex='23', chain=mdt.Chain(name=None)) + >>> _mutation_as_str(res, 'TRP') + 'A23W' + >>> res = mdt.Residue(resname='ALA', pdbindex='23', chain=mdt.Chain(name='C')) + >>> _mutation_as_str(res, 'TRP') + 'C.A23W' + Args: + res (moldesign.Residue): residue to be mutated + newres (str): 3-letter residue code for new amino acid -@compute.runsremotely(enable=force_remote) -def get_missing_residues(mol): - fixer = mol_to_fixer(mol) - fixer.findMissingResidues() - fixerchains = list(fixer.topology.chains) + Returns: + str: mutation string - missing = list() - for (chainidx, insertionpoint), reslist in fixer.missingResidues.items(): - chainid = fixerchains[chainidx].id - for ires, resname in enumerate(reslist): - missing.append(mdt.helpers.MissingResidue(chainid, resname, insertionpoint + ires)) + References: + Nomenclature for the description of sequence variations + J.T. den Dunnen, S.E. Antonarakis: Hum Genet 109(1): 121-124, 2001 + Online at http://www.hgmd.cf.ac.uk/docs/mut_nom.html#protein + """ + try: # tries to describe mutation using standard + mutstr = '%s%s%s' % (res.code, res.pdbindex, + mdt.data.RESIDUE_ONE_LETTER.get(newres, '?')) + if res.chain.pdbname: + mutstr = '%s.%s' % (res.chain.pdbname, mutstr) + return mutstr + except (TypeError, ValueError) as e: + print('WARNING: failed to construct mutation code: %s' % e) + return '%s -> %s' % (str(res), newres) + + +MUT_RE = re.compile(r'(.*\.)?([^\d]*)(\d+)([^\d]+)') # parses mutation strings + + +def _mut_strs_to_map(mol, strs): + if isinstance(strs, basestring): + strs = [strs] + mutmap = {} + for s in strs: + match = MUT_RE.match(s) + if match is None: + raise ValueError("Failed to parse mutation string '%s'" % s) + chainid, initialcode, residx, finalcode = match.groups() + if chainid is not None: + parent = mol.chains[chainid[:-1]] + else: + parent = mol # queries the whole molecule + + newresname = mdt.data.RESIDUE_CODE_TO_NAME[finalcode] + + query = {'pdbindex': int(residx)} + if initialcode: + query['code'] = initialcode + + residues = parent.get_residues(**query) - return missing + if len(residues) == 0: + raise ValueError("Mutation '%s' did not match any residues" % s) + + for res in residues: + assert res not in mutmap, "Multiple mutations for %s" % res + mutmap[res] = newresname + + return mutmap @compute.runsremotely(enable=force_remote) def add_water(mol, min_box_size=None, padding=None, - ion_concentration=None, neutralize=True, + ion_concentration=0.0, neutralize=True, positive_ion='Na+', negative_ion='Cl-'): """ Solvate a molecule in a water box with optional ions @@ -125,10 +244,10 @@ def add_water(mol, min_box_size=None, padding=None, assert negative_ion[-1] != '+' negative_ion += '-' - if ion_concentration is not None: - ion_concentration = u.MdtQuantity(ion_concentration) - if ion_concentration.dimensionless: - ion_concentration *= u.molar + ion_concentration = u.MdtQuantity(ion_concentration) + if ion_concentration.dimensionless: + ion_concentration *= u.molar + ion_concentration = opm.pint2simtk(ion_concentration) # calculate box size - in each dimension, use the largest of min_box_size or # the calculated padding @@ -143,20 +262,36 @@ def add_water(mol, min_box_size=None, padding=None, modeller = opm.mol_to_modeller(mol) - # TODO: this is like 10 bad things at once. Should probably submit a - # PR to PDBFixer to make this a public staticmethod instead of a private instancemethod - # Alternatively, PR to create Fixers directly from Topology objs + # Creating my fixers directly from Topology objs ff = pdbfixer.PDBFixer.__dict__['_createForceField'](None, modeller.getTopology(), True) modeller.addSolvent(ff, boxSize=opm.pint2simtk(boxsize), positiveIon=positive_ion, negativeIon=negative_ion, - ionicStrength=opm.pint2simtk(ion_concentration), + ionicStrength=ion_concentration, neutralize=neutralize) - return opm.topology_to_mol(modeller.getTopology(), - positions=modeller.getPositions(), - name='%s with water box' % mol.name) + solv_tempmol = opm.topology_to_mol(modeller.getTopology(), + positions=modeller.getPositions(), + name='%s with water box' % mol.name) + + _pdbfixer_chainnames_to_letter(solv_tempmol) + + + # PDBFixer reorders atoms, so to keep things consistent, we'll graft the mutated residues + # into an MDT structure + newmol_atoms = [mol] + for residue in solv_tempmol.residues[mol.num_residues:]: + newmol_atoms.append(residue) + + newmol = mdt.Molecule(newmol_atoms, + name="Solvated %s" % mol, + metadata={'origin':mol.metadata}) + + assert newmol.num_atoms == solv_tempmol.num_atoms + assert newmol.num_residues == solv_tempmol.num_residues + return newmol + diff --git a/moldesign/min/base.py b/moldesign/min/base.py index 2e8fc50..378fc61 100644 --- a/moldesign/min/base.py +++ b/moldesign/min/base.py @@ -40,6 +40,8 @@ def __init__(self, mol, nsteps=20, self.frame_interval = mdt.utils.if_not_none(frame_interval, max(nsteps/10, 1)) self._restart_from = _restart_from + self._foundmin = None + self._calc_cache = {} # Set up the trajectory to track the minimization self.traj = mdt.Trajectory(mol) @@ -89,6 +91,8 @@ def objective(self, vector): except mdt.QMConvergenceError: # returning infinity can help rescue some line searches return np.inf + self._cachemin() + self._calc_cache[tuple(vector)] = self.mol.properties pot = self.mol.potential_energy if self._initial_energy is None: self._initial_energy = pot @@ -101,6 +105,8 @@ def grad(self, vector): """ self._sync_positions(vector) self.mol.calculate(requests=self.request_list) + self._cachemin() + self._calc_cache[tuple(vector)] = self.mol.properties grad = -self.mol.forces grad = grad.reshape(self.mol.num_atoms * 3) @@ -110,20 +116,58 @@ def grad(self, vector): else: return grad.defunits() - def __call__(self): + def _cachemin(self): + """ Caches the minimum potential energy properties so we can return them + when the calculation is done. + + Underlying implementations can use this or not - it may not be valid if constraints + are present + """ + if self._foundmin is None or self.mol.potential_energy < self._foundmin.potential_energy: + self._foundmin = self.mol.properties + + def __call__(self, remote=False, wait=True): """ Run the minimization + Args: + remote (bool): launch the minimization in a remote job + wait (bool): if remote, wait until the minimization completes before returning. + (if remote=True and wait=False, will return a reference to the job) + Returns: moldesign.Trajectory: the minimization trajectory """ - self.run() - if self.traj.num_frames == 0 or self.traj.frames[-1].minimization_step != self.current_step: + if remote or getattr(self.mol.energy_model, '_CALLS_MDT_IN_DOCKER', False): + return self.runremotely(wait=wait) + + self._run() + + # Write the last step to the trajectory, if needed + if self.traj.potential_energy[-1] != self.mol.potential_energy: + assert self.traj.potential_energy[-1] > self.mol.potential_energy self.traj.new_frame(minimization_step=self.current_step, annotation='minimization result (%d steps) (energy=%s)'% (self.current_step, self.mol.potential_energy)) return self.traj - def run(self): + def runremotely(self, wait=True): + """ Execute this minimization in a remote process + + Args: + wait (bool): if True, block until the minimization is complete. + Otherwise, return a ``pyccc.PythonJob`` object + """ + return mdt.compute.runremotely(self.__call__, wait=wait, + jobname='%s: %s' % (self.__class__.__name__, self.mol.name), + when_finished=self._finishremoterun) + + def _finishremoterun(self, job): + traj = job.function_result + self.mol.positions = traj.positions[-1] + self.mol.properties.update(traj.frames[-1]) + return traj + + def _run(self): raise NotImplementedError('This is an abstract base class') def callback(self, *args): @@ -171,13 +215,11 @@ def _as_function(cls, newname): """ @mdt.utils.args_from(cls, allexcept=['self']) def asfn(*args, **kwargs): + remote = kwargs.pop('remote', False) + wait = kwargs.pop('wait', True) obj = cls(*args, **kwargs) - return obj() + return obj(remote=remote, wait=wait) asfn.__name__ = newname asfn.__doc__ = cls.__doc__ return asfn - - - - diff --git a/moldesign/min/descent.py b/moldesign/min/descent.py index 941467d..1dc313f 100644 --- a/moldesign/min/descent.py +++ b/moldesign/min/descent.py @@ -66,7 +66,7 @@ def __init__(self, mol, self.control = control self._last_energy = None - def run(self): + def _run(self): print('Starting geometry optimization: built-in gradient descent') lastenergy = self.objective(self._coords_to_vector(self.mol.positions)) current = self._coords_to_vector(self.mol.positions) diff --git a/moldesign/min/scipy.py b/moldesign/min/scipy.py index 8b513d7..f822624 100644 --- a/moldesign/min/scipy.py +++ b/moldesign/min/scipy.py @@ -20,6 +20,7 @@ from .. import units as u from ..utils import exports from .base import MinimizerBase +from .. import exceptions from . import toplevel @@ -37,27 +38,32 @@ class ScipyMinimizer(MinimizerBase): _TAKES_FTOL = False _TAKES_GTOL = False - def run(self): + def _run(self): import scipy.optimize + if self.mol.constraints and self._METHOD_NAME == 'bfgs': + raise exceptions.NotSupportedError('BFGS minimization does not ' + 'support constrained minimization') + print('Starting geometry optimization: SciPy/%s with %s gradients'%( self._METHOD_NAME, self.gradtype)) options = {'disp': True} if self.nsteps is not None: options['maxiter'] = self.nsteps - if self.gradtype == 'analytical': grad = self.grad + if self.gradtype == 'analytical': + grad = self.grad else: grad = None if self.force_tolerance is not None: if self._TAKES_GTOL: options['gtol'] = self.force_tolerance.defunits().magnitude elif self._TAKES_FTOL: - print ('WARNING: this method does not use force to measure convergence; ' - 'approximating force_tolerance keyword') + print('WARNING: this method does not use force to measure convergence; ' + 'approximating force_tolerance keyword') options['ftol'] = (self.force_tolerance * u.angstrom / 50.0).defunits_value() else: - print ('WARNING: no convergence criteria for this method; using defaults') + print('WARNING: no convergence criteria for this method; using defaults') result = scipy.optimize.minimize(self.objective, self._coords_to_vector(self.mol.positions), @@ -69,6 +75,10 @@ def run(self): self.traj.info = result + finalprops = self._calc_cache[tuple(result.x)] + self.mol.positions = finalprops.positions + self.mol.properties = finalprops + def _make_constraints(self): constraints = [] for constraint in self.mol.constraints: diff --git a/moldesign/min/smart.py b/moldesign/min/smart.py index 77fb5c0..ba88cd6 100644 --- a/moldesign/min/smart.py +++ b/moldesign/min/smart.py @@ -48,37 +48,68 @@ def __init__(self, *args, **kwargs): self.gd_threshold = kwargs.pop('gd_threshold', GDTHRESH) self.args = args self.kwargs = kwargs + self._spmin = None + self._descent = None + self._traj = None + self._currentstep = None + self.__foundmin = None super().__init__(*args, **kwargs) - def run(self): + def _run(self): # If forces are already low, go directly to the quadratic convergence methods and return forces = self.mol.calculate_forces() if abs(forces).max() <= self.gd_threshold: - spmin = self._make_quadratic_method() - spmin.run() - self.traj = spmin.traj - self.current_step = spmin.current_step + self._spmin = self._make_quadratic_method() + self._spmin._run() + self.traj = self._spmin.traj return # Otherwise, remove large forces with gradient descent; exit if we pass the cycle limit descent_kwargs = self.kwargs.copy() descent_kwargs['force_tolerance'] = self.gd_threshold - descender = GradientDescent(*self.args, **descent_kwargs) - descender.run() - if descender.current_step >= self.nsteps: - self.traj = descender.traj + self._descender = GradientDescent(*self.args, **descent_kwargs) + self._descender._run() + if self._descender.current_step >= self.nsteps: + self.traj = self._descender.traj return # Finally, use a quadratic method to converge the optimization - kwargs = dict(_restart_from=descender.current_step, - _restart_energy=descender._initial_energy) + kwargs = dict(_restart_from=self._descender.current_step, + _restart_energy=self._descender._initial_energy) kwargs['frame_interval'] = self.kwargs.get('frame_interval', - descender.frame_interval) - spmin = self._make_quadratic_method(kwargs) - spmin.current_step = descender.current_step - spmin.run() - self.traj = descender.traj + spmin.traj - self.current_step = spmin.current_step + self._descender.frame_interval) + self._spmin = self._make_quadratic_method(kwargs) + self._spmin.current_step = self.current_step + self._spmin._foundmin = self._foundmin + self._spmin._run() + self.traj = self._descender.traj + self._spmin.traj + self.traj.info = getattr(self._spmin, 'info', None) + + @property + def _foundmin(self): + if self._descent: + return self._descent._foundmin + elif self._spmin: + return self._spmin._foundmin + else: + return self.__foundmin + + @_foundmin.setter + def _foundmin(self, val): + self.__foundmin = val + + @property + def currentstep(self): + if self._descent: + return self._descent.currentstep + elif self._spmin: + return self._spmin.currentstep + else: + return self._currentstep + + @currentstep.setter + def currentstep(self, val): + self._currentstep = val def _make_quadratic_method(self, kwargs=None): if kwargs is None: kwargs = {} diff --git a/moldesign/models/base.py b/moldesign/models/base.py index 64a7f8e..cb2a1d7 100644 --- a/moldesign/models/base.py +++ b/moldesign/models/base.py @@ -40,6 +40,8 @@ class EnergyModelBase(Method): PARAMETERS = [] + _CALLS_MDT_IN_DOCKER = False # gets set to true if a python-interfaced dependency is missing + def calculate(self, requests): """Calculate the the default properties and any additiona requests diff --git a/moldesign/models/openbabel.py b/moldesign/models/openbabel.py index cde4a68..cac0d5b 100644 --- a/moldesign/models/openbabel.py +++ b/moldesign/models/openbabel.py @@ -34,6 +34,7 @@ @exports class OpenBabelPotential(EnergyModelBase): + _CALLS_MDT_IN_DOCKER = mdt.interfaces.openbabel.force_remote DEFAULT_PROPERTIES = ['potential_energy', 'forces'] ALL_PROPERTIES = DEFAULT_PROPERTIES PARAMETERS = [mdt.parameters.Parameter(name='forcefield', diff --git a/moldesign/models/openmm.py b/moldesign/models/openmm.py index 8368112..c6ab6e8 100644 --- a/moldesign/models/openmm.py +++ b/moldesign/models/openmm.py @@ -37,6 +37,8 @@ class OpenMMPotential(MMBase, opm.OpenMMPickleMixin): """ # NEWFEATURE: need to set/get platform (and properties, e.g. number of threads) DEFAULT_PROPERTIES = ['potential_energy', 'forces'] + _CALLS_MDT_IN_DOCKER = opm.force_remote + _openmm_compatible = True def __init__(self, **kwargs): diff --git a/moldesign/models/pyscf.py b/moldesign/models/pyscf.py index 83e4326..7b82488 100644 --- a/moldesign/models/pyscf.py +++ b/moldesign/models/pyscf.py @@ -80,6 +80,7 @@ def __iter__(self): @utils.exports class PySCFPotential(QMBase): + _CALLS_MDT_IN_DOCKER = force_remote DEFAULT_PROPERTIES = ['potential_energy', 'wfn', 'mulliken'] diff --git a/moldesign/molecules/atomcollections.py b/moldesign/molecules/atomcollections.py index ea5b83a..381e4c0 100644 --- a/moldesign/molecules/atomcollections.py +++ b/moldesign/molecules/atomcollections.py @@ -103,10 +103,14 @@ def get_atoms(self, *keywords, **queries): atoms = self.atoms + KEYS = 'protein dna rna water unknown ion'.split() + for key in keywords: - if key in 'protein dna rna water unknown': + if key in KEYS: atoms = mdt.AtomList(atom for atom in atoms if atom.residue.type == key) + else: + raise ValueError("Invalid keyword '%s': valid values are %s" % (key, KEYS)) result = mdt.AtomList() for atom in atoms: diff --git a/moldesign/molecules/chain.py b/moldesign/molecules/chain.py index 9048da5..ab555c2 100644 --- a/moldesign/molecules/chain.py +++ b/moldesign/molecules/chain.py @@ -21,11 +21,11 @@ import moldesign as mdt from .. import utils, data -from . import BioContainer, toplevel +from . import BioContainer, toplevel, HasResidues @toplevel -class Chain(BioContainer): +class Chain(BioContainer, HasResidues): """ Biomolecular chain class - its children are almost always residues. Attributes: diff --git a/moldesign/molecules/molecule.py b/moldesign/molecules/molecule.py index 693bb88..3c7d2b5 100644 --- a/moldesign/molecules/molecule.py +++ b/moldesign/molecules/molecule.py @@ -29,7 +29,7 @@ from ..exceptions import NotCalculatedError from ..min.base import MinimizerBase from .properties import MolecularProperties -from . import toplevel, Residue, Chain, Instance, AtomGroup, Bond +from . import toplevel, Residue, Chain, Instance, AtomGroup, Bond, HasResidues from ..helpers import WidgetMethod from .coord_arrays import * @@ -360,11 +360,6 @@ def _biomol_summary_markdown(self): str: Markdown string""" lines = [] if len(self.residues) > 1: - table = self.get_residue_table() - lines.append('### Residues') - # extra '|' here may be workaround for a bug in ipy.markdown? - lines.append(table.markdown(replace={0: ' '})+'|') - lines.append('### Biopolymer chains') seqs = [] for chain in self.chains: @@ -377,13 +372,13 @@ def _biomol_summary_markdown(self): return lines - def get_residue_table(self): # pragma: no cover + def get_residue_table(self): """Creates a data table summarizing this molecule's primary structure. Returns: moldesign.utils.MarkdownTable""" table = utils.MarkdownTable(*(['chain']+ - 'protein dna rna unknown water solvent'.split())) + 'protein dna rna unknown water solvent ion'.split())) for chain in self.chains: counts = {} unk = [] @@ -540,12 +535,19 @@ def _assign_residue_indices(self): num_biores = 0 conflicts = set() - last_pdb_idx = None + pdbatoms = [atom for atom in self.atoms if atom.pdbindex is not None] + if pdbatoms: + min_pdb_atom = min(pdbatoms, key=lambda x:x.pdbindex) + last_pdb_idx = min_pdb_atom.pdbindex - min_pdb_atom.index - 1 + else: + last_pdb_idx = 0 for atom in self.atoms: + if atom.pdbindex is None: + atom.pdbindex = last_pdb_idx + 1 if last_pdb_idx is not None and atom.pdbindex <= last_pdb_idx: atom.pdbindex = last_pdb_idx + 1 - conflicts.add('atom numbers') + conflicts.add('atom indices') last_pdb_idx = atom.pdbindex # if atom has no chain/residue, assign defaults @@ -569,7 +571,7 @@ def _assign_residue_indices(self): atom.chain.name = chr(ord(atom.chain.name)+1) if oldname != atom.chain.name: - conflicts.add('chain') + conflicts.add('chain ids') self.chains.add(atom.chain) else: assert atom.chain.molecule is self @@ -585,7 +587,7 @@ def _assign_residue_indices(self): assert atom.chain.molecule is self if conflicts: - print('WARNING: %s indices modified due to name clashes' % ( + print('WARNING: %s modified due to name clashes' % ( ', '.join(conflicts))) self.is_biomolecule = (num_biores >= 2) @@ -938,7 +940,9 @@ def _reset_methods(self): class Molecule(AtomGroup, MolConstraintMixin, MolPropertyMixin, - MolTopologyMixin, MolSimulationMixin): + MolTopologyMixin, + MolSimulationMixin, + HasResidues): """ ``Molecule`` objects store a molecular system, including atoms, 3D coordinates, molecular properties, biomolecular entities, and other model-specific information. Interfaces with @@ -1138,6 +1142,11 @@ def _repr_markdown_(self): if self.integrator: lines.append('**Integrator**: %s'%str(self.integrator)) + if self.num_residues > 1: + table = self.get_residue_table() + lines.append('### Residues') + lines.append(table.markdown(replace={0: ' '})+'|') # extra '|' is bug workaround (?) + if self.is_biomolecule: lines.extend(self._biomol_summary_markdown()) diff --git a/moldesign/molecules/residue.py b/moldesign/molecules/residue.py index 4e2a171..34683a3 100644 --- a/moldesign/molecules/residue.py +++ b/moldesign/molecules/residue.py @@ -16,6 +16,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from past.builtins import basestring import collections @@ -423,3 +424,42 @@ def is_standard_residue(self): def __str__(self): return 'Residue %s (index %s, chain %s)' % (self.name, self.index, self.chain.name) + + +class HasResidues(object): + """ Mixin for classes that *contain* residues (i.e. Molecules and Chains) + """ + + def get_residues(self, **queries): + """Allows keyword-based residue queries. Returns residues that match ALL queries. + + Args: + **queries (dict): attributes (or residue attributes) to match + + Examples: + >>> mol.get_residues(type='protein') # returns all amino acid residues in molecule + >>> mol.chains['A'].get_residues(resname='ALA') # returns all alanines in chain A + >>> mol.get_residues(chain='A') # all residues in chain A + + + Returns: + List[Residues]: residues matching the query + """ + if not queries: + return list(self.residues) + + result = [] + for res in self.residues: + for field, val in queries.items(): + if field == 'chain' and isinstance(val, basestring): + if res.chain.name != val: + break + else: + continue + + elif getattr(res, field, None) != val: + break + else: + result.append(res) + + return result \ No newline at end of file diff --git a/moldesign/tools/topology.py b/moldesign/tools/topology.py index f52838c..8b1bdea 100644 --- a/moldesign/tools/topology.py +++ b/moldesign/tools/topology.py @@ -25,14 +25,13 @@ from . import toplevel, __all__ as _pkgall -from moldesign.interfaces.openbabel import add_hydrogen, guess_bond_orders, set_protonation -from moldesign.interfaces.pdbfixer_interface import mutate, add_water +from moldesign.interfaces.openbabel import add_hydrogen, guess_bond_orders +from moldesign.interfaces.pdbfixer_interface import mutate_residues, add_water from moldesign.interfaces.ambertools import create_ff_parameters from moldesign.interfaces.ambertools import calc_am1_bcc_charges, calc_gasteiger_charges -_pkgall.extend(('add_hydrogen guess_bond_orders mutate add_water' - ' create_ff_parameters calc_am1_bcc_charges calc_gasteiger_charges ' - 'set_protonation').split()) +_pkgall.extend(('add_hydrogen guess_bond_orders mutate_residues add_water' + ' create_ff_parameters calc_am1_bcc_charges calc_gasteiger_charges ').split()) ATNUM_VALENCE_CHARGE = {6: {3: -1, 4: 0}, 7: {2: -1, 3: 0, 4: 1}, @@ -101,30 +100,6 @@ def assign_formal_charges(mol, ignore_nonzero=True): atom.formal_charge = newcharge * u.q_e -@toplevel -def set_hybridization_and_ph(mol, ph=7.4): - """ Add missing hydrogens, bond orders, and formal charges - - Specifically, this is a convenience function that runs: - ``mdt.guess_bond_orders``, ``mdt.add_hydrogen``, and ``mdt.assign_formal_charges`` - - Note: - This does NOT add missing residues to biochemical structures. This functionality will be - available as :meth:`moldesign.add_missing_residues` - - Args: - mol (moldesign.Molecule): molecule to clean - ph (float): assigned pH. Assign protonation states using the default OpenBabel pKa model - - Returns: - moldesign.Molecule: cleaned version of the molecule - """ - m1 = mdt.guess_bond_orders(mol) - m2 = mdt.add_hydrogen(m1) - m3 = mdt.set_protonation(m2, ph) - return m3 - - @toplevel def set_hybridization_and_saturate(mol): """ Assign bond orders, saturate with hydrogens, and assign formal charges diff --git a/moldesign/units/quantity.py b/moldesign/units/quantity.py index bc91e73..abda408 100644 --- a/moldesign/units/quantity.py +++ b/moldesign/units/quantity.py @@ -83,6 +83,9 @@ def __deepcopy__(self, memo): memo[id(self)] = result return result + def __hash__(self): + return hash((self._magnitude, str(self.units))) + # This doesn't deal with length specs correctly (pint's doesn't either though) #def __format__(self, fmt): # fmtstring = '{m:%s} {u}' % fmt diff --git a/moldesign/units/tools.py b/moldesign/units/tools.py index d040ce1..0cddfa5 100644 --- a/moldesign/units/tools.py +++ b/moldesign/units/tools.py @@ -53,36 +53,6 @@ def from_json(j): return j['value'] * ureg(j['units']) -def units_transfer(from_var, to_var, force=False): - """ Give the "to_var" object the same units as "from_var" - - Args: - from_var (MdtQuantity): use this quantities units - to_var (MdtQuantity): apply units to this quantity - force (bool): Transfer the units even if from_var and to_var have incompatible units - - Returns: - MdtQuantity: to_var with from_var's units - """ - - # If from_var is not a Quantity-like object, return a normal python scalar - if not hasattr(from_var, '_units'): - try: - if to_var.dimensionless or force: return to_var._magnitude - else: raise DimensionalityError(from_var, to_var) - except AttributeError: - return to_var - - # If to_var is a quantity-like object, just perform in-place conversion - try: - return to_var.to(from_var.units) - except AttributeError: - pass - - # If to_var has no units, return a Quantity object - return to_var * from_var.units - - def get_units(q): """ Return the base unit system of an quantity @@ -137,23 +107,6 @@ def array(qlist, baseunit=None): def broadcast_to(arr, *args, **kwargs): units = arr.units newarr = np.zeros(2) * units - # TODO: replace with np.broadcast_to once numpy 1.10 is available in most package managers tmp = np.broadcast_to(arr, *args, **kwargs) newarr._magnitude = tmp return newarr - - -# TODO: remove once numpy 1.10 is available in most package managers -def _maybe_view_as_subclass(original_array, new_array): - """ Temporary copy of a new NumPy function in 1.10, which isn't available in pkg managers yet - """ - if type(original_array) is not type(new_array): - # if input was an ndarray subclass and subclasses were OK, - # then view the result as that subclass. - new_array = new_array.view(type=type(original_array)) - # Since we have done something akin to a view from original_array, we - # should let the subclass finalize (if it has it implemented, i.e., is - # not None). - if new_array.__array_finalize__: - new_array.__array_finalize__(original_array) - return new_array \ No newline at end of file diff --git a/moldesign/utils/_deadfunctions.py b/moldesign/utils/_deadfunctions.py.txt similarity index 84% rename from moldesign/utils/_deadfunctions.py rename to moldesign/utils/_deadfunctions.py.txt index 02f0292..87ef60b 100644 --- a/moldesign/utils/_deadfunctions.py +++ b/moldesign/utils/_deadfunctions.py.txt @@ -1,5 +1,29 @@ -# Graveyard for functions that are currently unused, but that may be useful in the future (so -# we don't want to lose them in the depths of the repository) +# Graveyard for functions that are currently unused and untested, but that may be useful in the +# future. +# +# They're here simply so that they don't get lost in the depths of the repository + + +@compute.runsremotely(enable=force_remote) +def pdbfixer_interface.get_missing_residues(mol): + fixer = mol_to_fixer(mol) + fixer.findMissingResidues() + fixerchains = list(fixer.topology.chains) + + missing = list() + for (chainidx, insertionpoint), reslist in fixer.missingResidues.items(): + chainid = fixerchains[chainidx].id + for ires, resname in enumerate(reslist): + missing.append(mdt.helpers.MissingResidue(chainid, resname, insertionpoint + ires)) + + return missing + + +@compute.runsremotely(enable=force_remote) +def pdbfixer_interface.add_hydrogen(mol, pH=7.4): + fixer = mol_to_fixer(mol) + fixer.addMissingHydrogens(pH) + return fixer_to_mol(fixer) def guess_atnum_from_name(s): diff --git a/moldesign/utils/classes.py b/moldesign/utils/classes.py index 9127a74..664fe0b 100644 --- a/moldesign/utils/classes.py +++ b/moldesign/utils/classes.py @@ -20,6 +20,7 @@ import collections from .descriptors import Alias + class Categorizer(dict): """ Create a dict of lists from an iterable, with dict keys given by keyfn @@ -39,7 +40,7 @@ def add(self, item): class ExclusiveList(object): - """ Behaves like a list, but won't allow duplicate items with duplicate keys to be added. + """ Behaves like a list, but won't allow items with duplicate keys to be added. """ def __init__(self, iterable=None, key=None): self._keys = collections.OrderedDict() diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..c6846c3 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +testpaths = moldesign/_tests + +markers = + base: tests data structures, molecule construction + slow: heavy duty simulation and numerical tests diff --git a/requirements.txt b/requirements.txt index 0d2611b..29b2964 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ numpy >= 1.12 pathlib2 ; python_version < '3.3' parmed >= 2.7.3 pint >= 0.8 -pyccc >= 0.7.7 +pyccc >= 0.7.9 pyyaml requests scipy