diff --git a/charmm-gui-gmx/charmm-gui-gmx.ipynb b/charmm-gui-gmx/charmm-gui-gmx.ipynb index aee2bff..ff01c19 100644 --- a/charmm-gui-gmx/charmm-gui-gmx.ipynb +++ b/charmm-gui-gmx/charmm-gui-gmx.ipynb @@ -31,13 +31,36 @@ "while the MOL2 file serves as a prototype for constructing an `mb.Compound`" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install requirements" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "import mbuild as mb" + "!conda install --file requirements.txt --yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If `gmx` (the `GROMACS` executable) is not found, install it via `conda`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!if ! [ -x \"$(command -v gmx)\" ]; then conda install -c bioconda gromacs; fi" ] }, { @@ -56,6 +79,8 @@ "metadata": {}, "outputs": [], "source": [ + "import mbuild as mb\n", + "\n", "class charmm_ethane(mb.Compound):\n", " def __init__(self):\n", " super(charmm_ethane, self).__init__()\n", @@ -257,7 +282,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.7.6" } }, "nbformat": 4, diff --git a/charmm-gui-gmx/requirements.txt b/charmm-gui-gmx/requirements.txt new file mode 100644 index 0000000..cf75c3e --- /dev/null +++ b/charmm-gui-gmx/requirements.txt @@ -0,0 +1,3 @@ +mbuild +mdtraj +matplotlib diff --git a/smirnoff_omm/requirements.txt b/smirnoff_omm/requirements.txt new file mode 100644 index 0000000..81bbbe2 --- /dev/null +++ b/smirnoff_omm/requirements.txt @@ -0,0 +1,6 @@ +mbuild +foyer +parmed +openforcefield<0.6 +openmm +mdtraj diff --git a/smirnoff_omm/smirnoff.ipynb b/smirnoff_omm/smirnoff.ipynb index e61f74f..fb47332 100644 --- a/smirnoff_omm/smirnoff.ipynb +++ b/smirnoff_omm/smirnoff.ipynb @@ -4,22 +4,34 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## You will need\n", - "Openforcefield\n", - "`conda install -c omnia openforcefield`\n", - "\n", - "Smirnoff FF\n", - "`conda install -c omnia smirnoff99frosst`\n", - "(You may also need to clone [the smirnoff repo](https://github.com/openforcefield/smirnoff99Frosst) and pip install it to get the setuptools entry points to work)\n", - "\n", - "Also: mbuild, foyer, parmed, openmm, mdtraj, nglview" + "## Install requirements" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!conda config --add channels omnia && \\\n", + "conda config --add channels mosdef && \\\n", + "conda config --add channels conda-forge" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!conda install --file requirements.txt --yes && conda install -c omnia smirnoff99frosst --yes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Building the our molecular system and model\n", + "## Build the molecular system and model\n", "We begin with some imports. We can already see a variety of packages being used: mBuild, Foyer, ParmEd, OpenForceField, Simtk, OpenMM, MDTraj, and NGLView. \n", "\n", "Take note of all the different data structure interconversions happening. There are *a lot*. This is good that we can get these API working together this often, but maybe not-so-good that we have to do these interconversions so often\n", @@ -29,43 +41,12 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Warning: Unable to load toolkit 'OpenEye Toolkit'. The Open Force Field Toolkit does not require the OpenEye Toolkits, and can use RDKit/AmberTools instead. However, if you have a valid license for the OpenEye Toolkits, consider installing them for faster performance and additional file format support: https://docs.eyesopen.com/toolkits/python/quickstart-python/linuxosx.html OpenEye offers free Toolkit licenses for academics: https://www.eyesopen.com/academic-licensing\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "eb82828e746746eb939f798763e64d0c", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "_ColormakerRegistry()" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/ayang41/anaconda3/envs/mosdef37/lib/python3.7/site-packages/nglview/widget.py:162: DeprecationWarning: Traits should be given as instances, not types (for example, `Int()`, not `Int`). Passing types is deprecated in traitlets 4.1.\n", - " _ngl_view_id = List(Unicode).tag(sync=True)\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# MoSDeF tools for initializing and parametrizing systems\n", - "import mbuild\n", - "from mbuild.examples import Ethane\n", + "import mbuild as mb\n", "import foyer\n", "\n", "# ParmEd for interconverting data structures\n", @@ -86,43 +67,18 @@ "metadata": {}, "source": [ "We will use mBuild to create a generic Ethane ($C_2H_6$) molecule. \n", - "While this is imported from the examples, mBuild functionality allows users to construct chemical systems in a lego-like fashion by declaring particles and bonding them. \n", + "While this is imported from Open Babel via a SMILES string, mBuild functionality allows users to construct chemical systems in a lego-like fashion by declaring particles and bonding them. \n", "Under the hood, rigid transformations are performed to orient particles-to-be-bonded" ] }, { "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/ayang41/Programs/mbuild/mbuild/utils/io.py:120: DeprecationWarning: openbabel 2.0 detected and will be dropped in a future release. Consider upgrading to 3.x.\n", - " warnings.warn(msg, DeprecationWarning)\n", - "/Users/ayang41/Programs/mbuild/mbuild/utils/io.py:120: DeprecationWarning: openbabel 2.0 detected and will be dropped in a future release. Consider upgrading to 3.x.\n", - " warnings.warn(msg, DeprecationWarning)\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "939b956d2c20446ca4dd9922b5eb39e8", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "NGLWidget()" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "mbuild_compound = Ethane()\n", - "mbuild_compound.visualize(backend='nglview')" + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mbuild_compound = mb.load('CC', smiles=True)\n", + "mbuild_compound.visualize(backend='py3dmol')" ] }, { @@ -135,25 +91,9 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['CC\\tEthane\\n']\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/ayang41/Programs/mbuild/mbuild/utils/io.py:120: DeprecationWarning: openbabel 2.0 detected and will be dropped in a future release. Consider upgrading to 3.x.\n", - " warnings.warn(msg, DeprecationWarning)\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "ethane_obmol = mbuild_compound.to_pybel()\n", "ethane_obmol.write(\"smi\", 'out.smi', overwrite=True)\n", @@ -172,18 +112,9 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "[Vec3(x=2.819666989525475e-16, y=-1.4, z=-1.4644271506889933e-16), Vec3(x=-1.0699999332427972, y=-1.4000000000000001, z=-6.273541601638111e-17), Vec3(x=0.3570000827312472, y=-2.169000053405761, z=0.6530000269412993), Vec3(x=0.3570000827312474, y=-1.5810000836849212, z=-0.9929999709129338), Vec3(x=0.0, y=0.0, z=0.0), Vec3(x=1.0699999332427979, y=0.0, z=0.0), Vec3(x=-0.35700008273124695, y=0.7690000534057617, z=0.6530000269412994), Vec3(x=-0.35700008273124695, y=0.18100008368492126, z=-0.9929999709129333)] A\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "omm_topology, xyz = foyer.forcefield.generate_topology(mbuild_compound, residues='Ethane')\n", "print(omm_topology)\n", @@ -201,20 +132,9 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Molecule with name '' and SMILES '[H][C]([H])([H])[C]([H])([H])[H]'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "ethane_molecule = Molecule.from_smiles(smiles_string[0].split()[0])\n", "ethane_molecule" @@ -229,20 +149,9 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "off_topology = Topology.from_openmm(omm_topology, unique_molecules=[ethane_molecule])\n", "off_topology" @@ -269,22 +178,11 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#off_forcefield = ForceField('test_forcefields/smirnoff99Frosst.offxml')\n", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "off_forcefield = ForceField('test_forcefields/smirnoff99Frosst.offxml')\n", "off_forcefield = ForceField('smirnoff99Frosst-1.1.0.offxml')\n", "off_forcefield" ] @@ -301,30 +199,11 @@ }, { "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Warning: In AmberToolsToolkitwrapper.compute_partial_charges_am1bcc: Molecule '' has more than one conformer, but this function will only generate charges for the first one.\n" - ] - }, - { - "data": { - "text/plain": [ - " >" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "smirnoff_omm_system = off_forcefield.create_openmm_system(off_topology)\n", - "smirnoff_omm_system" + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "smirnoff_omm_system = off_forcefield.create_openmm_system(off_topology)" ] }, { @@ -337,7 +216,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -380,20 +259,9 @@ }, { "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Quantity(value=44.96860291382222, unit=kilocalorie/mole)" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "new_vectors = [[10*unit.nanometer, 0*unit.nanometer, 0*unit.nanometer], \n", " [0*unit.nanometer, 10*unit.nanometer, 0*unit.nanometer],\n", @@ -414,20 +282,9 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "pmd_structure = parmed.openmm.load_topology(omm_topology, system=smirnoff_omm_system, xyz=xyz)\n", "pmd_structure" @@ -451,30 +308,9 @@ }, { "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/ayang41/Programs/foyer/foyer/validator.py:132: ValidationWarning: You have empty smart definition(s)\n", - " warn(\"You have empty smart definition(s)\", ValidationWarning)\n", - "/Users/ayang41/Programs/foyer/foyer/forcefield.py:248: UserWarning: Parameters have not been assigned to all impropers. Total system impropers: 8, Parameterized impropers: 0. Note that if your system contains torsions of Ryckaert-Bellemans functional form, all of these torsions are processed as propers\n", - " warnings.warn(msg)\n" - ] - }, - { - "data": { - "text/plain": [ - "Quantity(value=37.52734328319192, unit=kilocalorie/mole)" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "foyer_ff = foyer.Forcefield(name='oplsaa')\n", "opls_pmd_structure = foyer_ff.apply(mbuild_compound)\n", @@ -514,23 +350,9 @@ }, { "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[ >,\n", - " >,\n", - " >,\n", - " >]" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "smirnoff_omm_system.getForces()" ] @@ -545,24 +367,9 @@ }, { "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[ >,\n", - " >,\n", - " >,\n", - " >,\n", - " >]" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "opls_omm_system.getForces()" ] @@ -589,40 +396,9 @@ }, { "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[Quantity(value=-0.18, unit=elementary charge), Quantity(value=0.35000000000000003, unit=nanometer), Quantity(value=0.276144, unit=kilojoule/mole)]\n", - "[Quantity(value=-0.0941, unit=elementary charge), Quantity(value=0.3399669508423535, unit=nanometer), Quantity(value=0.4577296, unit=kilojoule/mole)]\n", - "---\n", - "[Quantity(value=0.06, unit=elementary charge), Quantity(value=0.25, unit=nanometer), Quantity(value=0.12552, unit=kilojoule/mole)]\n", - "[Quantity(value=0.0317, unit=elementary charge), Quantity(value=0.2649532787749369, unit=nanometer), Quantity(value=0.06568879999999999, unit=kilojoule/mole)]\n", - "---\n", - "[Quantity(value=0.06, unit=elementary charge), Quantity(value=0.25, unit=nanometer), Quantity(value=0.12552, unit=kilojoule/mole)]\n", - "[Quantity(value=0.0317, unit=elementary charge), Quantity(value=0.2649532787749369, unit=nanometer), Quantity(value=0.06568879999999999, unit=kilojoule/mole)]\n", - "---\n", - "[Quantity(value=0.06, unit=elementary charge), Quantity(value=0.25, unit=nanometer), Quantity(value=0.12552, unit=kilojoule/mole)]\n", - "[Quantity(value=0.0317, unit=elementary charge), Quantity(value=0.2649532787749369, unit=nanometer), Quantity(value=0.06568879999999999, unit=kilojoule/mole)]\n", - "---\n", - "[Quantity(value=-0.18, unit=elementary charge), Quantity(value=0.35000000000000003, unit=nanometer), Quantity(value=0.276144, unit=kilojoule/mole)]\n", - "[Quantity(value=-0.0941, unit=elementary charge), Quantity(value=0.3399669508423535, unit=nanometer), Quantity(value=0.4577296, unit=kilojoule/mole)]\n", - "---\n", - "[Quantity(value=0.06, unit=elementary charge), Quantity(value=0.25, unit=nanometer), Quantity(value=0.12552, unit=kilojoule/mole)]\n", - "[Quantity(value=0.0317, unit=elementary charge), Quantity(value=0.2649532787749369, unit=nanometer), Quantity(value=0.06568879999999999, unit=kilojoule/mole)]\n", - "---\n", - "[Quantity(value=0.06, unit=elementary charge), Quantity(value=0.25, unit=nanometer), Quantity(value=0.12552, unit=kilojoule/mole)]\n", - "[Quantity(value=0.0317, unit=elementary charge), Quantity(value=0.2649532787749369, unit=nanometer), Quantity(value=0.06568879999999999, unit=kilojoule/mole)]\n", - "---\n", - "[Quantity(value=0.06, unit=elementary charge), Quantity(value=0.25, unit=nanometer), Quantity(value=0.12552, unit=kilojoule/mole)]\n", - "[Quantity(value=0.0317, unit=elementary charge), Quantity(value=0.2649532787749369, unit=nanometer), Quantity(value=0.06568879999999999, unit=kilojoule/mole)]\n", - "---\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "opls_omm_nonbond_force = opls_omm_system.getForce(3)\n", "smirnoff_omm_nonbond_force = smirnoff_omm_system.getForce(0)\n", @@ -654,7 +430,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -674,7 +450,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -696,7 +472,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -714,7 +490,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -730,24 +506,9 @@ }, { "cell_type": "code", - "execution_count": 38, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "e9a89ab7cb8f453ea70be365b2f3e427", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "NGLWidget(max_frame=109)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "traj = mdtraj.load('trajectory.dcd', top='first_frame.pdb')\n", "nglview.show_mdtraj(traj)" @@ -770,7 +531,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.7.6" } }, "nbformat": 4, diff --git a/water-box/requirements.txt b/water-box/requirements.txt new file mode 100644 index 0000000..9e6993b --- /dev/null +++ b/water-box/requirements.txt @@ -0,0 +1,6 @@ +jupyter +numpy +mbuild +foyer +mdtraj +matplotlib diff --git a/water-box/water-box.ipynb b/water-box/water-box.ipynb index 6bacec0..44aa26d 100644 --- a/water-box/water-box.ipynb +++ b/water-box/water-box.ipynb @@ -1,13 +1,63 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Water box\n", + "\n", + "This workflow demonstrates how to use MoSDeF to prepare, run, and briefly analyze an MD simulation of water" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install requirements" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!conda install --file requirements.txt --yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If `gmx` (the `GROMACS` executable) is not found, install it via `conda`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!if ! [ -x \"$(command -v gmx)\" ]; then conda install -c bioconda gromacs; fi" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "import mbuild as mb\n", - "from foyer import Forcefield" + "import mbuild\n", + "import foyer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preparing the system coordinates\n", + "\n", + "After importing our libraries, can load in a water molecule from disk. Then, the packing function `fill_box` calls `PACKMOL` under the hood to place copies of this molecule in a cubic box." ] }, { @@ -33,6 +83,15 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Applying the force field\n", + "\n", + "We use `Foyer` to load the SPC/E force field, in XML format, and apply it to our system." + ] + }, { "cell_type": "code", "execution_count": null, @@ -43,6 +102,13 @@ "system = spce.apply(water_box.to_parmed(residues=['water']))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we save out two of the files we need to run a `GROMACS` simulation. The GRO file stores atomic coordinates and the TOP file stores force field information." + ] + }, { "cell_type": "code", "execution_count": null, @@ -53,6 +119,15 @@ "system.save('system.top', combine='all')" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running the simulation\n", + "\n", + "We need to call `grompp` to prepare the simulation and then run it with `mdrun`" + ] + }, { "cell_type": "code", "execution_count": null, @@ -65,12 +140,23 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [], "source": [ "!gmx mdrun -v -deffnm npt" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Analyzing the simulation\n", + "\n", + "Here we use `MDTraj` to load trajectory data from disk and make a quick plot of the density over time compared to reference data." + ] + }, { "cell_type": "code", "execution_count": null, @@ -80,7 +166,7 @@ "import mdtraj as md\n", "import numpy as np\n", "\n", - "trj = md.load('nvt.xtc', top='system.gro')" + "trj = md.load('npt.xtc', top='system.gro')" ] }, { @@ -91,7 +177,7 @@ "source": [ "rho = md.density(trj)\n", "\n", - "t_ref, rho_ref = np.loadtxt('ref_data/rho.txt')" + "rho_ref = np.loadtxt('ref_data/rho.txt')" ] }, { @@ -109,8 +195,13 @@ "metadata": {}, "outputs": [], "source": [ - "plt.plot(trj.time, rho, 'b-')\n", - "plt.plot(t_ref, rho_ref, 'k-')" + "fig, ax = plt.subplots()\n", + "\n", + "ax.plot(trj.time, rho, 'b-', label='Data')\n", + "ax.plot(rho_ref[:, 0], rho_ref[:, 1], 'k-', label='Reference')\n", + "ax.legend()\n", + "ax.set_xlabel('Simulation time, ps')\n", + "ax.set_ylabel('System density, kg/m^3')" ] } ], @@ -130,7 +221,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.7.6" } }, "nbformat": 4,