{ "cells": [ { "cell_type": "markdown", "metadata": { "jp-MarkdownHeadingCollapsed": true, "tags": [] }, "source": [ "# notes\n", "\n", "- I can not use dispersion correction, because gromacs takes C6 and i set it to 1 for the tabulated interactions\n", " - actually I could, by adjusting ε and the table with C from Buckingham fit, but is complicated (to complicated for paper i think)" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "# setup" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "## import stuff" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "tags": [] }, "outputs": [], "source": [ "# magics\n", "%matplotlib inline\n", "\n", "# regulary used\n", "from copy import deepcopy\n", "import collections\n", "import glob\n", "import importlib\n", "import itertools\n", "import json\n", "import matplotlib as mpl\n", "import matplotlib.pyplot as plt\n", "from mpl_toolkits.axes_grid1.inset_locator import inset_axes\n", "import numpy as np\n", "import operator\n", "import os\n", "import pandas as pd\n", "from pathlib import Path\n", "import pickle\n", "import random\n", "import re\n", "from scipy import constants as const\n", "from scipy import integrate\n", "from scipy import ndimage\n", "from scipy import optimize\n", "from scipy import signal\n", "import string\n", "import subprocess\n", "import sys\n", "import tempfile\n", "import xml.etree.ElementTree as ET\n", "\n", "# from own toolbox\n", "import gromacstools as gt\n", "run_bash = gt.general.run_bash\n", "WorkingDir = gt.general.WorkingDir\n", "\n", "# own constants\n", "class oconst: pass\n", "oconst.k_gro = const.k * const.N_A * 1e-3\n", "oconst.h_gro = const.h * const.N_A * 1e-3 * 1e12\n", "oconst.f_gro = 138.935458 # electric conversion factor: V = f q²/r\n", "oconst.e_gro = 1.0\n", "oconst.epsilon_0_gro = const.epsilon_0 * const.N_A * 1e3\n", "oconst.rec_cm_per_THz = 1e12 / const.c / 100\n", "oconst.bar_per_md_pressure = 10**28 * const.u\n", "\n", "# notebook wide variables\n", "jobids = []" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class OrderedSet(collections.abc.Set):\n", " def __init__(self, iterable=()):\n", " self.d = collections.OrderedDict.fromkeys(iterable)\n", "\n", " def __len__(self):\n", " return len(self.d)\n", "\n", " def __contains__(self, element):\n", " return element in self.d\n", "\n", " def __iter__(self):\n", " return iter(self.d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## often used helper functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def check_job_stati(jobids, remote_host):\n", " stati = gt.remote.check_slurm_jobs(jobids, remote_host)\n", "\n", " # print status for each job\n", " completed_jobids = []\n", " for jobid, status in zip(jobids, stati):\n", " print(jobid, status)\n", " if (status in ('FAILED', 'COMPLETED', 'OUT_OF_MEMORY', None)\n", " or status.startswith('CANCELLED')):\n", " completed_jobids.append(jobid)\n", "\n", " # remove jobids which are completed\n", " new_jobids = [jobid for jobid in jobids if jobid not in completed_jobids]\n", " return new_jobids" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def indent(elem, level=0):\n", " \"\"\"indent xml\"\"\"\n", " i = \"\\n\" + level*\" \"\n", " if len(elem):\n", " if not elem.text or not elem.text.strip():\n", " elem.text = i + \" \"\n", " if not elem.tail or not elem.tail.strip():\n", " elem.tail = i\n", " for elem in elem:\n", " indent(elem, level+1)\n", " if not elem.tail or not elem.tail.strip():\n", " elem.tail = i\n", " else:\n", " if level and (not elem.tail or not elem.tail.strip()):\n", " elem.tail = i" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def gen_potential_and_force(at1, at2, r, combining_rule, add_coulomb, nonbond_params, repulsive_only=False, coulomb_only=False):\n", " \n", " if not coulomb_only:\n", " if frozenset((at1['type'], at2['type'])) in nonbond_params:\n", " nb = nonbond_params[frozenset((at1['type'], at2['type']))]\n", " if nb[0] == 'LJ':\n", " sig, eps = nb[1:3]\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = (sig/r)**12\n", " if not repulsive_only:\n", " pot -= (sig/r)**6\n", " pot *= 4 * eps\n", " else:\n", " raise Exception('Not implemented')\n", " elif combining_rule == 'geometric':\n", " C12 = (at1['C12'] * at2['C12'])**(1/2)\n", " C6 = (at1['C6'] * at2['C6'])**(1/2)\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = C12 / r**12\n", " if not repulsive_only:\n", " pot -= C6 / r**6\n", " elif combining_rule == 'lorentz-berthelot':\n", " sig = 1/2 * (at1['σ'] + at2['σ'])\n", " eps = (at1['ε'] * at2['ε'])**(1/2)\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = (sig/r)**12\n", " if not repulsive_only:\n", " pot -= (sig/r)**6\n", " pot *= 4 * eps\n", " else:\n", " raise Exception('Not implemented')\n", " else:\n", " pot = np.zeros_like(r)\n", " \n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " if add_coulomb or coulomb_only:\n", " pot += oconst.f_gro * at1['q'] * at2['q'] / r\n", " force = -1/2 * (np.diff(pot, prepend=0) + np.diff(pot, append=0)) / (r[1] - r[0])\n", " #f_max = force[np.argmax(np.where(pot > 1e6))]\n", " f_max = 0 # physical for constant potential\n", " force[pot > 1e6] = f_max\n", " pot[pot > 1e6] = 1e6\n", " pot = np.nan_to_num(pot, nan=1e6)\n", " force = np.nan_to_num(force, nan=f_max)\n", " return pot, force" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def decombinate_LJ(sigma_ij, epsilon_ij, sigma_ii, epsilon_ii, combining_rule):\n", " if combining_rule == 'geometric':\n", " # epsilon_ij = (epsilon_ii * epsilon_jj)^(1/2)\n", " epsilon_jj = epsilon_ij**2 / epsilon_ii\n", " sigma_jj = sigma_ij**2 / sigma_ii\n", " elif combining_rule == 'lorentz-berthelot':\n", " epsilon_jj = epsilon_ij**2 / epsilon_ii\n", " # sigma_ij = (sigma_ii + sigma_jj) / 2\n", " sigma_jj = 2*sigma_ij - sigma_ii\n", " else:\n", " raise Exception('Not implemented')\n", " \n", " return sigma_jj, epsilon_jj" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def save_parametric_force_field_as_top(filename, ff, system_name, moltypes, osm_restraints={}):\n", " parametric_ff = PARAMETRIC_FORCE_FIELDS[ff['parametric-ff']]\n", " file_content = \"\"\n", " file_content += \"[ defaults ]\\n\"\n", " file_content += \";nbfunc comb-rule gen-pairs fudgeLJ fudgeQQ\\n\"\n", " nbfunc_dict = {'LJ': '1'}\n", " comb_rule_dict = {'lorentz-berthelot': '2', 'geometric': '3'}\n", " nbfunc = nbfunc_dict[parametric_ff['nbfunc']]\n", " comb_rule = comb_rule_dict[parametric_ff['combining-rule']]\n", " gen_pairs = parametric_ff['gen-pairs']\n", " fudgeLJ = parametric_ff['fudgeLJ']\n", " fudgeQQ = parametric_ff['fudgeQQ']\n", " file_content += f\"{nbfunc} {comb_rule} {gen_pairs} {fudgeLJ} {fudgeQQ}\\n\"\n", " file_content += \"\\n\"\n", " \n", " # atomtypes\n", " mass_dict = {\n", " 'OW': 15.9994,\n", " 'HW': 1.008,\n", " 'MW': 0.0,\n", " 'CA': 40.08,\n", " 'K': 39.0983,\n", " 'LI': 6.941,\n", " 'NA': 22.98977,\n", " 'CL': 35.45300,\n", " }\n", " ptype_dict = {\n", " 'OW': 'A',\n", " 'HW': 'A',\n", " 'MW': 'D',\n", " 'CA': 'A',\n", " 'K': 'A',\n", " 'LI': 'A',\n", " 'NA': 'A',\n", " 'CL': 'A',\n", " }\n", " file_content += \"[ atomtypes ]\\n\"\n", " file_content += \";name mass charge ptype sigma epsilon\\n\"\n", " for at in parametric_ff['atomtypes']:\n", " file_content += f\"{at['type']:5s} {mass_dict[at['type']]:8.5f} {at['q']:7.4f} {ptype_dict[at['type']]:4s} {at['σ']:8.6f} {at['ε']:8.6f}\\n\"\n", " file_content += \"\\n\"\n", " \n", " # nonbond_params\n", " file_content += \"[ nonbond_params ]\\n\"\n", " file_content += \";i j func sigma epsilon\\n\"\n", " nb_dict2 = {frozenset(pair): ('LJ', 1, 0.25) for pair in ff.get('tabulated-potentials', [])}\n", " for nb_set, nb in {**nb_dict2, **parametric_ff['nonbond-params']}.items():\n", " at1, at2 = sorted(list(nb_set))\n", " if nb[0] == 'LJ':\n", " file_content += f\"{at1:4s} {at2:4s} 1 {nb[1]:10.8f} {nb[2]:10.8f}\\n\"\n", " else:\n", " raise Exception('not implemented')\n", " file_content += \"\\n\"\n", " \n", " # moleculetypes\n", " mt_names_done = []\n", " for mt_name, mt in ((mt['name'], mt) for mt in moltypes):\n", " if mt_name in mt_names_done:\n", " continue\n", " if mt_name == 'SOL':\n", " itp_file = os.path.join(template_dir, 'itp', parametric_ff['water-model'] + '.itp')\n", " with open(itp_file, 'r') as f:\n", " file_content += f.read()\n", " else:\n", " if len(mt['atoms']) == 1:\n", " file_content += \"[ moleculetype ]\\n\"\n", " file_content += \";molname nrexcl\\n\"\n", " file_content += f\"{mt_name:8s} 1\\n\"\n", " file_content += \"\\n\"\n", " file_content += \"[ atoms ]\\n\"\n", " file_content += \";id at_type res_nr residu_name at_name cg_nr\\n\"\n", " file_content += f\"1 {mt_name:7s} 1 {mt_name:7s} {mt_name:7s} 1\\n\"\n", " if mt_name in osm_restraints:\n", " osm_res = osm_restraints[mt_name]\n", " file_content += \"\\n\"\n", " file_content += \"[ position_restraints ]\\n\"\n", " file_content += \";ai funct g r k\\n\"\n", " file_content += f\"{osm_res['ai']} {osm_res['funct']} {osm_res['g']} {osm_res['r']} {osm_res['k']}\\n\"\n", " else:\n", " raise Exception('not implemented for non single atom moltypes')\n", " file_content += \"\\n\"\n", " mt_names_done.append(mt_name)\n", " \n", " file_content += \"[ system ]\\n\"\n", " file_content += f\"{system_name}\\n\"\n", " file_content += \"\\n\"\n", " \n", " file_content += \"[ molecules ]\\n\"\n", " for mt in moltypes:\n", " file_content += f\"{mt['name']} {mt['nmols']}\\n\"\n", " \n", " with open(filename, 'w') as f:\n", " f.write(file_content)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def show_energy_graphs(properties_list, edr_file=\"ener.edr\"):\n", " run_bash(\"gmx energy -f {} -o energy-temp.xvg <<< '{}'\".format(edr_file, \"\\n\".join(properties_list)))\n", " gt.xvg.plot(\"energy-temp.xvg\")\n", " run_bash(\"rm energy-temp.xvg\")\n", " \n", "def _exp_decay(x, a, b, c):\n", " return a * np.exp(-b*x) + c\n", "\n", "def check_equi(properties_list, edr_file=\"ener.edr\", safe_factor=3):\n", " run_bash(\"gmx energy -f {} -o energy-temp.xvg <<< '{}'\".format(edr_file, \"\\n\".join(properties_list)))\n", " data, _ = gt.xvg.load(\"energy-temp.xvg\")\n", " run_bash(\"rm energy-temp.xvg\")\n", " for prop in properties_list:\n", " x = data['Time (ps)']\n", " y = data[prop]\n", " p0 = (y[0]-np.mean(y), 0.1, np.mean(y))\n", " try:\n", " popt, pcov = optimize.curve_fit(_exp_decay, x, y, p0=p0)\n", " except:\n", " print('..fit unsuccessful, continuing..')\n", " continue\n", " lifetime = 1 / popt[1]\n", " a = popt[0]\n", " if (safe_factor * lifetime > max(x)) and (abs(a) / np.std(y) > 2):\n", " print('abs(a) / std(y):', abs(a) / np.std(y), '(should be small)')\n", " plt.plot(x, y)\n", " plt.plot(x, _exp_decay(x, *popt))\n", " plt.show()\n", " raise Exception('looks unequilibrated')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def show_energy_graphs(properties_list, edr_file=\"ener.edr\"):\n", " run_bash(\"gmx energy -f {} -o energy-temp.xvg <<< '{}'\".format(edr_file, \"\\n\".join(properties_list)))\n", " gt.xvg.plot(\"energy-temp.xvg\")\n", " run_bash(\"rm energy-temp.xvg\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def readin_table(filename):\n", " data = np.loadtxt(filename, dtype=str, comments=['#', '@'])\n", " x = data[:, 0].astype(float)\n", " y = data[:, 1].astype(float)\n", " try:\n", " y_flag = data[:, 2].astype('S1')\n", " except:\n", " y_flag = [''] * len(x)\n", " return x, y, y_flag" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def test_files_same(file1, file2, exclude_lines=('#.*',), compare_numpy=False):\n", " assert type(exclude_lines) in (tuple, list)\n", " if compare_numpy:\n", " return np.allclose(np.loadtxt(file1), np.loadtxt(file2), rtol=1e-2) # 1% derivation\n", " else:\n", " exclude_args = ' '.join((f\"-I '{ex}'\" for ex in exclude_lines))\n", " return run_bash(f\"if diff -q {exclude_args} {file1} {file2} &>/dev/null; then echo -n True; else echo -n False; fi\") == 'True'\n", "\n", "\n", "def skip_or_overwrite(source, dest, overwrite, compare_numpy=False):\n", " \"\"\"skips when dest and source have same content\n", " or when overwrite is False and content is different\n", " returns True when we skip\"\"\"\n", " if os.path.isfile(dest):\n", " if test_files_same(source, dest, exclude_lines=('#.*', '%.*'), compare_numpy=compare_numpy):\n", " #print(f\".. files {source} and {dest} have same content ..\")\n", " print(f\".. files have same content ..\")\n", " print(f\".. skipping ..\")\n", " return True\n", " else:\n", " #print(f\".. files {source} and {dest} have different content ..\")\n", " print(f\".. files have different content ..\")\n", " if overwrite:\n", " print(f\".. backing up, overwriting ..\")\n", " run_bash(f\"cp {dest} {dest}-backup-{''.join(random.choices(string.ascii_letters, k=10))}\")\n", " run_bash(f\"cp {source} {dest}\")\n", " return False\n", " else:\n", " print(f\".. doing nothing ..\")\n", " return True\n", " else:\n", " print(f\".. file {dest} not present, copying ..\")\n", " run_bash(f\"cp {source} {dest}\")\n", " return False" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## load iterative integral equations module" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sys.path.append(\"/home/marvin/research/code/votca/csg/share/scripts/inverse\")\n", "import iie" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## notebook specific constants" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "working_dir_base = os.path.realpath('.')\n", "template_dir = os.path.join(os.getcwd(), 'template')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## remote computing resource" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def gen_remote_stuff(remote_host, remote_partition, ntasks='exclusive', votca=False, **kwargs):\n", " remote_dir_base = os.path.join(\"/home/mbernhardt/run/projects/yuki-azade-collab/simulations\")\n", "\n", " remote_gromacs_module = {\n", " #'enzogpu': 'gromacs-2019-gcc-7.3.0-yfcl45y # gromacs@2019 build_type=RelWithDebInfo +cuda~double~mpi~plumed+rdtscp+shared simd=AVX2_256',\n", " 'enzogpu': 'gromacs-2021.5-gcc-10.2.0-7muu47b # gromacs@2021.5~blas+cuda~cycle_subcounters~double+hwloc~ipo~lapack~mdrun_only~mpi~nosuffix~opencl+openmp~plumed~relaxed_double_precision+shared~sycl build_type=RelWithDebInfo',\n", " 'mammut-b': 'gromacs-2019.6-gcc-10.2.0-c5rae2n',\n", " 'mammut-c': 'gromacs-2019.3-gcc-8.2.0-2skeqyg # gromacs@2019.3 build_type=RelWithDebInfo ~cuda~double~mpi~plumed+rdtscp+shared simd=AVX_256',\n", " 'enzo': 'gromacs-2019.5-gcc-8.2.0-qmpnnzb # gromacs@2019.5 build_type=RelWithDebInfo ~cuda~double~mpi~plumed+rdtscp+shared simd=SSE2',\n", " 'biby': 'gromacs-2019.3-gcc-8.2.0-rf37djz # gromacs@2019.3 build_type=RelWithDebInfo ~cuda~double~mpi~plumed~rdtscp+shared simd=SSE2',\n", " }\n", " sbatch_arguments = [\n", " '--job-name=ions',\n", " f'--partition={remote_partition}',\n", " '--exclusive' if ntasks == 'exclusive' else f\"--ntasks={ntasks}\",\n", " '--time=96:00:00',\n", " ]\n", " for key, arg in kwargs.items():\n", " if arg is not None:\n", " sbatch_arguments.append(f\"--{key.replace('_', '-')}={arg}\")\n", " sbatch_arguments_section = '\\n'.join((f'#SBATCH {arg}' for arg in sbatch_arguments))\n", " remote_header = f\"\"\"#!/bin/sh\n", "{sbatch_arguments_section}\n", "\n", "set -eo pipefail # -u does not work with VOTCA and/or conda?\n", "\n", "MODULEPATH=$MODULEPATH:/shared/spack/share/spack/modules/linux-fedora29-x86_64/\n", "MODULEPATH=$MODULEPATH:/shared/spack/share/spack/modules/linux-fedora33-bulldozer/\n", "MODULEPATH=$MODULEPATH:/shared/spack/share/spack/modules/linux-fedora33-x86_64/\n", "module purge\n", "module load {remote_gromacs_module[remote_partition]}\n", "\"\"\"\n", " if votca:\n", " remote_header += \"\"\"\n", "spack load /e4wa6u5 # fftw@3.3.8~mpi~openmp~pfft_patches precision=double,float\n", "spack load /p2rbc3m # boost\n", "spack load /dthwmyc # netlib-lapack@3.8.0\n", "source /home/mbernhardt/software/votca/bin/VOTCARC.bash\n", "\"\"\"\n", " remote_header += \"\"\"\n", "source ~/software/miniconda3/etc/profile.d/conda.sh\n", "conda activate\n", "export PYTHONPATH=$PYTHONPATH:~/software/gromacstools\n", "\"\"\"\n", " remote_footer = ''\n", " return remote_dir_base, remote_header, remote_footer\n", "\n", "remote_host = 'franklin'\n", "def test():\n", " remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'enzo', ntasks=24, mem_per_cpu='1000M')\n", " #remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'enzo', votca=False)\n", " print(remote_header)\n", " \n", "test()\n", "remote_dir_base, _, _ = gen_remote_stuff(remote_host, 'enzo', ntasks=24, mem_per_cpu='1000M')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## simulation set up stuff" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### parametric force fields" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# tip4p/2005 water ow epsilon\n", "93.2 * const.k * const.N_A / 1000" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# fudge values are not set, but not important for the systems in this notebook\n", "\n", "PARAMETRIC_FORCE_FIELDS = {}\n", "\n", "# Aquist model with spc water and cl- from smith and dang\n", "# cl- from table 3 in chen2007 or \n", "# Amber and opls both use it for cations? \n", "# not used here, since effectively the same as OPLS\n", "\n", "PARAMETRIC_FORCE_FIELDS['opls-q1.0'] = {\n", " 'atomtypes': [\n", " {'type': 'OW', 'q': -0.8476, 'σ': 3.16557e-01, 'ε': 6.50194e-01},\n", " {'type': 'HW', 'q': +0.4238, 'σ': 0.00000e+00, 'ε': 0.00000e+00},\n", " {'type': 'CA', 'q': +2.0000, 'σ': 2.41203e-01, 'ε': 1.88136e+00},\n", " {'type': 'K', 'q': +1.0000, 'σ': 4.93463e-01, 'ε': 1.37235e-03},\n", " {'type': 'LI', 'q': +1.0000, 'σ': 2.12645e-01, 'ε': 7.64793e-02},\n", " {'type': 'NA', 'q': +1.0000, 'σ': 3.33045e-01, 'ε': 1.15980e-02},\n", " {'type': 'CL', 'q': -1.0000, 'σ': 4.41724e-01, 'ε': 4.92833e-01},\n", " ],\n", " 'scalings': {},\n", " 'water-model': 'water-spce',\n", " 'nonbond-params': {},\n", " 'nbfunc': 'LJ', 'gen-pairs': 'no', 'fudgeLJ': 0.0, 'fudgeQQ': 0.0,\n", " 'combining-rule': 'geometric'\n", "}\n", "PARAMETRIC_FORCE_FIELDS['opls-q1.0-dummyions'] = deepcopy(PARAMETRIC_FORCE_FIELDS['opls-q1.0'])\n", "for at in PARAMETRIC_FORCE_FIELDS['opls-q1.0-dummyions']['atomtypes']:\n", " if at['type'] not in ('OW', 'HW'):\n", " at['ε'] = 0.0\n", " at['q'] = 0.0\n", "\n", "# ECC\n", "# from kohagen 2016\n", "# Ca²⁺ from martinek 2018 (1.2 nm cut-off, ? tail correction)\n", "# K⁺ from bruce 2018 (only q scaled, not structure fitted, not with Cl¯) (1.4 nm cut-off, no tail correction)\n", "# SPC/E water\n", "# 1.2 nm cut off in kohagen 2016, no tail correction\n", "PARAMETRIC_FORCE_FIELDS['eccr1'] = {\n", " 'atomtypes': [\n", " {'type': 'OW', 'q': -0.8476, 'σ': 3.16557e-01, 'ε': 6.50194e-01},\n", " {'type': 'HW', 'q': +0.4238, 'σ': 0.00000e+00, 'ε': 0.00000e+00},\n", " {'type': 'CA', 'q': +1.5000, 'σ': 2.66560e-01, 'ε': 5.07200e-01},\n", " {'type': 'LI', 'q': +0.7500, 'σ': 1.80000e-01, 'ε': 7.64700e-02},\n", " {'type': 'K', 'q': +0.7500, 'σ': 3.34000e-01, 'ε': 1.30000e-01},\n", " {'type': 'NA', 'q': +0.7500, 'σ': 2.11500e-01, 'ε': 5.44284e-01},\n", " {'type': 'CL', 'q': -0.7500, 'σ': 4.10000e-01, 'ε': 4.92800e-01},\n", " ],\n", " 'scalings': {},\n", " 'water-model': 'water-spce',\n", " 'nonbond-params': {},\n", " 'nbfunc': 'LJ', 'gen-pairs': 'no', 'fudgeLJ': 0.0, 'fudgeQQ': 0.0,\n", " 'combining-rule': 'lorentz-berthelot'\n", "}\n", "PARAMETRIC_FORCE_FIELDS['eccr1-dummyions'] = deepcopy(PARAMETRIC_FORCE_FIELDS['eccr1'])\n", "for at in PARAMETRIC_FORCE_FIELDS['eccr1-dummyions']['atomtypes']:\n", " if at['type'] not in ('OW', 'HW'):\n", " at['ε'] = 0.0\n", " at['q'] = 0.0\n", "\n", "# netz\n", "# K⁺, Li⁺, NA⁺, Cl¯ from horinek 2009 (set 5b, medium deep LJ, ε=0.65) (cut-off = 0.9 nm, E-,p-tail correction)\n", "# Ca²⁺ from mamatkulov 2013 (cut-off = 0.9 nm, E-,p-tail correction)\n", "# spc/e water\n", "PARAMETRIC_FORCE_FIELDS['netz'] = {\n", " 'atomtypes': [\n", " {'type': 'OW', 'q': -0.8476, 'σ': 3.16557e-01, 'ε': 6.50194e-01},\n", " {'type': 'HW', 'q': +0.4238, 'σ': 0.00000e+00, 'ε': 0.00000e+00},\n", " #{'type': 'CA', 'q': +2.0000, 'σ': 2.79000e-01, 'ε': 7.80000e-01}, # old and WRONG!\n", " {'type': 'CA', 'q': +2.0000, 'σ': 2.41000e-01, 'ε': 9.40000e-01},\n", " {'type': 'K', 'q': +1.0000, 'σ': 2.89000e-01, 'ε': 6.50000e-01},\n", " {'type': 'LI', 'q': +1.0000, 'σ': 1.47000e-01, 'ε': 6.50000e-01},\n", " {'type': 'NA', 'q': +1.0000, 'σ': 2.23000e-01, 'ε': 6.50000e-01},\n", " {'type': 'CL', 'q': -1.0000, 'σ': 4.40000e-01, 'ε': 4.20000e-01},\n", " ],\n", " 'scalings': {},\n", " 'water-model': 'water-spce',\n", " 'nonbond-params': {},\n", " 'nbfunc': 'LJ', 'gen-pairs': 'no', 'fudgeLJ': 0.0, 'fudgeQQ': 0.0,\n", " 'combining-rule': 'lorentz-berthelot'\n", "}\n", "PARAMETRIC_FORCE_FIELDS['netz-dummyions'] = deepcopy(PARAMETRIC_FORCE_FIELDS['netz'])\n", "for at in PARAMETRIC_FORCE_FIELDS['netz-dummyions']['atomtypes']:\n", " if at['type'] not in ('OW', 'HW'):\n", " at['ε'] = 0.0\n", " at['q'] = 0.0\n", "\n", "# madrid-2019 (cut-off = 1.0 nm, E-,p-tail correction)\n", "# 0.85 charge factor\n", "# TIP4P/2005 water\n", "PARAMETRIC_FORCE_FIELDS['madrid'] = {\n", " 'atomtypes': [\n", " {'type': 'OW', 'q': +0.0000, 'σ': 3.15890e-01, 'ε': 7.74908e-01},\n", " {'type': 'HW', 'q': +0.5564, 'σ': 0.00000e+00, 'ε': 0.00000e+00},\n", " {'type': 'MW', 'q': -1.1128, 'σ': 0.00000e+00, 'ε': 0.00000e+00},\n", " {'type': 'CA', 'q': +1.7000, 'σ': 2.66560e-01, 'ε': 5.07200e-01},\n", " {'type': 'K', 'q': +0.8500, 'σ': 2.30140e-01, 'ε': 1.98574e+00},\n", " {'type': 'LI', 'q': +0.8500, 'σ': 1.43970e-01, 'ε': 4.35090e-01},\n", " {'type': 'NA', 'q': +0.8500, 'σ': 2.21737e-01, 'ε': 1.472356e+00},\n", " {'type': 'CL', 'q': -0.8500, 'σ': 4.69906e-01, 'ε': 7.69230e-02},\n", " ],\n", " 'scalings': {},\n", " 'nonbond-params': {\n", " #frozenset(('A1', 'A2')): ('sigma', 'epsilon'),\n", " frozenset(('CL', 'OW')): ('LJ', 0.42386698, 0.06198347),\n", " frozenset(('NA', 'OW')): ('LJ', 0.26083754, 0.79338830),\n", " frozenset(('NA', 'CL')): ('LJ', 0.30051231, 1.43889423),\n", " frozenset(('CA', 'OW')): ('LJ', 0.24000000, 7.25000000),\n", " frozenset(('CA', 'CL')): ('LJ', 0.31500000, 1.00000000),\n", " frozenset(('K', 'OW')): ('LJ', 0.28904000, 1.40043000),\n", " frozenset(('K', 'CL')): ('LJ', 0.33970000, 1.40000000),\n", " frozenset(('LI', 'OW')): ('LJ', 0.21200000, 0.70065003),\n", " frozenset(('LI', 'CL')): ('LJ', 0.27000000, 1.28294385),\n", " },\n", " 'water-model': 'water-tip4p2005',\n", " 'nbfunc': 'LJ', 'gen-pairs': 'no', 'fudgeLJ': 0.0, 'fudgeQQ': 0.0,\n", " 'combining-rule': 'lorentz-berthelot'\n", "}\n", "PARAMETRIC_FORCE_FIELDS['madrid-dummyions'] = deepcopy(PARAMETRIC_FORCE_FIELDS['madrid'])\n", "for at in PARAMETRIC_FORCE_FIELDS['madrid-dummyions']['atomtypes']:\n", " if at['type'] not in ('OW', 'HW', 'MW'):\n", " at['ε'] = 0.0\n", " at['q'] = 0.0\n", "PARAMETRIC_FORCE_FIELDS['madrid-dummyions']['nonbond-params'] = {}\n", "\n", "# apply scalings\n", "def pff_apply_scalings(parametric_force_fields):\n", " for ff_name, ff in parametric_force_fields.items():\n", " scalings = ff['scalings']\n", " for at in ff['atomtypes']:\n", " at['q'] *= scalings.get(at['type'], {}).get('k_q', 1)\n", " at['σ'] *= scalings.get(at['type'], {}).get('k_σ', 1)\n", " at['ε'] *= scalings.get(at['type'], {}).get('k_ε', 1)\n", " del ff['scalings']\n", "pff_apply_scalings(PARAMETRIC_FORCE_FIELDS)\n", "\n", "# calculate C6, C12\n", "# redundancy, but that's ok\n", "def pff_calc_C6_C12(parametric_force_fields):\n", " for ff_name, ff in parametric_force_fields.items():\n", " for at in ff['atomtypes']:\n", " C6 = 4 * at['ε'] * at['σ']**6\n", " C12 = 4 * at['ε'] * at['σ']**12\n", " at['C6'] = C6\n", " at['C12'] = C12\n", " #print(' ', at['type'], f\"{C6:.6e}\", f\"{C12:.6e}\")\n", "pff_calc_C6_C12(PARAMETRIC_FORCE_FIELDS)\n", "\n", "# print parameters\n", "def pff_print_atomtypes(parametric_force_fields):\n", " for ff_name, ff in parametric_force_fields.items():\n", " print(ff_name)\n", " for at in ff['atomtypes']:\n", " print(' ', at['type'], end=' ')\n", " #print('q, σ, ε, C6, C12:', at['q'], at['σ'], at['ε'], at['C6'], at['C12'])\n", " print()\n", "PARAMETRIC_FORCE_FIELDS = {pff_name: {'name': pff_name, **pff} for pff_name, pff in PARAMETRIC_FORCE_FIELDS.items()}\n", "pff_print_atomtypes(PARAMETRIC_FORCE_FIELDS)\n", "pd.DataFrame(PARAMETRIC_FORCE_FIELDS).transpose()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "tags": [] }, "outputs": [], "source": [ "# check dummyion ff\n", "[(\n", " pd.DataFrame(PARAMETRIC_FORCE_FIELDS[f'{ff}-dummyions']['atomtypes']),\n", " pd.DataFrame(PARAMETRIC_FORCE_FIELDS[f'{ff}-dummyions']['nonbond-params']),\n", ") for ff in ['opls-q1.0', 'eccr1', 'netz', 'madrid']]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# check cl- opls-aa from chandrasekhar\n", "def check_opls():\n", " # cl- - O\n", " A_squared = 26000 * const.calorie\n", " C_squared = 3500 * const.calorie\n", " A_OO_squared = 6e5 * const.calorie\n", " C_OO_squared = 610 * const.calorie\n", " A = np.sqrt(A_squared)\n", " C = np.sqrt(C_squared)\n", " A_OO = np.sqrt(A_OO_squared)\n", " C_OO = np.sqrt(C_OO_squared)\n", " sigma = (A / C)**(1/6)\n", " epsilon = C**2 / 4 / A\n", " sigma_OO = (A_OO / C_OO)**(1/6)\n", " epsilon_OO = C_OO**2 / 4 / A_OO\n", " print(decombinate_LJ(sigma, epsilon, sigma_OO, epsilon_OO, 'geometric'))\n", " print(sigma, epsilon)\n", " \n", "#check_opls()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# check opls-aa from aqvist\n", "def check_opls():\n", " # Li+\n", " A = 25**2\n", " C = 2.6**2\n", " sigma = (A / C)**(1/6)\n", " epsilon = C**2 / 4 / A * const.calorie\n", " print(sigma, epsilon)\n", " \n", "#check_opls()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### force fields" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "force_fields = {\n", " 'opls-co0.9tc': {'tags': ['conc-range', 'tail-corr'], 'cut-off': 0.9, 'parametric-ff': 'opls-q1.0',\n", " 'cations': ('CA', 'K', 'LI', 'NA')},\n", " 'eccr1-co1.2': {'tags': ['conc-range', ], 'cut-off': 1.2, 'parametric-ff': 'eccr1',\n", " 'cations': ('CA', 'K', 'LI', 'NA')},\n", " 'netz-co0.9tc': {'tags': ['conc-range', 'tail-corr'], 'cut-off': 0.9, 'parametric-ff': 'netz',\n", " 'cations': ('CA', 'K', 'LI', 'NA')},\n", " 'madrid-co1.0tc': {'tags': ['conc-range', 'tail-corr'], 'cut-off': 1.0, 'parametric-ff': 'madrid',\n", " 'cations': ('CA', 'K', 'LI', 'NA')},\n", " # as basis for inverse method\n", " # can not have tail corrections\n", " 'netz-co0.9': {'tags': ['dummy'], 'cut-off': 0.9, 'parametric-ff': 'netz',\n", " 'cations': ('CA', 'K', 'LI', 'NA')},\n", " # can not have different cut of\n", " 'eccr1-co0.9': {'tags': ['dummy'], 'cut-off': 0.9, 'parametric-ff': 'eccr1',\n", " 'cations': ('CA', 'K', 'LI', 'NA')},\n", " # inverse results\n", " 'iff-altern5-eccr1-co1.2-nopc': {'tags': ['halftabulated', 'inverse-result', 'conc-range'], 'cut-off': 1.2,\n", " 'parametric-ff': 'eccr1', 'cations': ('CA', 'K', 'LI', 'NA'),\n", " 'tabulated-potentials': tuple((('OW', ion) for ion in ('CA', 'K', 'LI', 'NA', 'CL')))},\n", " 'iff-altern5-netz-co0.9-nopc': {'tags': ['halftabulated', 'inverse-result', 'conc-range'], 'cut-off': 0.9, # no tail-corr\n", " 'parametric-ff': 'netz', 'cations': ('CA', 'K', 'LI', 'NA'),\n", " 'tabulated-potentials': tuple((('OW', ion) for ion in ('CA', 'K', 'LI', 'NA', 'CL')))},\n", " # inverse results fitted\n", " # not in final paper\n", " # 'Buckingham-iff-altern5-eccr1-co1.2-nopc': {'tags': ['halftabulated', 'fit', 'conc-range'], 'cut-off': 1.2,\n", " # 'parametric-ff': 'eccr1', 'cations': ('CA', 'K', 'LI', 'NA'),\n", " # 'tabulated-potentials': tuple((('OW', ion) for ion in ('CA', 'K', 'LI', 'NA', 'CL')))},\n", " # 'Buckingham-iff-altern5-netz-co0.9-nopc': {'tags': ['halftabulated', 'fit', 'conc-range'], 'cut-off': 0.9, # no tail-corr\n", " # 'parametric-ff': 'netz', 'cations': ('CA', 'K', 'LI', 'NA'),\n", " # 'tabulated-potentials': tuple((('OW', ion) for ion in ('CA', 'K', 'LI', 'NA', 'CL')))},\n", " # inverse results fitted for TI test\n", " 'LJ-12-6-iff-altern5-netz-co0.9-nopc': {'tags': ['halftabulated', 'fit', 'dummy'], 'cut-off': 0.9, # no tail-corr\n", " 'parametric-ff': 'netz', 'cations': ('CA', 'K', 'LI', 'NA'),\n", " 'tabulated-potentials': tuple((('OW', ion) for ion in ('CA', 'K', 'LI', 'NA', 'CL')))},\n", " # half tabluated lj potentials for TI tests\n", " 'netz-co0.9-tab': {'tags': ['halftabulated', 'dummy'], 'cut-off': 0.9,\n", " 'parametric-ff': 'netz', 'cations': ('CA', 'K', 'LI', 'NA'),\n", " 'tabulated-potentials': tuple((('OW', ion) for ion in ('CA', 'K', 'LI', 'NA', 'CL')))},\n", " # as startpoint for TI, ions are 'ideal gas'\n", " 'opls-co0.9tc-dummyions': {'tags': ['tail-corr', 'dummy'], 'cut-off': 0.9, 'parametric-ff': 'opls-q1.0-dummyions',\n", " 'cations': ('CA', 'K', 'LI', 'NA')},\n", " 'eccr1-co1.2-dummyions': {'tags': ['dummy'], 'cut-off': 1.2, 'parametric-ff': 'eccr1-dummyions',\n", " 'cations': ('CA', 'K', 'LI', 'NA')},\n", " 'netz-co0.9tc-dummyions': {'tags': ['tail-corr', 'dummy'], 'cut-off': 0.9, 'parametric-ff': 'netz-dummyions',\n", " 'cations': ('CA', 'K', 'LI', 'NA')},\n", " 'madrid-co1.0tc-dummyions': {'tags': ['tail-corr', 'dummy'], 'cut-off': 1.0, 'parametric-ff': 'madrid-dummyions',\n", " 'cations': ('CA', 'K', 'LI', 'NA')},\n", "}\n", "\n", "force_fields = {ff_name: {'name': ff_name, **ff} for ff_name, ff in force_fields.items()}\n", "pd.DataFrame(force_fields).transpose()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### atom types" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "atomtypes = {\n", " 'OW': {'name': 'OW', 'mass': 15.9994},\n", " 'HW1': {'name': 'HW1', 'mass': 1.008},\n", " 'HW2': {'name': 'HW2', 'mass': 1.008},\n", " 'MW': {'name': 'MW', 'mass': 0.0},\n", " 'CA': {'name': 'CA', 'mass': 40.08000},\n", " 'K': {'name': 'K', 'mass': 39.09830},\n", " 'LI': {'name': 'LI', 'mass': 6.94100},\n", " 'NA': {'name': 'NA', 'mass': 22.98977},\n", " 'CL': {'name': 'CL', 'mass': 35.45300},\n", " 'M': {'name': 'M', 'mass': 0.0}, # virtual site for TI\n", "}\n", "\n", "# for later assignement\n", "def get_atomnames(atomtype):\n", " atomnames_dict = {\n", " 'HW': ('HW1', 'HW2')\n", " }\n", " return atomnames_dict.get(atomtype, (atomtype,))\n", "\n", "def get_atomtype(atomname):\n", " atomtypes_dict = {\n", " 'HW1': 'HW',\n", " 'HW2': 'HW',\n", " }\n", " return atomtypes_dict.get(atomname, atomname)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### system types" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def gen_systemtypes(): \n", "\n", " # redundancy, but that's ok\n", " spce_atoms = tuple((atomtypes[atom] for atom in ['OW', 'HW1', 'HW2']))\n", " tip4p_atoms = tuple((atomtypes[atom] for atom in ['OW', 'HW1', 'HW2', 'MW']))\n", " ca_atoms = tuple((atomtypes[atom] for atom in ['CA']))\n", " k_atoms = tuple((atomtypes[atom] for atom in ['K']))\n", " li_atoms = tuple((atomtypes[atom] for atom in ['LI']))\n", " na_atoms = tuple((atomtypes[atom] for atom in ['NA']))\n", " cl_atoms = tuple((atomtypes[atom] for atom in ['CL']))\n", "\n", " system_types = {\n", " 'water-pure': {'ions': None, 'n_cation_anion': None},\n", " 'water-licl': {'ions': (li_atoms, cl_atoms), 'n_cation_anion': (1, 1)},\n", " 'water-nacl': {'ions': (na_atoms, cl_atoms), 'n_cation_anion': (1, 1)},\n", " 'water-kcl': {'ions': (k_atoms, cl_atoms), 'n_cation_anion': (1, 1)},\n", " 'water-cacl2_': {'ions': (ca_atoms, cl_atoms), 'n_cation_anion': (1, 2)},\n", " }\n", " system_types = {st_name: {'name': st_name, **st} for st_name, st in system_types.items()}\n", " return system_types\n", "\n", "system_types = gen_systemtypes()\n", "pd.DataFrame(system_types).transpose()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### system generator" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# likely constant parameters, better than having those seperatedly globals\n", "SYSTEM_PARAMS = dict(\n", " n_water = 5000,\n", " n_salts = (25, 50, 100, 150, 200, 250, 500),\n", " n_salt_all_ff = (0, 50),\n", " n_salt_all_analyis = (0, 50, 500),\n", " tags_all = ['npt', 'npt-dist'],\n", " tags_all_analysis = ['resacf', 'therm-exp', 'dos'],\n", " # unused: nvt dist\n", ")\n", "SYSTEM_PARAMS" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def calc_volume_from_real(density, molar_mass, n_mols):\n", " \"\"\"\n", " density in g/mL\n", " molar_mass in g/mol\n", " n_mols\n", " \"\"\"\n", " system_mass = n_mols * molar_mass / const.N_A # in g\n", " volume = system_mass / density # in mL\n", " return volume / 1e-21 # in nm³\n", "\n", "# specific for water-pure and water-salt\n", "def system_generator(system_types, force_fields, system_params=SYSTEM_PARAMS, parametric_force_fields=PARAMETRIC_FORCE_FIELDS, verbose=False): \n", " \n", " # system parameters\n", " n_water = system_params['n_water']\n", " n_salts = system_params['n_salts']\n", " n_salt_all_ff = system_params['n_salt_all_ff']\n", " n_salt_all_analyis = system_params['n_salt_all_analyis']\n", " tags_all = system_params['tags_all']\n", " tags_all_analysis = system_params['tags_all_analysis']\n", " \n", " # redundancy, but that's ok\n", " water_atoms_dict = {\n", " 'water-spce': tuple((atomtypes[atom] for atom in ['OW', 'HW1', 'HW2'])),\n", " 'water-tip4p2005': tuple((atomtypes[atom] for atom in ['OW', 'HW1', 'HW2', 'MW'])),\n", " }\n", " \n", " for system_type_name, system_type in system_types.items():\n", " if verbose: print(system_type_name)\n", " \n", " ions = system_type['ions']\n", " if system_type['ions'] is None:\n", " systype_n_salts = [0]\n", " else:\n", " systype_n_salts = n_salts\n", "\n", " for n_salt in systype_n_salts:\n", " if verbose: print(n_salt)\n", " if ions is not None:\n", " n_cation = n_salt * system_type['n_cation_anion'][0]\n", " n_anion = n_salt * system_type['n_cation_anion'][1]\n", "\n", " # all force fields for n_salt in n_salt_all_ff\n", " force_fields_this_n_salt = (\n", " force_fields if n_salt in n_salt_all_ff\n", " else {ff_name: ff for ff_name, ff in force_fields.items() if 'conc-range' in ff['tags']}\n", " )\n", " for force_field_name, force_field in force_fields_this_n_salt.items():\n", " # only if force field has info about cation\n", " if ions is not None and not ions[0][0]['name'] in force_field['cations']:\n", " continue\n", " if verbose: print(force_field['name'])\n", " \n", " parametric_ff = parametric_force_fields[force_field['parametric-ff']]\n", " moltypes = [\n", " {'name': \"SOL\", 'atoms': water_atoms_dict[parametric_ff['water-model']], 'nmols': n_water,\n", " 'sigma': 2, 'abc_indicators': [1, 2, 0, -1], 'rot_treat': 'f',\n", " 'type': parametric_ff['water-model']},\n", " ]\n", " if ions is not None:\n", " if n_cation != 0:\n", " moltypes = moltypes + [\n", " {'name': ions[0][0]['name'], 'atoms': ions[0], 'nmols': n_cation,\n", " 'sigma': 1, 'abc_indicators': [0, 0, 0, 0], 'rot_treat': 'f'},\n", " ]\n", " if n_anion != 0:\n", " moltypes = moltypes + [\n", " {'name': ions[1][0]['name'], 'atoms': ions[1], 'nmols': n_anion,\n", " 'sigma': 1, 'abc_indicators': [0, 0, 0, 0], 'rot_treat': 'f'}\n", " ]\n", " tags = []\n", " tags += tags_all\n", " tags += force_field['tags']\n", " if n_salt in n_salt_all_analyis:\n", " tags += tags_all_analysis\n", "\n", " if ions is None:\n", " name = f\"water{n_water}-pure/\" + force_field['name']\n", " else:\n", " salt_name = system_type['name'].split('-')[1]\n", " name = f\"water{n_water}-{salt_name}{n_salt}/\" + force_field['name']\n", " system = {\n", " 'name': name, 'type': system_type,\n", " 'force-field': force_field,\n", " 'parametric-ff': parametric_ff,\n", " 'temperature': 300, 'density-init': 1.0, 'is-small': False,\n", " 'moltypes': moltypes, 'tags': tags,\n", " }\n", "\n", " # some additional fields for later use\n", " # redundancy, but that's ok\n", " system['n-salt'] = n_salt\n", " average_molar_mass = gt.moltypes.get_average_molar_mass(moltypes)\n", " system['r_max_g_intra'] = 0.2\n", " system['volume-init'] = calc_volume_from_real(system['density-init'], average_molar_mass, gt.moltypes.get_nmols(moltypes))\n", " assert system['volume-init']**(1/3) / 2 > system['force-field']['cut-off'], \"system to small for RDF\"\n", " # concentration measures\n", " if ions is None:\n", " system['concentration-init'] = system['mole-fraction'] = system['molar-mixing-ratio'] = system['mass-fraction'] = 0\n", " else:\n", " system['concentration-init'] = system['moltypes'][1]['nmols'] / const.N_A / (system['volume-init'] * 1e-24) # mol/l\n", " system['mole-fraction'] = system['moltypes'][1]['nmols'] / (system['moltypes'][0]['nmols'] + system['moltypes'][1]['nmols'])\n", " system['molar-mixing-ratio'] = system['moltypes'][1]['nmols'] / system['moltypes'][0]['nmols']\n", " system['mass-fraction'] = (\n", " sum((at['mass'] * mt['nmols'] for mt in system['moltypes'][1:3] for at in mt['atoms']))\n", " / (average_molar_mass * gt.moltypes.get_nmols(moltypes))\n", " )\n", " system['atomnames'] = OrderedSet((atom['name'] for mt in system['moltypes'] for atom in mt['atoms']))\n", " system['atomnames-no-h'] = system['atomnames'] - OrderedSet(('HW1', 'HW2', 'MW'))\n", " system['atomtypes'] = OrderedSet((get_atomtype(atom['name']) for mt in system['moltypes'] for atom in mt['atoms']))\n", " system['atomtypes-no-h'] = system['atomtypes'] - OrderedSet(('HW', 'MW'))\n", " \n", " yield system" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# system overview\n", "pd.DataFrame(system_generator(system_types, force_fields))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## thermal expansion settings" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "DeltaTs = (-10, 0, 10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## dos parameters" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dos_names = [\n", " {'name': 'trn', 'tags': []},\n", " {'name': 'roto', 'tags': []},\n", " {'name': 'vib', 'tags': []},\n", " {'name': 'roto_a', 'tags': ['comp']},\n", " {'name': 'roto_b', 'tags': ['comp']},\n", " {'name': 'roto_c', 'tags': ['comp']},\n", "]\n", "\n", "param_dos = {\n", " 'n_samples': 5,\n", " 'n_blocks': 4,\n", " 'n_frames_per_block': 2000,\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## plot settings" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ff_short_names = {\n", " 'opls-co0.9tc': 'OPLS',\n", " 'eccr1-co1.2': 'ECC',\n", " 'eccr1-co0.9': 'ECC (0.9 nm cut)',\n", " 'netz-co0.9tc': 'HMN',\n", " 'netz-co0.9': 'HMN (no t.c.)',\n", " 'madrid-co1.0tc': 'Madrid',\n", " 'iff-altern5-eccr1-co1.2-nopc': 'ECC IMC',\n", " 'iff-altern5-netz-co0.9-nopc': 'HMN IMC',\n", " 'Buckingham-iff-altern5-eccr1-co1.2-nopc': 'ECC IMC Buck.',\n", " 'Buckingham-iff-altern5-netz-co0.9-nopc': 'HMN IMC Buck.',\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "colors = ['#228833', '#4477AA', '#66CCEE', '#AA3377', '#EE6677', '#CCBB44']\n", "ff_colors = {\n", " 'opls-co0.9tc': '#1a3', #'#228833',\n", " 'eccr1-co1.2': '#38c', #'#4477aa',\n", " 'eccr1-co0.9': '#38c', #'#4477aa', # for TI\n", " 'netz-co0.9tc': '#b17', #'#aa3377',\n", " 'madrid-co1.0tc': '#ec1', #'#CCBB44',\n", " 'netz-co0.9': '#b17', #'#aa3377',\n", " 'iff-altern5-eccr1-co1.2-nopc': '#9bd', #'#66bbee', #'#99bbdd',\n", " 'iff-altern5-netz-co0.9-nopc': '#d9b', #'#dd99bb',\n", " #'Buckingham-iff-altern5-eccr1-co1.2-nopc': '#99ccdd',\n", " #'Buckingham-iff-altern5-netz-co0.9-nopc': '#ffaa99',\n", "}\n", "ff_linestyles = {\n", " 'opls-co0.9tc': '--',\n", " 'eccr1-co1.2': '--',\n", " 'eccr1-co0.9': '--', # for TI\n", " 'netz-co0.9tc': '--',\n", " 'madrid-co1.0tc': '--',\n", " 'netz-co0.9': '--',\n", " 'iff-altern5-eccr1-co1.2-nopc': '-.',\n", " 'iff-altern5-netz-co0.9-nopc': '-.',\n", " #'Buckingham-iff-altern5-eccr1-co1.2-nopc': ':',\n", " #'Buckingham-iff-altern5-netz-co0.9-nopc': ':',\n", "}\n", "fig, ax = plt.subplots()\n", "for f, (ff, color) in enumerate(ff_colors.items()):\n", " ax.plot([0, 1], [-f, -f], color=color, label=ff_short_names[ff], linewidth=5, linestyle=ff_linestyles[ff])\n", "ax.legend()\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sys_type_short_names = {\n", " 'water-cacl2_': r'\\ce{CaCl2}',\n", " 'water-kcl': r'\\ce{KCl}',\n", " 'water-licl': r'\\ce{LiCl}',\n", " 'water-nacl': r'\\ce{NaCl}',\n", " 'water-pure': r'\\ce{H2O}',\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "at_short_names = {\n", " 'CA': 'Ca',\n", " 'NA': 'Na',\n", " 'K': 'K',\n", " 'LI': 'Li',\n", " 'CL': 'Cl',\n", " 'OW': 'O',\n", "}\n", "ion_short_names = {\n", " 'CA': r'\\ce{Ca^2+}',\n", " 'NA': r'\\ce{Na+}',\n", " 'K': r'\\ce{K+}',\n", " 'LI': r'\\ce{Li+}',\n", " 'CL': r'\\ce{Cl-}',\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "nb_plot_name = {\n", " 'OW-LI': 'O - Li',\n", " 'OW-NA': 'O - Na',\n", " 'OW-K': 'O - K',\n", " 'OW-CA': 'O - Ca',\n", " 'OW-CL': 'O - Cl',\n", " 'CA-CL': 'Ca - Cl',\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "nb_plot_cmap = {\n", " 'OW-LI': 'Purples',\n", " 'OW-NA': 'Greens',\n", " 'OW-K': 'Blues',\n", " 'OW-CA': 'Oranges',\n", " 'OW-CL': 'RdPu',\n", "}\n", "nb_plot_color = {ia_name: mpl.cm.get_cmap(cmap)(160) for ia_name, cmap in nb_plot_cmap.items()}\n", "\n", "gradient = np.linspace(0, 1, 256)\n", "gradient = np.vstack((gradient, gradient))\n", "fig, ax = plt.subplots(figsize=(3, 3), constrained_layout=True)\n", "for i, ia_name in enumerate(nb_plot_cmap.keys()):\n", " color = nb_plot_color[ia_name]\n", " cmap = nb_plot_cmap[ia_name]\n", " ax.plot([0, 1], [-i, -i], color=color, label=nb_plot_name[ia_name], linewidth=5, linestyle='-')\n", " ax.imshow(gradient, cmap=cmap, extent=(-1, 0, -i-0.2, -i+0.2))\n", "ax.set_xlim(-1, 1)\n", "ax.set_ylim(-len(nb_plot_cmap), 1)\n", "fig.legend()\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SMALL_SIZE = 8\n", "MEDIUM_SIZE = 10\n", "BIGGER_SIZE = 12\n", "mpl_rc_global = {\n", " 'figure.dpi': 120,\n", " 'legend.frameon': False,\n", " 'font.size': SMALL_SIZE, # controls default text sizes\n", " 'axes.titlesize': SMALL_SIZE, # fontsize of the axes title\n", " 'axes.labelsize': SMALL_SIZE, # fontsize of the x and y labels\n", " 'xtick.labelsize': SMALL_SIZE, # fontsize of the tick labels\n", " 'ytick.labelsize': SMALL_SIZE, # fontsize of the tick labels\n", " 'legend.fontsize': SMALL_SIZE, # legend fontsize\n", " 'figure.titlesize': MEDIUM_SIZE, # fontsize of the figure title\n", " 'xtick.top': True,\n", " 'ytick.right': True,\n", " 'xtick.direction': 'in',\n", " 'ytick.direction': 'in',\n", " 'legend.frameon': False,\n", " 'text.usetex': True, # otherwise \\epsilon is same as \\varepsilon. Also nice font\n", " 'text.latex.preamble': r\"\\usepackage[version=4]{mhchem}\",\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## literature data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "osmp_lit_dict = {\n", " 'water-cacl2_': {\n", " 'crc-liquid-data': {\n", " 'mass_percent': np.array([0.0, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 12.0, 14.0,\n", " 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0, 30.0, 32.0, 34.0, 36.0, 38.0, 40.0]),\n", " 'molality': np.array([0.0, 0.045, 0.091, 0.184, 0.279, 0.375, 0.474, 0.575, 0.678, 0.784, 0.891, 1.001, 1.229, 1.467,\n", " 1.716, 1.978, 2.253, 2.541, 2.845, 3.166, 3.504, 3.862, 4.240, 4.642, 5.068, 5.522, 6.007]),\n", " 'concentration': np.array([0.0, 0.045, 0.091, 0.183, 0.277, 0.372, 0.469, 0.567, 0.667, 0.768, 0.872, 0.976, 1.191, 1.413,\n", " 1.641, 1.878, 2.122, 2.374, 2.634, 2.902, 3.179, 3.464, 3.759, 4.062, 4.375, 4.698, 5.030]),\n", " 'density': np.array([0.9982, 1.0024, 1.0065, 1.0148, 1.0232, 1.0316, 1.0401, 1.0486, 1.0572, 1.0659, 1.0747, 1.0835, 1.1014, 1.1198,\n", " 1.1386, 1.1579, 1.1775, 1.1976, 1.2180, 1.2388, 1.2600, 1.2816, 1.3036, 1.3260, 1.3488, 1.3720, 1.3957]),\n", " },\n", " 'osm-coeff': {\n", " # alternative?: Robinson, R. A.; Stokes, R. H. Electrolyte Solutions. Butterworth Scientific Publications: London. 1955.\n", " # M. E. Guendouzi, A. Dinane and A. Mounir, The Journal of Chemical Thermodynamics, 2001, 33, 1059–1072.\n", " 'Guendouzi 2001': {\n", " 'temperature': 298.15,\n", " 'molality': np.array([0.2, 0.3, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0]), # mol / kg\n", " 'osm-coeff': np.array([0.864, 0.870, 0.914, 1.047, 1.190, 1.374, 1.560, 1.774, 1.977, 2.174, 2.377, 2.565, 2.731, 2.872]),\n", " }\n", " }\n", " },\n", " 'water-kcl': {\n", " 'crc-liquid-data': {\n", " 'mass_percent': np.array([0.0, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0]),\n", " 'molality': np.array([0.0, 0.067, 0.135, 0.274, 0.415, 0.559, 0.706, 0.856, 1.010, 1.166, 1.327, 1.490, 1.829, 2.184, 2.555, 2.944, 3.353, 3.783, 4.236]),\n", " 'concentration': np.array([0.0, 0.067, 0.135, 0.271, 0.409, 0.549, 0.691, 0.835, 0.980, 1.127, 1.276, 1.426, 1.733, 2.048, 2.370, 2.701, 3.039, 3.386, 3.742]),\n", " 'density': np.array([0.9982, 1.0014, 1.0046, 1.0110, 1.0174, 1.0239, 1.0304, 1.0369, 1.0434, 1.0500, 1.0566, 1.0633, 1.0768, 1.0905, 1.1043, 1.1185,\n", " 1.1328, 1.1474, 1.1623]),\n", " },\n", " 'osm-coeff': {\n", " # alternative?: Robinson, R. A.; Stokes, R. H. Electrolyte Solutions. Butterworth Scientific Publications: London. 1955.\n", " # M. E. Guendouzi, A. Dinane and A. Mounir, The Journal of Chemical Thermodynamics, 2001, 33, 1059–1072.\n", " 'Guendouzi 2001': {\n", " 'temperature': 298.15,\n", " 'molality': np.array([0.2, 0.3, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5]), # mol / kg\n", " 'osm-coeff': np.array([0.905, 0.902, 0.901, 0.897, 0.900, 0.913, 0.924, 0.934, 0.951, 0.974, 0.988]),\n", " }\n", " }\n", " },\n", " 'water-licl': {\n", " 'crc-liquid-data': {\n", " 'mass_percent': np.array([0.0, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0, 30.0]),\n", " 'molality': np.array([0.0, 0.119, 0.238, 0.481, 0.730, 0.983, 1.241, 1.506, 1.775, 2.051, 2.333, 2.621, 3.217, 3.840, 4.493, 5.178, 5.897, 6.653, 7.449,\n", " 8.288, 9.173, 10.109]),\n", " 'concentration': np.array([0.0, 0.118, 0.237, 0.476, 0.719, 0.964, 1.211, 1.462, 1.715, 1.971, 2.230, 2.491, 3.022, 3.564, 4.118, 4.683, 5.260, 5.851, 6.453,\n", " 7.069, 7.700, 8.344]),\n", " 'density': np.array([0.9982, 1.0012, 1.0041, 1.0099, 1.0157, 1.0215, 1.0272, 1.0330, 1.0387, 1.0444, 1.0502, 1.0560, 1.0675, 1.0792, 1.0910, 1.1029,\n", " 1.1150, 1.1274, 1.1399, 1.1527, 1.1658, 1.1791]),\n", " },\n", " 'osm-coeff': {\n", " # alternative?: Robinson, R. A.; Stokes, R. H. Electrolyte Solutions. Butterworth Scientific Publications: London. 1955.\n", " # M. E. Guendouzi, A. Dinane and A. Mounir, The Journal of Chemical Thermodynamics, 2001, 33, 1059–1072.\n", " 'Guendouzi 2001': {\n", " 'temperature': 298.15,\n", " 'molality': np.array([0.2, 0.3, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0]), # mol / kg\n", " 'osm-coeff': np.array([0.933, 0.948, 0.969, 1.018, 1.066, 1.142, 1.207, 1.278, 1.364, 1.454, 1.532, 1.612, 1.700, 1.784]),\n", " }\n", " }\n", " },\n", " 'water-nacl': {\n", " 'crc-liquid-data': {\n", " 'mass_percent': np.array([0.0, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,\n", " 10.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0]),\n", " 'molality': np.array([0.0, 0.086, 0.173, 0.349, 0.529, 0.713, 0.901, 1.092, 1.288, 1.488,\n", " 1.692, 1.901, 2.333, 2.785, 3.259, 3.756, 4.278, 4.826, 5.403, 6.012]),\n", " 'concentration': np.array([0.0, 0.086, 0.172, 0.346, 0.523, 0.703, 0.885, 1.069, 1.256, 1.445,\n", " 1.637, 1.832, 2.229, 2.637, 3.056, 3.486, 3.928, 4.382, 4.847, 5.326]),\n", " 'density': np.array([0.9982, 1.0018, 1.0053, 1.0125, 1.0196, 1.0268, 1.0340, 1.0413, 1.0486, 1.0559,\n", " 1.0633, 1.0707, 1.0857, 1.1008, 1.1162, 1.1319, 1.1478, 1.1640, 1.1804, 1.1972]),\n", " },\n", " 'osm-coeff': {\n", " # alternative?: Robinson, R. A.; Stokes, R. H. Electrolyte Solutions. Butterworth Scientific Publications: London. 1955.\n", " # M. E. Guendouzi, A. Dinane and A. Mounir, The Journal of Chemical Thermodynamics, 2001, 33, 1059–1072.\n", " 'Guendouzi 2001': {\n", " 'temperature': 298.15,\n", " 'molality': np.array([0.2, 0.3, 0.5, 1.0, 1.5, 2.0, 2.5,\n", " 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0]), # mol / kg (?)\n", " 'osm-coeff': np.array([0.919, 0.930, 0.923, 0.931, 0.949, 0.977, 1.010,\n", " 1.037, 1.086, 1.111, 1.149, 1.183, 1.228, 1.257]),\n", " }\n", " }\n", " },\n", "}\n", "\n", "def vant_Hoff_osmotic_pressure(concentration, n_diss, temperature):\n", " return n_diss * concentration * 1e3 * const.R * temperature / 1e5 # bar\n", " \n", "def get_osmotic_coeff_lit(osmp_lit_dict, sys_type_name, lit_source):\n", " osmp_lit_dict_st = osmp_lit_dict[sys_type_name]\n", " if 'molality' in osmp_lit_dict_st['osm-coeff'][lit_source]:\n", " lit_molar_concentration = np.interp(osmp_lit_dict_st['osm-coeff'][lit_source]['molality'],\n", " osmp_lit_dict_st['crc-liquid-data']['molality'],\n", " osmp_lit_dict_st['crc-liquid-data']['concentration'])\n", " else:\n", " raise Exception(\"not implemented how to handle this literature data\")\n", " lit_osm_coeff = deepcopy(osmp_lit_dict_st['osm-coeff'][lit_source]['osm-coeff'])\n", " return lit_molar_concentration, lit_osm_coeff" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.plot(osmp_lit_dict['water-cacl2_']['crc-liquid-data']['concentration'],\n", " osmp_lit_dict['water-cacl2_']['crc-liquid-data']['molality'],\n", " )\n", "plt.plot([0, 5], [0, 5], color='k', linestyle=':')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# iterative coarse-graining" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "force_fields_inv = {ffn: ff for ffn, ff in force_fields.items() if ffn in ['eccr1-co1.2', 'netz-co0.9']}\n", "#force_fields_inv = {ffn: ff for ffn, ff in force_fields.items() if ffn in ['netz-co0.9']}\n", "system_types_inv = {stn: st for stn, st in system_types.items() if st['ions'] is not None} # not pure water\n", "#system_types_inv = {stn: st for stn, st in system_types.items() if (st['ions'] is not None and stn.startswith('water-cacl2_'))}" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# eccr convergence is smooth so I took the last IMC\n", "# netz convergence is noisy so I took the best (filled in later)\n", "final_potential_from_segment_step = {\n", " # segments are zero-indexed, iterations one-indexed\n", " 'water5000-cacl2_50/altern5-eccr1-co1.2-nopc': (5, 10),\n", " 'water5000-kcl50/altern5-eccr1-co1.2-nopc': (5, 10),\n", " 'water5000-licl50/altern5-eccr1-co1.2-nopc': (5, 10),\n", " 'water5000-nacl50/altern5-eccr1-co1.2-nopc': (5, 10),\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## inverse setup generator" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def inverse_setup_generator(system_types, force_fields, verbose=False):\n", " # kind of constants, not changing those right now\n", " pressure_setting_name, pressure_setting = ('nopc', {'name': 'nopc'})\n", " all_interactions = [\n", " {'name': 'OW-CA', 'plot-name': 'O - Ca', 'type1': 'OW', 'type2': 'CA'},\n", " {'name': 'OW-K', 'plot-name': 'O - K', 'type1': 'OW', 'type2': 'K'},\n", " {'name': 'OW-LI', 'plot-name': 'O - Li', 'type1': 'OW', 'type2': 'LI'},\n", " {'name': 'OW-NA', 'plot-name': 'O - Na', 'type1': 'OW', 'type2': 'NA'},\n", " {'name': 'OW-CL', 'plot-name': 'O - Cl', 'type1': 'OW', 'type2': 'CL'},\n", " ]\n", " r_max_g_dict = {\n", " 'water-cacl2_': 0.85,\n", " 'water-kcl': 0.73,\n", " 'water-licl': 0.726,\n", " 'water-nacl': 0.726,\n", " }\n", " n_salt_cg = 50\n", " \n", " for system_type_name, system_type in system_types.items():\n", " if verbose: print(system_type['name'])\n", " for ff_base_name, ff_base in force_fields.items():\n", " if verbose: print(ff_base['name'])\n", " \n", " # choose reference system\n", " refsys_parametric = tuple((sys for sys in system_generator({system_type_name: system_type}, {ff_base_name: ff_base})\n", " if sys['n-salt'] == n_salt_cg))\n", " if len(refsys_parametric) == 1:\n", " refsys_parametric = refsys_parametric[0]\n", " else:\n", " raise Exception('should only yield one system')\n", " moltypes = refsys_parametric['moltypes']\n", " interactions = [f\"OW-{ion[0]['name']}\" for ion in system_type['ions']]\n", " atomtypes = refsys_parametric['atomtypes']\n", " atomtypes_no_h = refsys_parametric['atomtypes-no-h']\n", " with WorkingDir(refsys_parametric['name']):\n", " run_bash(\"gmx energy -f npt-prod/ener.edr -o /tmp/volume.xvg <<< 'volume'\")\n", " data, _ = gt.xvg.load('/tmp/volume.xvg')\n", " run_bash(\"rm /tmp/volume.xvg\")\n", " one_bar_volume = np.mean(data['Volume'])\n", " del data\n", " ff_name = f\"iff-altern5-{ff_base['name']}-{pressure_setting_name}\"\n", " inverse_setup_name = f\"{refsys_parametric['name'].split('/')[0]}/altern5-{ff_base['name']}-{pressure_setting_name}\"\n", " inverse_setup = {\n", " 'name': inverse_setup_name,\n", " 'ff-name': ff_name,\n", " 'refsys-parametric': refsys_parametric,\n", " 'r-max-g': r_max_g_dict[system_type['name']],\n", " 'r-max-u': refsys_parametric['force-field']['cut-off'],\n", " 'conf-from': f\"{ff_base['name']}/npt-prod/confout.gro\",\n", " '1bar-volume': one_bar_volume,\n", " 'init': {'type': 'ref-ff'},\n", " 'target': 'tgt1',\n", " 'imc-regul': 0.3,\n", " 'segments': [\n", " {'method': 'ibi', 'iterations': 5, 'nsteps': 40000},\n", " {'method': 'imc', 'iterations': 5, 'nsteps': 400000}\n", " ]*2 + [\n", " {'method': 'ibi', 'iterations': 5, 'nsteps': 40000},\n", " {'method': 'imc', 'iterations': 10, 'nsteps': 400000},\n", " {'method': 'ibi', 'iterations': 10, 'nsteps': 40000}\n", " ],\n", " 'interactions': [ia for ia in all_interactions if ia['name'] in interactions],\n", " 'pressure-setting': pressure_setting,\n", " }\n", " yield inverse_setup" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "pd.DataFrame(inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## prepare IMC/IBI all interactions simultaneously\n", "\n", "- prepare targets in other notebook" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " \n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", " with WorkingDir(working_dir):\n", " # make dirs and copy template files\n", " run_bash(f\"mkdir -p segment-template\")\n", " # topol.top\n", " refsysp = inverse_setting['refsys-parametric']\n", " pff = PARAMETRIC_FORCE_FIELDS[refsysp['force-field']['parametric-ff']]\n", " ff = {'name': inverse_setting['ff-name'], 'tags': ['halftabulated'], 'cut-off': 1.0, 'parametric-ff': pff['name'],\n", " 'tabulated-potentials': ((interaction['type1'], interaction['type2']) for interaction in inverse_setting['interactions'])}\n", " save_parametric_force_field_as_top(f'segment-template/topol.top', ff, \"foo\",\n", " refsysp['moltypes'], osm_restraints={})\n", " # copy conf.gro and scale it\n", " run_bash(f\"cp ../../{inverse_setting['conf-from']} segment-template/conf-unscaled.gro\")\n", " scale = (inverse_setting['1bar-volume'] / np.prod(gt.gro.get_box(f\"segment-template/conf-unscaled.gro\")))**(1/3)\n", " run_bash(f\"gmx editconf -f segment-template/conf-unscaled.gro -o segment-template/conf.gro -scale {scale} {scale} {scale}\")\n", " run_bash(\"rm -f segment-template/\\#*\")\n", " # generate index.ndx\n", " top = gt.top.Topology()\n", " top.load_simple_top(refsysp['moltypes'])\n", " gt.top.generate_index_file(top, f'segment-template/index.ndx')\n", " del top\n", " # copy target\n", " for interaction in inverse_setting['interactions']:\n", " run_bash(f\"cp {template_dir}/tgt/{refsysp['type']['name']}/{interaction['name']}.dist.{inverse_setting['target']} \"\n", " + f\"segment-template/{interaction['name']}.dist.tgt\")\n", " # copy grompp.mdp\n", " run_bash(f\"cp {template_dir}/mdp/prod.mdp segment-template/grompp.mdp\")\n", " # adjust grompp.mdp\n", " gt.mdp.set_parameter(f\"segment-template/grompp.mdp\", 'ref-t', refsysp['temperature'])\n", " gt.mdp.set_parameter(f\"segment-template/grompp.mdp\", 'cutoff-scheme', 'Group')\n", " gt.mdp.set_parameter(f\"segment-template/grompp.mdp\", 'vdwtype', 'User')\n", " for key in ('rlist', 'rvdw', 'rcoulomb'):\n", " gt.mdp.set_parameter(f\"segment-template/grompp.mdp\", key, inverse_setting['r-max-u'])\n", " if 'tail-corr' in refsysp['tags']:\n", " pass # does not make sense with halftabluated systems. tabulated C6=C12=1 would go into the calculation.\n", " #gt.mdp.set_parameter(f\"segment-template/grompp.mdp\", 'DispCorr', 'EnerPress')\n", " energygrps = [interaction['type1'] for interaction in inverse_setting['interactions']]\n", " energygrps += [interaction['type2'] for interaction in inverse_setting['interactions']]\n", " energygrps = ' '.join(tuple(OrderedSet(energygrps)))\n", " gt.mdp.set_parameter(f\"segment-template/grompp.mdp\", 'energygrps', energygrps)\n", " energygrp_table = ' '.join((' '.join(interaction['name'].split('-')) for interaction in inverse_setting['interactions']))\n", " gt.mdp.set_parameter(f\"segment-template/grompp.mdp\", 'energygrp-table', energygrp_table)\n", " # copy table.xvg\n", " run_bash(f\"cp {template_dir}/table/table6-12.xvg segment-template/table.xvg\")\n", "\n", " # per segment settings\n", " for s, segment in enumerate(inverse_setting['segments']):\n", " # copy segment template\n", " run_bash(f\"cp -rT segment-template segment{s}\")\n", " # adjust grompp.mdp\n", " gt.mdp.set_parameter(f\"segment{s}/grompp.mdp\", 'nsteps', segment['nsteps'])\n", " # copy settingx.xml\n", " run_bash(f\"cp {template_dir}/xml/{segment['method']}.xml segment{s}/settings.xml\")\n", " # adjust settings.xml\n", " tree = ET.parse(f'segment{s}/settings.xml')\n", " root = tree.getroot()\n", " # make copies of non-bonded\n", " nb = root.findall('non-bonded')[0]\n", " for i in range(1, len(inverse_setting['interactions'])):\n", " nb_new = deepcopy(nb)\n", " root.insert(i, nb_new)\n", " \n", " for i, interaction in enumerate(inverse_setting['interactions']):\n", " nb = root.findall('non-bonded')[i]\n", " nb.find('name').text = interaction['name']\n", " nb.find('type1').text = interaction['type1']\n", " nb.find('type2').text = interaction['type2']\n", " nb.find('max').text = str(inverse_setting['r-max-g'])\n", " nb.find('inverse/target').text = interaction['name'] + '.dist.tgt'\n", " nb.find('inverse/gromacs/table').text = 'table_' + interaction['name'].replace('-', '_') + '.xvg'\n", " if inverse_setting['pressure-setting']['name'] == 'wjkpc':\n", " # votca advanced pressure correction likely not made for multicomponent\n", " # taking ρ = sqrt(ρ₁ * ρ₂)\n", " N1 = gt.moltypes.count_atomname(refsysp['moltypes'], interaction['type1'])\n", " N2 = gt.moltypes.count_atomname(refsysp['moltypes'], interaction['type2'])\n", " nb.find('inverse/particle_dens').text = str(np.sqrt(N1 * N2\n", " / inverse_setting['1bar-volume']**2))\n", " nb.find('inverse/post_update').text += ' pressure'\n", " nb.find('inverse/p_target').text = str(inverse_setting['pressure-setting']['p-target'])\n", " nb.find('inverse/post_update_options/pressure/do').text = str(inverse_setting['pressure-setting']['do'])\n", " nb.find('inverse/post_update_options/pressure/wjk/scale').text = str(inverse_setting['pressure-setting']['wjk-scale'])\n", " nb.find('inverse/post_update_options/pressure/wjk/max_A').text = str(inverse_setting['pressure-setting']['max-A'])\n", " elif inverse_setting['pressure-setting']['name'] == 'nopc':\n", " nb.find('inverse').remove(nb.find('inverse/particle_dens'))\n", " nb.find('inverse').remove(nb.find('inverse/p_target'))\n", " nb.find('inverse/post_update_options').remove(nb.find('inverse/post_update_options/pressure'))\n", " else:\n", " raise Exception('not implemented')\n", " root.find('inverse/kBT').text = str(refsysp['temperature'] * oconst.k_gro)\n", " root.find('inverse/iterations_max').text = str(segment['iterations'])\n", " root.find('inverse/gromacs/grompp/opts').text = '-maxwarn 1'\n", " if segment['method'] == 'imc':\n", " root.find('inverse/imc/default_reg').text = str(inverse_setting['imc-regul'])\n", " tree.write(f'segment{s}/settings.xml')\n", " \n", " # generate pot.in\n", " if inverse_setting['init']['type'] == 'bi':\n", " pass\n", " elif inverse_setting['init']['type'] == 'ref-ff':\n", " r = np.linspace(0, 3, num=1501)\n", " for interaction in inverse_setting['interactions']:\n", " at1 = next((at for at in pff['atomtypes'] if at['type'] == interaction['type1']))\n", " at2 = next((at for at in pff['atomtypes'] if at['type'] == interaction['type2']))\n", " pot, force = gen_potential_and_force(at1, at2, r, pff['combining-rule'], add_coulomb=False,\n", " nonbond_params=pff['nonbond-params'])\n", " np.savetxt(f\"segment0/{interaction['name']}.pot.in\", np.stack((r, pot)).T)\n", " else:\n", " raise Exception('Not implemented!')\n", " \n", " # remove template\n", " run_bash(\"rm -rf segment-template\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## run on cluster" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### run inverse simultaneously" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def run_inv():\n", " remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'mammut-c', votca=True)\n", " for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", " remote_dir = os.path.join(remote_dir_base, working_dir)\n", " with WorkingDir(working_dir):\n", " # mkdir\n", " run_bash(f\"ssh {remote_host} mkdir -p {remote_dir}\")\n", "\n", " # push files\n", " filelist = [\"segment*/conf.gro\", \"segment*/grompp.mdp\", \"segment*/index.ndx\", \"segment*/settings.xml\",\n", " \"segment*/*.dist.tgt\", \"segment*/table*\", \"segment*/topol.top\"]\n", " if inverse_setting['init']['type'] not in ['bi']:\n", " filelist += [\"segment0/*.pot.in\"]\n", " gt.remote.push_files(filelist, remote_host, remote_dir)\n", "\n", " script = remote_header\n", " for s, segment in enumerate(inverse_setting['segments']):\n", " if s != 0:\n", " last_segment = inverse_setting['segments'][s-1]\n", " for interaction in inverse_setting['interactions']:\n", " script += (f\"\\ncp segment{s-1}/step_{last_segment['iterations']:03}/{interaction['name']}.pot.new \"\n", " f\"segment{s}/{interaction['name']}.pot.in\")\n", "\n", " script += f\"\"\"\n", "pushd segment{s}\n", " ln -sf $JOBTMP jobtmp\n", " csg_inverse --options settings.xml\n", " unlink jobtmp\n", "popd\n", "\"\"\" \n", " script += remote_footer\n", "\n", " jobid = gt.remote.run_slurm_script(script, remote_host, remote_dir, dry_run=False)\n", " print(jobid)\n", " if jobid != None:\n", " jobids.append(jobid)\n", "run_inv()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### check job status" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "jobids = check_job_stati(jobids, remote_host)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### copy results from cluster" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def copy_from_cluster():\n", " remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'enzo', votca=True)\n", " for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " \n", " # TEMPORARY\n", " if not inverse_setting['name'].startswith('water5000-cacl2_50/altern5-netz'):\n", " print('.. continue ..')\n", " continue\n", " \n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", " remote_dir = os.path.join(remote_dir_base, working_dir)\n", " with WorkingDir(working_dir):\n", " filelist = [\"segment*/step_*/*.dist*.new\", \"segment*/step_*/*.pot.new\", \"segment*/step_*/*.dpot.new\",\n", " \"segment*/step_*/*.pot.cur\",\n", " \"segment*/step_*/ener.edr\", \"segment0/step_001/*.dist.tgt\"]\n", " gt.remote.pull_files(filelist, remote_host, remote_dir)\n", "copy_from_cluster()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## evaluate coarse-graining" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "### determine best iteration for Netz-derived model" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def determine_best_iteration():\n", " Deltas = {}\n", " for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", "\n", " Deltas[inverse_setting['name']] = collections.defaultdict(lambda: 0.0)\n", "\n", " with WorkingDir(working_dir):\n", " if len(glob.glob('segment*/step_*/')) <= 1:\n", " print('.. no data ..')\n", " continue\n", "\n", "\n", " for interaction in inverse_setting['interactions']:\n", " print(' ' + interaction['name'])\n", " ia_name = interaction['name']\n", "\n", " # plot g(r) or Δg(r)\n", " dist_files = sorted(glob.glob(f\"segment*/step_*/{ia_name}.dist.new\"))\n", " # load target g(r)\n", " _, dist_tgt_g, _ = readin_table(f\"segment0/step_001/{ia_name}.dist.tgt\")\n", " for i, dist_file in enumerate(dist_files):\n", " segment = int(re.search('segment(\\d+)', dist_file).group(1))\n", " step = int(re.search('step_(\\d+)', dist_file).group(1))\n", " dist_r, dist_g, dist_flag = readin_table(dist_file)\n", " Delta = np.sqrt(1/max(dist_r) * np.trapz(x=dist_r, y=(dist_g - dist_tgt_g)**2))\n", " Deltas[inverse_setting['name']][(segment, step)] += Delta / len(inverse_setting['interactions'])\n", "\n", " best_it = min(Deltas[inverse_setting['name']], key=Deltas[inverse_setting['name']].get)\n", " if inverse_setting['name'] not in final_potential_from_segment_step:\n", " final_potential_from_segment_step[inverse_setting['name']] = best_it\n", "determine_best_iteration()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "final_potential_from_segment_step" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "### equilibration check" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def equi_check():\n", " cmap = plt.get_cmap('rainbow')\n", " \n", " for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", " with WorkingDir(working_dir):\n", "\n", " # show stuff\n", " ener_files = sorted(glob.glob('segment*/step_*/ener.edr'))\n", " if ener_files == []:\n", " print('.. nothing here ..')\n", " continue\n", " segment_step_tuples = []\n", " pressures = []\n", " pressures_std = []\n", "\n", " # energy files to actually plot\n", " ener_files_to_show = ener_files\n", "\n", " fig, (ax0, ax1, ax2) = plt.subplots(nrows=1, ncols=3, figsize=[18, 2])\n", " # load data and plot\n", " e_max = len(ener_files)\n", " for e, ener_file in enumerate(ener_files):\n", " # skip empty ener.edr\n", " if os.stat(ener_file).st_size == 0:\n", " continue\n", " # get segment and step\n", " segment = int(re.search('segment(\\d+)', ener_file).group(1))\n", " step = int(re.search('step_(\\d+)', ener_file).group(1))\n", " segment_step_tuples.append((segment, step))\n", " # get energy data\n", " tmp_fd, tmp_path = tempfile.mkstemp(suffix='.xvg')\n", " try:\n", " run_bash(f\"gmx energy -f {ener_file} -o {tmp_path} <<< 'Temperature\\nPressure'\")\n", " data, _ = gt.xvg.load(tmp_path)\n", " finally:\n", " os.remove(tmp_path)\n", " data_noequi = data[data['Time (ps)'] > 10]\n", " pressures.append(data_noequi['Pressure'].mean())\n", " #pressures_std.append(data_noequi['Pressure'].std())\n", " # block average\n", " pressures_std.append(data_noequi.groupby(data_noequi.index.array // 100)['Pressure'].mean().std())\n", "\n", " if ener_file in ener_files_to_show:\n", " label = f\"{segment} {step}\"\n", " ax0.plot(data['Time (ps)'], np.array(data['Temperature']), label=label, color=cmap(e/e_max))\n", " ax1.plot(data['Time (ps)'], np.array(data['Pressure']), label=label, color=cmap(e/e_max))\n", "\n", " # plot average pressure\n", " ax2.errorbar([f'{seg} {step}' for seg, step in segment_step_tuples],\n", " pressures, pressures_std, label='pressure cur')\n", " # final potential marker\n", " if inverse_setting['name'] in final_potential_from_segment_step:\n", " ax2.axvline(f\"{final_potential_from_segment_step[inverse_setting['name']][0]} \"\n", " f\"{final_potential_from_segment_step[inverse_setting['name']][1]}\", color='green', linestyle='--')\n", "\n", " ax0.set_title(\"Temperature\")\n", " ax1.set_title(\"Pressure\")\n", " ax2.set_title(\"Pressure vs. step\")\n", " ax2.set_xlabel('segment step')\n", " ax2.set_ylabel('pressure in bar')\n", " ax2.xaxis.set_major_locator(mpl.ticker.MaxNLocator(integer=True))\n", " #ax0.legend(loc='center right')\n", " ax2.legend()\n", " fig.tight_layout() \n", " #fig.savefig(os.path.join(working_dir_base, \"figures\", f\"p-convergence_{inverse_setting['name']}_{system['name'].replace('/', '-')}.png\"), dpi=150)\n", " plt.show()\n", "equi_check()" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "### plot dist, dU, U " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "tags": [] }, "outputs": [], "source": [ "def show_dist_dU_U():\n", " cmap = plt.get_cmap('rainbow')\n", "\n", " mpl_rc = {\n", " 'figure.dpi': 100,\n", " 'legend.title_fontsize': 8,\n", " 'legend.fontsize': 8,\n", " 'legend.handlelength': 1,\n", " }\n", "\n", " plot_delta_g = False\n", "\n", " for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", "\n", "\n", " with WorkingDir(working_dir):\n", " if len(glob.glob('segment*/step_*/')) <= 1:\n", " print('.. nothing here ..')\n", " continue\n", "\n", "\n", " for interaction in inverse_setting['interactions']:\n", " print(' ' + interaction['name'])\n", " fig, (ax0, ax1, ax2) = plt.subplots(nrows=1, ncols=3, figsize=[18, 5])\n", " ia_name = interaction['name']\n", "\n", " # plot g(r) or Δg(r)\n", " dist_files = sorted(glob.glob(f\"segment*/step_*/{ia_name}.dist.new\"))\n", " #dist_files = dist_files[:10]\n", " axins0 = inset_axes(ax0, width=\"50%\", height=\"50%\", loc='upper right')\n", " # load target g(r)\n", " _, dist_tgt_g, _ = readin_table(f\"segment0/step_001/{ia_name}.dist.tgt\")\n", " g_std = []\n", " ax0_legend_lines = []\n", " for i, dist_file in enumerate(dist_files):\n", " segment = int(re.search('segment(\\d+)', dist_file).group(1))\n", " step = int(re.search('step_(\\d+)', dist_file).group(1))\n", " label = None\n", " if segment == 0 and step == 0:\n", " label = 'init. guess'\n", " elif step == 5:\n", " label = f\"{segment} \" + inverse_setting['segments'][segment]['method'].upper()\n", " dist_r, dist_g, dist_flag = readin_table(dist_file)\n", " if plot_delta_g:\n", " line = ax0.plot(dist_r, dist_g - dist_tgt_g, label=label, color=cmap(i/len(dist_files)))\n", " else:\n", " line = ax0.plot(dist_r, dist_g, label=label, color=cmap(i/len(dist_files)))\n", " if label is not None:\n", " ax0_legend_lines.append(line[0])\n", " ax0.set_title(r'$Δg(r)$' if plot_delta_g else r'$g(r)$')\n", "\n", " x = f\"{segment} {step}\"\n", " y = np.sqrt(1/max(dist_r) * np.trapz(x=dist_r, y=(dist_g - dist_tgt_g)**2))\n", " label = inverse_setting['segments'][segment]['method'].upper()\n", " g_std.append((x, y, label))\n", "\n", " # plot target g(r)\n", " if plot_delta_g:\n", " line = ax0.plot(dist_r, np.zeros_like(dist_r), label=f\"tgt\", color='k', linestyle='--')\n", " ax0.set_ylim(-0.04, 0.02)\n", " else:\n", " line = ax0.plot(dist_r, dist_tgt_g, label=\"tgt\", color='k', linestyle='--')\n", " ax0_legend_lines.append(line[0])\n", "\n", " # now that we have g, define core_end\n", " ndx_ce = np.where(dist_g > 1e-10)[0][0]\n", " core_end = dist_r[ndx_ce]\n", "\n", " axins0.plot(*list(zip(*g_std))[0:2], '.-')\n", " axins0.set_xticks(range(len(g_std)))\n", " axins0.set_xticklabels(*list(zip(*g_std))[2:3])\n", " #axins0.set_xticklabels(axins0.get_xticklabels(), rotation=90, ha='right')\n", " axins0.tick_params(labelrotation=90)\n", " # only every fith tick\n", " for l, label in enumerate(axins0.xaxis.get_ticklabels()):\n", " if l % 5 != 0:\n", " label.set_visible(False)\n", " # final potential marker\n", " axins0.axvline(f\"{final_potential_from_segment_step[inverse_setting['name']][0]} \"\n", " f\"{final_potential_from_segment_step[inverse_setting['name']][1]}\", color='green', linestyle='--')\n", "\n", " axins0.set_ylim(0, 0.3)\n", " #axins0.set_xlabel('seg. st.')\n", " axins0.set_ylabel('RMSD')\n", " #axins0.grid()\n", " #axins0.remove()\n", "\n", " # plot dU(r) \n", " ax1_legend_lines = []\n", " dpot_files = sorted(glob.glob(f'segment*/step_*/{ia_name}.dpot.new'))\n", " #dpot_files = dpot_files[:10]\n", " for i, dpot_file in enumerate(dpot_files):\n", " segment = int(re.search('segment(\\d+)', dpot_file).group(1))\n", " step = int(re.search('step_(\\d+)', dpot_file).group(1))\n", " label = None\n", " if segment == 0 and step == 0:\n", " label = 'init. guess'\n", " elif step == 5:\n", " label = f\"{segment} \" + inverse_setting['segments'][segment]['method'].upper()\n", " dpot_r, dpot_dU, dist_flag = readin_table(dpot_file)\n", " line = ax1.plot(dpot_r, dpot_dU, '-', label=label, color=cmap(i/len(dpot_files)))\n", " if label is not None: ax1_legend_lines.append(line[0])\n", " ax1.set_title(\"ΔU(r)\")\n", " ax1.set_ylim((-10, 10))\n", "\n", " # plot U(r)\n", " pot_files = sorted(glob.glob(f'segment*/step_*/{ia_name}.pot.new'))\n", " ax2_legend_lines = []\n", " #pot_files = pot_files[:10]\n", " axins2 = inset_axes(ax2, width=\"50%\", height=\"70%\", loc='upper right')\n", " for i, pot_file in enumerate(pot_files):\n", " segment = int(re.search('segment(\\d+)', pot_file).group(1))\n", " step = int(re.search('step_(\\d+)', pot_file).group(1))\n", " label = f\"{segment} {step}\"\n", " label = None\n", " if segment == 0 and step == 0:\n", " label = 'init. guess'\n", " elif step == 5:\n", " label = f\"{segment} \" + inverse_setting['segments'][segment]['method'].upper()\n", " pot_r, pot_U, dist_flag = readin_table(pot_file)\n", " line = ax2.plot(pot_r, pot_U, label=label, color=cmap(i/len(pot_files)))\n", " axins2.plot(pot_r, pot_U, '-', label=label, color=cmap(i/len(pot_files)))\n", " if label is not None:\n", " ax2_legend_lines.append(line[0])\n", " ax2.set_ylim((-8, 8))\n", " ax2.set_title(\"U(r)\")\n", "\n", " cut_off = inverse_setting['r-max-u']\n", " axins2.set_xlim(core_end, core_end+0.1)\n", " axins2.set_ylim(pot_U[ndx_ce+20], pot_U[ndx_ce])\n", " #axins2.set_ylim(-3, 3)\n", " #axins2.remove()\n", " axins2.patch.set_alpha(0.5)\n", "\n", " ax0.legend(ax0_legend_lines, [li.get_label() for li in ax0_legend_lines],\n", " loc='upper left', title='seg. met.')\n", " ax1.legend(ax1_legend_lines, [li.get_label() for li in ax1_legend_lines],\n", " loc='upper left', title='seg. met.')\n", " ax2.legend(ax2_legend_lines, [li.get_label() for li in ax2_legend_lines],\n", " loc='upper left', title='seg. met.')\n", " for ax in (ax0, ax1, ax2):\n", " ax.set_xlim((0.10, inverse_setting['r-max-g'] + 0.1))\n", " ax.set_xlabel(\"r in nm\")\n", " #ax.grid()\n", " for ax in (ax1, ax2):\n", " ax.set_ylabel(\"U in kJ mol¯¹\")\n", "\n", " #fig.tight_layout() \n", " #fig.savefig(os.path.join(working_dir_base, \"../figures\", f\"{inverse_setting['name'].replace('/', '-')}-{ia_name}.png\"), dpi=300)\n", " plt.show()\n", " \n", "show_dist_dU_U()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### convergence plot for paper" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "tags": [] }, "outputs": [], "source": [ "def convergence_plot():\n", " mpl_rc = {\n", " 'figure.dpi': 120,\n", " 'legend.title_fontsize': 8,\n", " 'legend.fontsize': 8,\n", " 'legend.handlelength': 2,\n", " }\n", "\n", " with plt.rc_context(rc={**mpl_rc_global, **mpl_rc}):\n", " fig, axes = plt.subplots(nrows=1, ncols=2, figsize=[4.6, 2.0], tight_layout=True, dpi=200) # sharey='row' \n", " for i, inverse_setting in enumerate(inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False)):\n", " ax = axes[i%2]\n", " if i < 2:\n", " ax.set_title(ff_short_names[inverse_setting['ff-name']])\n", " print(inverse_setting['name'])\n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", "\n", " Delta = collections.defaultdict(lambda: 0.0)\n", "\n", " with WorkingDir(working_dir):\n", " if len(glob.glob('segment*/step_*/')) <= 1:\n", " print('.. nothing here ..')\n", " continue\n", "\n", " for interaction in inverse_setting['interactions']:\n", " #print(' ' + interaction['name'])\n", " ia_name = interaction['name']\n", "\n", " # plot g(r) or Δg(r)\n", " dist_files = sorted(glob.glob(f\"segment*/step_*/{ia_name}.dist.new\"))\n", " # load target g(r)\n", " _, dist_tgt_g, _ = readin_table(f\"segment0/step_001/{ia_name}.dist.tgt\")\n", " for i, dist_file in enumerate(dist_files):\n", " segment = int(re.search('segment(\\d+)', dist_file).group(1))\n", " step = int(re.search('step_(\\d+)', dist_file).group(1))\n", " dist_r, dist_g, dist_flag = readin_table(dist_file)\n", " Delta_ = np.sqrt(1/max(dist_r) * np.trapz(x=dist_r, y=(dist_g - dist_tgt_g)**2))\n", " Delta[(segment, step)] += Delta_ / len(inverse_setting['interactions'])\n", " \n", "\n", " x = [\"{} {}\".format(*key) for key in Delta.keys()]\n", " y = Delta.values()\n", " color = nb_plot_color[inverse_setting['interactions'][0]['name']]\n", " line, = ax.plot(x, y, label=sys_type_short_names[inverse_setting['refsys-parametric']['type']['name']], linewidth=1.0, color=color)\n", " xticklabels = [(\"IBI\", \"IMC\")[key[0] % 2] if key[1] == 1 else \"\" for key in Delta.keys()]\n", " ax.set_xticks(x)\n", " ax.set_xticklabels(xticklabels)\n", " ax.axvline(f\"{final_potential_from_segment_step[inverse_setting['name']][0]} \"\n", " f\"{final_potential_from_segment_step[inverse_setting['name']][1]}\", color=line.get_color(), linestyle='--')\n", " \n", " for ax in axes:\n", " ax.tick_params(labelrotation=90)\n", " ax.set_yticks([0, 0.05, 0.1])\n", " ax.set_ylim(0, 0.10)\n", " ax.set_yticklabels([\"0\", \"0.05\", \"0.10\"])\n", " axes[0].set_ylabel(r'$\\Delta^\\mathrm{cat.-O} + \\Delta^\\mathrm{an.-O}$')\n", " axes[0].legend(frameon=False)\n", "\n", " fig.savefig(os.path.join(working_dir_base, \"../figures\", f\"convergence.pdf\"))\n", " plt.show()\n", " \n", "convergence_plot()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!cp -a ../figures/convergence.pdf ~/research/output/ion-shortrange-paper/figures/" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### copy resulting OW-Cation to template/table" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def copy_tables():\n", " overwrite = False\n", " \n", " for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", "\n", " for interaction in inverse_setting['interactions'][0:1]:\n", " with WorkingDir(working_dir):\n", " print(' ' + interaction['name'])\n", " ia_name = interaction['name']\n", " # copy best pot to inverse method folder\n", " best_pot1 = (f\"segment{final_potential_from_segment_step[inverse_setting['name']][0]}\"\n", " f\"/step_{final_potential_from_segment_step[inverse_setting['name']][1]:03d}/{ia_name}.pot.cur\")\n", " best_pot2 = f\"best-{ia_name}.pot.cur\"\n", " skip_or_overwrite(best_pot1, best_pot2, overwrite)\n", " \n", " # make table\n", " table_name = \"table_\" + ia_name.replace('-', '_') + '.xvg'\n", " best_table1 = f\"best-{table_name}\"\n", " run_bash(f\"csg_call --options segment0/settings.xml --ia-type non-bonded --ia-name {ia_name} \"\n", " f\"convert_potential gromacs --clean {best_pot2} {best_table1}\")\n", " # copy to template/table\n", " ff_name = inverse_setting['ff-name']\n", " best_table2 = f\"{working_dir_base}/template/table/{ff_name}/{table_name}\"\n", " run_bash(f\"mkdir -p {working_dir_base}/template/table/{ff_name}\")\n", " skip_or_overwrite(best_table1, best_table2, overwrite)\n", "copy_tables()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### average oxygen-chloride potentials and save them" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def average_ox_an():\n", " \n", " overwrite = False\n", " \n", " for ff_name, ff_inverse_settings in itertools.groupby(sorted(inverse_setup_generator(system_types_inv, force_fields_inv), key=operator.itemgetter('ff-name')),\n", " key=operator.itemgetter('ff-name')):\n", " print(f\" {ff_name}\")\n", " U_dict = {}\n", " r_dict = {}\n", " ff_inverse_settings = tuple(ff_inverse_settings) # need to iterate this twice\n", " # shortest r will be common r later\n", " r_shortest = [0, np.inf]\n", " inverse_setting_shortest = None\n", " for inverse_setting in ff_inverse_settings:\n", " print(' ' + inverse_setting['name'])\n", " interaction = inverse_setting['interactions'][1] # only oxygen-chloride\n", " ia_name = interaction['name']\n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", " # load potential and rdf\n", " best_pot = (f\"segment{final_potential_from_segment_step[inverse_setting['name']][0]}\"\n", " f\"/step_{final_potential_from_segment_step[inverse_setting['name']][1]:03d}/{ia_name}.pot.cur\")\n", " with WorkingDir(working_dir):\n", " U_data = np.loadtxt(best_pot, dtype=str, comments=['#', '@'])\n", " r = U_data[:, 0].astype(float)\n", " U = U_data[:, 1].astype(float)\n", " U_dict[inverse_setting['name']] = U\n", " r_dict[inverse_setting['name']] = r\n", " if max(r) < max(r_shortest):\n", " r_shortest = r\n", " inverse_setting_shortest = inverse_setting\n", " # give values for Us on r_shortest (effectively cutting, but interp is convenient)\n", " for inverse_setting in ff_inverse_settings:\n", " U_dict[inverse_setting['name']] = np.interp(r_shortest, r_dict[inverse_setting['name']], U_dict[inverse_setting['name']])\n", " # average potential\n", " U_mean = np.mean(tuple(U_dict.values()), axis=0)\n", " # save potential\n", " pot1 = f\"/tmp/{ff_name}-{ia_name}.xvg\"\n", " np.savetxt(pot1, np.stack((r_shortest, U_mean)).T, header=\"averaged OW-CL interaction\")\n", " pot2 = f\"template/table/{ff_name}/{ia_name}.pot.avg\"\n", " skip_or_overwrite(pot1, pot2, overwrite)\n", " # make table\n", " table_name = \"table_\" + ia_name.replace('-', '_') + '.xvg'\n", " table1 = f\"/tmp/{table_name}\"\n", " run_bash(f\"csg_call --options {inverse_setting_shortest['name']}/both/segment0/settings.xml --ia-type non-bonded --ia-name {ia_name} \"\n", " f\"convert_potential gromacs --clean {pot2} {table1}\")\n", " # copy to template/table\n", " ff_name = inverse_setting['ff-name']\n", " table2 = f\"template/table/{ff_name}/{table_name}\"\n", " run_bash(f\"mkdir -p template/table/{ff_name}\")\n", " skip_or_overwrite(table1, table2, overwrite)\n", " \n", "average_ox_an()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### sanity check: compare potentials" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def compare_potentials():\n", " mpl_rc = {\n", " 'figure.dpi': 100,\n", " 'legend.title_fontsize': 8,\n", " 'legend.fontsize': 8,\n", " 'legend.handlelength': 1,\n", " }\n", " for interaction_index in range(2):\n", " for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " # OW-cation\n", " interaction = inverse_setting['interactions'][interaction_index]\n", " ia_name = interaction['name']\n", "\n", " # load potentials from iteration\n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", " with WorkingDir(working_dir):\n", " inv_file = (f\"segment{final_potential_from_segment_step[inverse_setting['name']][0]}\"\n", " f\"/step_{final_potential_from_segment_step[inverse_setting['name']][1]:03d}/{ia_name}.pot.cur\")\n", " inv_r, inv_U, _ = readin_table(inv_file)\n", " inv_r_max = max(inv_r)\n", "\n", " # load potentials from template/table\n", " template_table_file = f\"template/table/{inverse_setting['ff-name']}/table_{ia_name.replace('-', '_')}.xvg\"\n", " template_r, _, _, _, _, template_U, _ = np.loadtxt(template_table_file).transpose()\n", " template_r_max = np.nonzero(template_r)[0][-1]\n", "\n", " # load potentials from md folders\n", " md_table_files = glob.glob(f\"water5000-*/{inverse_setting['ff-name']}/topol/\"\n", " f\"table_{ia_name.replace('-', '_')}.xvg\")\n", " md_tables = {}\n", " for md_table_file in md_table_files:\n", " r, _, _, _, _, U, _ = np.loadtxt(md_table_file).transpose()\n", " md_r_max = np.nonzero(r)[0][-1]\n", " md_tables[md_table_file] = {'r': r, 'U': U, 'r-max': md_r_max}\n", "\n", " # plot\n", " fig, ax = plt.subplots(figsize=(17, 1.5), constrained_layout=True)\n", " # inv\n", " line, = ax.plot(inv_r, inv_U, label=inv_file)\n", " ax.axvline(inv_r_max, color=line.get_color())\n", " # template\n", " line, = ax.plot(template_r, template_U, linestyle='--', label=template_table_file)\n", " ax.axvline(template_r_max, color=line.get_color())\n", " # md\n", " for i, (filename, md_table) in enumerate(md_tables.items()):\n", " label = f\"{len(md_tables)} x\" + filename if i == 0 else None\n", " line, = ax.plot(md_table['r'], md_table['U'], linestyle=':', color='darkgreen', label=label)\n", " ax.axvline(md_table['r-max'], color=line.get_color())\n", "\n", " ax.legend(frameon=False, loc='upper right')\n", " ax.set_title(ia_name)\n", " #ax.set_xlim(0, inverse_setting['r-max-u'])\n", " ax.set_xlim(0, 3.1)\n", " ax.set_ylim(-6, 6)\n", " plt.show()\n", "\n", "compare_potentials()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## fitting" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### fit functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def gen_lj_12_6_potential(r, a, b):\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = a / r**12 - b / r**6\n", " pot[0] = pot[1]\n", " return pot\n", " \n", "def gen_buckingham_potential(r, a, b, c):\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = a * np.exp(-b * r) - c / r**6\n", " pot[0] = pot[1]\n", " return pot\n", "\n", "def gen_buckingham_mod_potential(r, a, b, c):\n", " offset = -0.1\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = a * np.exp(-b * (r + offset)) - c / (r + offset)**6\n", " ndx0 = np.where((r + offset) > 0.0)[0][0]\n", " pot[:ndx0] = pot[ndx0]\n", " return pot\n", "\n", "def gen_lj_9_6_potential(r, a, b):\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = a / r**9 - b / r**6\n", " pot[0] = pot[1]\n", " return pot\n", "\n", "def gen_lj_8_6_potential(r, a, b):\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = a / r**8 - b / r**6\n", " pot[0] = pot[1]\n", " return pot\n", "\n", "def gen_lj_7_6_potential(r, a, b):\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = a / r**7 - b / r**6\n", " pot[0] = pot[1]\n", " return pot\n", "\n", "def gen_lj_12_9_6_potential(r, a, b, c):\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = a / r**12 + b / r**9 - c / r**6\n", " pot[0] = pot[1]\n", " return pot\n", "\n", "def gen_mie_potential(r, sigma, epsilon, n, m):\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = (n / (n - m)) * (n / m)**(m/(n-m)) * epsilon * ((sigma/r)**n - (sigma/r)**m)\n", " pot[0] = pot[1]\n", " return pot\n", "\n", "def gen_att_6_potential(r, b):\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = - b / r**6\n", " pot[0] = pot[1]\n", " return pot\n", "\n", "def gen_rep_power_potential(r, a, e):\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = a * r**e\n", " pot[0] = pot[1]\n", " return pot\n", "\n", "def gen_lj_n_6_potential(r, a, n, b):\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " pot = a / r**n - b / r**6\n", " pot[0] = pot[1]\n", " return pot\n", "\n", "bounds_lj = (0, 1e-5) # A, B\n", "fit_functions = {\n", " 'Buckingham': {'name': 'Buckingham', 'func': gen_buckingham_potential,\n", " 'p0': (1e8, 5e1, 1e-3), 'bounds': ((0, 0, 0), (np.inf, np.inf, np.inf))},\n", " 'LJ-12-6': {'name': '12-6 LJ', 'func': gen_lj_12_6_potential,\n", " 'p0': (2e-7, 2e-4), 'bounds': (bounds_lj, (np.inf, np.inf))},\n", " 'LJ-9-6': {'name': '9-6 Mie', 'func': gen_lj_9_6_potential,\n", " 'p0': (2e-4, 2e-2), 'bounds': (bounds_lj, (np.inf, np.inf))},\n", " 'LJ-8-6': {'name': '8-6 Mie', 'func': gen_lj_8_6_potential,\n", " 'p0': (2e-4, 2e-2), 'bounds': (bounds_lj, (np.inf, np.inf))},\n", " #'Buckingham-offset': {'name': 'Buckingham of.', 'func': gen_buckingham_mod_potential,\n", " #'p0': (1e8, 5e1, 1e-3), 'bounds': ((0, 0, 0), (np.inf, np.inf, np.inf))},\n", " 'LJ-7-6': {'name': '7-6 Mie', 'func': gen_lj_7_6_potential,\n", " 'p0': (2e-4, 2e-2), 'bounds': (bounds_lj, (np.inf, np.inf))},\n", " #'LJ-12-9-6': {'name': 'LJ-12-9-6', 'func': gen_lj_12_9_6_potential,\n", " #'p0': (2e-6, 2e-5, 2e-4), 'bounds': ((0, 0, 0), (np.inf, np.inf, np.inf))},\n", " #'Mie': {'name': 'Mie', 'func': gen_mie_potential,\n", " #'p0': (0.3, 3, 12, 6), 'bounds': ((0, 0, 7, 2), (np.inf, np.inf, 30, 6))},\n", " 'LJ-n-6': {'name': 'n-6 Mie', 'func': gen_lj_n_6_potential,\n", " 'p0': (2e-4, 8, 2e-2), 'bounds': ((0, 1, 1e-5), (np.inf, np.inf, np.inf))},\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### load data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iff_pot_fit_dict = {}" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def load_data():\n", " for ff_name, ff_inverse_settings in itertools.groupby(sorted(inverse_setup_generator(system_types_inv, force_fields_inv), key=operator.itemgetter('ff-name')),\n", " key=operator.itemgetter('ff-name')):\n", " print(f\" {ff_name}\")\n", " U_dict = {}\n", " g_dict = {}\n", " r_dict = {}\n", " ff_inverse_settings = tuple(ff_inverse_settings) # need to iterate this twice\n", " r_shortest = (0, np.inf)\n", " for inverse_setting in ff_inverse_settings:\n", " print(' ' + inverse_setting['name'])\n", " for i, interaction in enumerate(inverse_setting['interactions']):\n", " ia_name = interaction['name']\n", " print(' ' + ia_name)\n", "\n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", " # load potential and rdf\n", " U_file = (f\"{working_dir}/segment{final_potential_from_segment_step[inverse_setting['name']][0]}\"\n", " f\"/step_{final_potential_from_segment_step[inverse_setting['name']][1]:03d}/{ia_name}.pot.cur\")\n", " U_data = np.loadtxt(U_file, dtype=str, comments=['#', '@'])\n", " r = U_data[:, 0].astype(float)\n", " U = U_data[:, 1].astype(float)\n", " g_file = (f\"{working_dir}/segment{final_potential_from_segment_step[inverse_setting['name']][0]}\"\n", " f\"/step_{final_potential_from_segment_step[inverse_setting['name']][1]:03d}/{ia_name}.dist.new\")\n", " g_data = np.loadtxt(g_file, dtype=str, comments=['#', '@'])\n", " r_g = g_data[:, 0].astype(float)\n", " g = g_data[:, 1].astype(float)\n", " if not np.allclose(r, r_g):\n", " print(\"grid of g and U differ, interpolating g..\")\n", " g = np.interp(r, r_g, g)\n", " U_dict[inverse_setting['name'], ia_name] = U\n", " g_dict[inverse_setting['name'], ia_name] = g\n", " r_dict[inverse_setting['name'], ia_name] = r\n", " if i == 1 and max(r) < max(r_shortest):\n", " r_shortest = r\n", " # cut OW-CL interactions\n", " # needs to be done for all before averaging\n", " for inverse_setting in ff_inverse_settings:\n", " interaction = inverse_setting['interactions'][1]\n", " ia_name = interaction['name']\n", " int_key = (inverse_setting['name'], ia_name)\n", " U_dict[int_key] = np.interp(r_shortest, r_dict[int_key], U_dict[int_key])\n", " g_dict[int_key] = np.interp(r_shortest, r_dict[int_key], g_dict[int_key])\n", " for inverse_setting in ff_inverse_settings:\n", " for i, interaction in enumerate(inverse_setting['interactions']):\n", " ia_name = interaction['name']\n", " int_key = (inverse_setting['name'], ia_name)\n", " if i == 1:\n", " # average OW-CL interactions\n", " U_mean = np.mean([U for (invset, ia), U in U_dict.items() if ia == ia_name], axis=0)\n", " g_mean = np.mean([g for (invset, ia), g in g_dict.items() if ia == ia_name], axis=0)\n", " r = r_shortest\n", " else:\n", " U_mean = None\n", " g_mean = None\n", " r = r_dict[int_key]\n", " U = U_dict[int_key]\n", " g = g_dict[int_key]\n", " iff_pot_fit_dict[(inverse_setting['name'], ia_name, 'data')] = (r, g, U, None, None, None, None, g_mean, U_mean)\n", "\n", "load_data()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def test():\n", " r, g, u, _, _, _, _, g_mean, u_mean = iff_pot_fit_dict[('water5000-cacl2_50/altern5-netz-co0.9-nopc', 'OW-CA', 'data')]\n", " plt.plot(r, g)\n", " #plt.plot(r, g_mean)\n", " plt.ylim(0, 0.2)\n", " plt.show()\n", " \n", " plt.plot(r, u)\n", " plt.ylim(-10, 100)\n", " plt.show()\n", "\n", "test()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### fit all potentials" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def fit_all():\n", " for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " for i, interaction in enumerate(inverse_setting['interactions']):\n", " print(' ' + interaction['name'])\n", " ia_name = interaction['name']\n", " # load data\n", " (r, g, U, _, _, _, _, g_mean, U_mean) = iff_pot_fit_dict[(inverse_setting['name'], ia_name, 'data')]\n", " # slice for fit (from first non-zero g)\n", " start = np.min(np.where(g > 1e-10))\n", " sl = slice(start, None)\n", " # fit\n", " for mean in ((False, True) if i == 1 else (False,)):\n", " print('mean' if mean else 'not mean')\n", " U_used = U_mean if mean else U\n", " g_used = g_mean if mean else g\n", " # sigma for fit\n", " with np.errstate(divide='ignore'):\n", " sigma = 1 / np.sqrt(g_used)\n", " ia_name_used = ia_name + '-mean' if mean else ia_name\n", " for fit_func_name, fit_func in fit_functions.items():\n", " #print(fit_func_name)\n", " p0 = fit_func['p0']\n", " bounds = fit_func['bounds']\n", " popt, pcov = optimize.curve_fit(fit_func['func'], r[sl], U_used[sl], p0=p0, sigma=sigma[sl], bounds=bounds, maxfev=int(2e5))\n", " iff_pot_fit_dict[(inverse_setting['name'], ia_name_used, fit_func_name)] = (r, g_used, U_used, popt, p0, sigma, sl, None, None)\n", " if fit_func_name == 'LJ-n-6':\n", " print(f\"n: {popt[1]:.1f}\")\n", "\n", "fit_all()" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "### show some fits" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def show_some_fits():\n", " for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " for i, interaction in enumerate(inverse_setting['interactions']):\n", " print(' ' + interaction['name'])\n", " ia_name = interaction['name']\n", " # load data\n", " (r, g, U, _, _, _, _, g_mean, U_mean) = iff_pot_fit_dict[(inverse_setting['name'], ia_name, 'data')]\n", " # slice for fit (from first non-zero g)\n", " start = np.min(np.where(g > 1e-10))\n", " sl = slice(start, None)\n", " # fit\n", " for mean in ((False, True) if i == 1 else (False,)):\n", " print('mean' if mean else 'not mean')\n", " U_used = U_mean if mean else U\n", " g_used = g_mean if mean else g\n", " # sigma for fit\n", " with np.errstate(divide='ignore'):\n", " sigma = 1 / np.sqrt(g_used)\n", " ia_name_used = ia_name + '-mean' if mean else ia_name\n", " for fit_func_name, fit_func in {\n", " k: v for k, v in fit_functions.items()\n", " if k in (\n", " 'LJ-n-6',\n", " )}.items():\n", " #print(fit_func_name)\n", " (r, g_used, U_used, popt, p0, sigma, sl, _, _) = iff_pot_fit_dict[(inverse_setting['name'], ia_name_used, fit_func_name)]\n", " print(f\"n: {popt[1]:.4f}\")\n", " print(popt[0], popt[2])\n", " fig, (ax0, ax1) = plt.subplots(ncols=2)\n", " for ax in (ax0, ax1):\n", " ax.plot(r, U_used, label='IMC')\n", " ax.plot(r, fit_func['func'](r, *popt), label='fit')\n", " ax0.set_ylim(-10, 10)\n", " ax1.set_yscale('log')\n", " ax1.set_ylim(0.5, 1000)\n", " ax1.legend()\n", " plt.show()\n", "\n", "show_some_fits()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### fit repulsive and attractive separately and paper table" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def fit_sep_all(show_plots=False):\n", " \n", " \n", " for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " for i, interaction in enumerate(inverse_setting['interactions']):\n", " print(' ' + interaction['name'])\n", " ia_name = interaction['name']\n", " # load data\n", " (r, g, U, _, _, _, _, g_mean, U_mean) = iff_pot_fit_dict[(inverse_setting['name'], ia_name, 'data')]\n", " # slice for fit (from first non-zero g)\n", " # fit\n", " for mean in ((False, True) if i == 1 else (False,)):\n", " print('mean' if mean else 'not mean')\n", " U_used = U_mean if mean else U\n", " g_used = g_mean if mean else g\n", " #start_att = int(np.argmin(U_used) * 2**(1/6))\n", " start_att = np.argmin(U_used)\n", " start_rep = np.min(np.where(g_used > 1e-10))\n", " #start_rep = np.min(np.where(g_used > 0.15))\n", " #end_rep = (np.argmax(g_used) + start_att) // 2\n", " end_rep = np.argmax(g_used) #+ 4\n", " #end_rep = start_att\n", " sl_rep = slice(start_rep, end_rep)\n", " sl_att = slice(start_att, None)\n", " # sigma for fit\n", " with np.errstate(divide='ignore'):\n", " sigma = 1 / np.sqrt(g_used)\n", " ia_name_used = ia_name + '-mean' if mean else ia_name\n", " # fit attractive\n", " bounds = ((0,), (np.inf,))\n", " sigma_att = sigma[sl_att]\n", " #sigma_att = r[sl_att] ** (-1)\n", " popt_att, pcov_att = optimize.curve_fit(gen_att_6_potential, r[sl_att], U_used[sl_att], p0=1e-4, sigma=sigma_att, bounds=bounds, maxfev=int(2e5))\n", " # fit repulsive\n", " U_rep = U_used - gen_att_6_potential(r, popt_att)\n", " popt_rep, pcov_rep = optimize.curve_fit(gen_rep_power_potential, r[sl_rep], U_rep[sl_rep], p0=(1, -8), sigma=sigma[sl_rep], maxfev=int(2e5))\n", " perr_rep = np.sqrt(np.diag(pcov_rep))\n", " #print(f\"{popt_rep[1]:.1f} ± {perr_rep[1]:.1f}\")\n", " \n", " if show_plots:\n", " fig, (ax0, ax1) = plt.subplots(ncols=2)\n", " ax0.plot(r, U_used)\n", " ax0.plot(r, gen_att_6_potential(r, popt_att))\n", " ax0.plot(r, gen_att_6_potential(r, popt_att) + gen_rep_power_potential(r, *popt_rep), label=f\"{popt_rep[1]:.1f}\")\n", " ax0.set_ylim(-10, 10)\n", " ax0.legend()\n", " ax1.plot(r, U_used - gen_att_6_potential(r, popt_att))\n", " ax1.plot(r, gen_rep_power_potential(r, *popt_rep), label=f\"{popt_rep[1]:.1f}\")\n", " ax1.set_yscale('log')\n", " ax1.set_ylim(0.5, 1000)\n", " ax1.legend()\n", " plt.show()\n", " \n", " fit_func_name = 'rep-att'\n", " iff_pot_fit_dict[(inverse_setting['name'], ia_name_used, fit_func_name)] = (r, g_used, U_used, (popt_att, popt_rep), None, sigma, (sl_att, sl_rep), None, None)\n", "\n", "fit_sep_all(show_plots=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "tags": [] }, "outputs": [], "source": [ "def make_sep_fit_table():\n", " index = pd.MultiIndex.from_product((['HMN IMC', 'ECC IMC'], nb_plot_name.values()))\n", " columns = ('C_rep', 'exp', 'C_6')\n", " columns_final = (r'{$C_\\text{rep}$}', '{$n$}', r'{$C_6$}')\n", " df_rep_fit = pd.DataFrame(index=index, columns=columns)\n", " for inverse_setting in inverse_setup_generator(system_types_inv, force_fields_inv, verbose=False):\n", " print(inverse_setting['name'])\n", " for i, interaction in enumerate(inverse_setting['interactions']):\n", " print(' ' + interaction['name'])\n", " ia_name = interaction['name']\n", " mean = i == 1\n", " print('mean' if mean else 'not mean')\n", " ia_name_used = ia_name + '-mean' if mean else ia_name\n", " # load data\n", " fit_func_name = 'rep-att'\n", " (_, _, _, (popt_att, popt_rep), _, _, (_, _), _, _) = iff_pot_fit_dict[(inverse_setting['name'], ia_name_used, fit_func_name)]\n", "\n", " df_rep_fit.at[(ff_short_names[inverse_setting['ff-name']], nb_plot_name[ia_name]), 'C_rep'] = f\"{popt_rep[0]:.2e}\"\n", " df_rep_fit.at[(ff_short_names[inverse_setting['ff-name']], nb_plot_name[ia_name]), 'exp'] = f\"{-popt_rep[1]:.1f}\" # ± {perr_rep[1]:.1f}\"\n", " df_rep_fit.at[(ff_short_names[inverse_setting['ff-name']], nb_plot_name[ia_name]), 'C_6'] = f\"{popt_att[0]:.2e}\"\n", " df_rep_fit.columns = columns_final\n", " print(df_rep_fit.to_latex(column_format='l l S[table-format=1.2e3] S[table-format=2.1] S[table-format=1.2e1]', escape=False))\n", "\n", "make_sep_fit_table()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### plot fit for repulsive potential" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "def plot_sep(use_gradient=True, show_fits=True, show_grid=True, show_labels=True):\n", " \n", " mpl_rc_local = {\n", " 'legend.handlelength': 1.2,\n", " 'legend.labelspacing': 0.2,\n", " }\n", " \n", " label_positioning = {\n", " (0, 'OW-CL'): ('left', 'bottom', 0.0, 0.0),\n", " (0, 'OW-K'): ('left', 'bottom', 0.0, 0.0),\n", " (0, 'OW-NA'): ('right', 'top', -0.002, 0.0),\n", " (0, 'OW-LI'): ('right', 'top', -0.002, 0.0),\n", " (0, 'OW-CA'): ('left', 'bottom', 0.0, 0.0),\n", " (1, 'OW-CL'): ('left', 'bottom', 0.0, 0.0),\n", " (1, 'OW-K'): ('right', 'top', 0.0, -1.5),\n", " (1, 'OW-NA'): ('right', 'top', -0.002, 0.0),\n", " (1, 'OW-CA'): ('left', 'bottom', 0.0, 0.0),\n", " }\n", " \n", " with plt.rc_context({**mpl_rc_global, **mpl_rc_local}):\n", " fig, axes = plt.subplots(nrows=1, ncols=2, constrained_layout=True, figsize=(3.9, 2.1), dpi=200)\n", " fig.set_constrained_layout_pads(w_pad=0.01, h_pad=0.01)\n", " legend_handles = []\n", " legend_labels = []\n", " for f, (ff_name, ff_inverse_settings) in enumerate(itertools.groupby(sorted(inverse_setup_generator(system_types_inv, force_fields_inv),\n", " key=operator.itemgetter('ff-name'), reverse=False),\n", " key=operator.itemgetter('ff-name'))):\n", " ax = axes[f]\n", " ax.set_title(ff_short_names[ff_name])\n", " #print(f\" {ff_name}\")\n", " for v, inverse_setting in enumerate(ff_inverse_settings):\n", " #print(inverse_setting['name'])\n", " for i, interaction in enumerate(inverse_setting['interactions']):\n", " #print(' ' + interaction['name'])\n", " ia_name = interaction['name']\n", " mean = ia_name == 'OW-CL'\n", " # skip other mean\n", " if ia_name == 'OW-CL' and v > 0:\n", " continue\n", " #print('mean' if mean else 'not mean')\n", "\n", " # load data\n", " ia_name_used = ia_name + '-mean' if mean else ia_name\n", " (r, g_used, U_used, (popt_att, popt_rep), _, sigma, (sl_att, sl_rep), _, _) = iff_pot_fit_dict[(inverse_setting['name'], ia_name_used, 'rep-att')]\n", " \n", " # plot potential\n", " U_rep = U_used - gen_att_6_potential(r, popt_att)\n", " label = nb_plot_name[ia_name]\n", " if use_gradient:\n", " # multicolored line\n", " points = np.array([r, U_rep]).T.reshape(-1, 1, 2)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", " # Create a continuous norm to map from data points to colors\n", " #norm = plt.Normalize(-1, max(g_used))\n", " norm = plt.Normalize(-0.2 * max(g_used), max(g_used))\n", " cmap = nb_plot_cmap[ia_name]\n", " lc = mpl.collections.LineCollection(segments, cmap=cmap, norm=norm)\n", " # Set the values used for colormapping\n", " lc.set_array(g_used)\n", " #lc.set_linewidth(2)\n", " line = ax.add_collection(lc)\n", " if f == 0:\n", " legend_handles.append(mpl.lines.Line2D([0], [0], color=nb_plot_color[ia_name], lw=1))\n", " legend_labels.append(label)\n", " else:\n", " color = nb_plot_color[ia_name]\n", " line, = ax.plot(r, U_rep, label=label, color=color, linestyle='-')\n", " if f == 0:\n", " legend_handles.append(line)\n", " legend_labels.append(label)\n", " \n", " # plot fit\n", " if show_fits:\n", " fit_width = 5\n", " color = nb_plot_color[ia_name]\n", " start = np.argmax(g_used) - fit_width\n", " end = np.argmax(g_used) + fit_width\n", " #print(start, end, popt_rep)\n", " sl = slice(start, end)\n", " x = r[sl]\n", " y = gen_rep_power_potential(r, *popt_rep)[sl]\n", " ax.plot(x, y, label=label, color=color, linestyle='--')\n", " \n", " if show_labels:\n", " ha, va, x_offset, y_offset = ('right', 'top', 0.0, 0.0)\n", " if (f, ia_name) in label_positioning:\n", " print(f, ia_name)\n", " ha, va, x_offset, y_offset = label_positioning[(f, ia_name)]\n", " ax.text(x[fit_width] + x_offset, y[fit_width] + y_offset, f'{-popt_rep[1]:.1f}',\n", " color=color, horizontalalignment=ha, verticalalignment=va)\n", " \n", " \n", " # plot r^-8 and r^-12\n", " if show_fits:\n", " for ax in axes:\n", " sl = slice(63, 73)\n", " x = r[sl]\n", " y = gen_rep_power_potential(r, 3e-4, -12)[sl]\n", " ax.plot(x, y, color='grey', linestyle='--')\n", " ax.text(np.mean(x), np.mean(y), r'$r^{-12}$', horizontalalignment='left', verticalalignment='center',)\n", " sl = slice(63, 73)\n", " x = r[sl]\n", " y = gen_rep_power_potential(r, 4e-2, -7)[sl]\n", " ax.plot(x, y, color='k', linestyle='--')\n", " ax.text(np.mean(x), np.mean(y)-7, r'$r^{-7}$', horizontalalignment='right', verticalalignment='top',)\n", " \n", " # show skewed grid\n", " if show_fits and show_grid:\n", " for ax in axes:\n", " x = r\n", " for a in np.logspace(-2, -8, num=10):\n", " y = gen_rep_power_potential(r, a, -12)\n", " ax.plot(x, y, color='grey', linestyle=':', linewidth=0.5)\n", " for a in np.logspace(-1, -5, num=10):\n", " y = gen_rep_power_potential(r, a, -8)\n", " ax.plot(x, y, color='darkgrey', linestyle=':', linewidth=0.5)\n", " \n", " for ax in axes:\n", " ax.set_xscale('log')\n", " ax.set_yscale('log')\n", " ax.set_xticks(np.arange(0.15, 0.4, 0.05))\n", " ax.set_xlim(0.17, 0.39)\n", " ax.set_ylim(2, 300)\n", " ax.get_xaxis().set_major_formatter(mpl.ticker.ScalarFormatter())\n", " ax.get_xaxis().set_minor_formatter(mpl.ticker.ScalarFormatter())\n", " legend_order = [0, 2, 3, 4, 1]\n", " axes[1].legend((legend_handles[ndx] for ndx in legend_order), (legend_labels[ndx] for ndx in legend_order), loc='lower left')\n", " axes[1].yaxis.set_major_formatter(mpl.ticker.NullFormatter())\n", " axes[1].yaxis.set_minor_formatter(mpl.ticker.NullFormatter())\n", " axes[0].set_xlabel(r'$r$ in nm')\n", " axes[1].set_xlabel(r'$r$ in nm')\n", " #fig.supxlabel(r'$r$ in nm')\n", " axes[0].set_ylabel(r'$u_\\mathrm{rep}(r)$ in kJ/mol')\n", " fig.savefig(os.path.join('..', 'figures', f\"fit-repulsive.pdf\"))\n", " plt.show()\n", "\n", "#plot_sep(False, False, False)\n", "#plot_sep(True, False, False)\n", "plot_sep(True, True, False, True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!cp -a ../figures/fit-repulsive.pdf ~/research/output/ion-shortrange-paper/figures/" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "# for trr146 b1 proposal\n", "def plot_sep_nico(use_gradient=True, show_fits=True, show_grid=True, show_labels=True, show_steepness=True):\n", " \n", " mpl_rc_local = {\n", " 'legend.handlelength': 1.2,\n", " 'legend.labelspacing': 0.2,\n", " }\n", " \n", " label_positioning = {\n", " (0, 'OW-CL'): ('left', 'bottom', 0.0, 0.0),\n", " (0, 'OW-K'): ('left', 'bottom', -0.005, 0.0),\n", " (0, 'OW-CA'): ('left', 'bottom', 0.0, 0.0),\n", " (1, 'OW-CL'): ('left', 'bottom', 0.0, 0.0),\n", " (1, 'OW-K'): ('right', 'top', 0.005, -1.5),\n", " (1, 'OW-CA'): ('left', 'bottom', 0.0, 0.0),\n", " }\n", " \n", " with plt.rc_context({**mpl_rc_global, **mpl_rc_local}):\n", " fig, ax = plt.subplots(nrows=1, ncols=1, constrained_layout=True, figsize=(2.4, 1.8), dpi=200)\n", " legend_handles = []\n", " legend_labels = []\n", " \n", " force_fields_inv = {ffn: ff for ffn, ff in force_fields.items() if ffn in ('netz-co0.9',)}\n", " system_types_inv = {stn: st for stn, st in system_types.items() if st['name'] == 'water-cacl2_'} # not pure water\n", " inverse_setting = next(inverse_setup_generator(system_types_inv, force_fields_inv))\n", " ff_name = inverse_setting['ff-name']\n", " print(f\" {ff_name}\")\n", " print(inverse_setting['name'])\n", " interaction = inverse_setting['interactions'][0]\n", " print(' ' + interaction['name'])\n", " ia_name = interaction['name']\n", " mean = False\n", " # load data\n", " ia_name_used = ia_name + '-mean' if mean else ia_name\n", " (r, g_used, U_used, (popt_att, popt_rep), _, sigma, (sl_att, sl_rep), _, _) = iff_pot_fit_dict[(inverse_setting['name'], ia_name_used, 'rep-att')]\n", "\n", " # plot potential\n", " U_rep = U_used - gen_att_6_potential(r, popt_att)\n", " label = nb_plot_name[ia_name]\n", " if use_gradient:\n", " # multicolored line\n", " points = np.array([r, U_rep]).T.reshape(-1, 1, 2)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", " # Create a continuous norm to map from data points to colors\n", " #norm = plt.Normalize(-1, max(g_used))\n", " norm = plt.Normalize(-0.2 * max(g_used), max(g_used))\n", " cmap = nb_plot_cmap[ia_name]\n", " lc = mpl.collections.LineCollection(segments, cmap=cmap, norm=norm)\n", " # Set the values used for colormapping\n", " lc.set_array(g_used)\n", " #lc.set_linewidth(2)\n", " line = ax.add_collection(lc)\n", " legend_handles.append(mpl.lines.Line2D([0], [0], color=nb_plot_color[ia_name], lw=1))\n", " legend_labels.append('IMC')\n", " else:\n", " color = nb_plot_color[ia_name]\n", " line, = ax.plot(r, U_rep, label=label, color=color, linestyle='-')\n", " if f == 0:\n", " legend_handles.append(line)\n", " legend_labels.append(label)\n", " \n", " # plot LJ potential of reference force field\n", " r_g_ref = npt_system_interaction_dict[('water5000-cacl2_50/netz-co0.9tc', 'OW-CA', 'r')]\n", " g_ref = npt_system_interaction_dict[('water5000-cacl2_50/netz-co0.9tc', 'OW-CA', 'g')]\n", " pff = PARAMETRIC_FORCE_FIELDS['netz']\n", " at1 = next((at for at in pff['atomtypes'] if at['type'] == interaction['type1']))\n", " at2 = next((at for at in pff['atomtypes'] if at['type'] == interaction['type2']))\n", " U_ref_rep, _ = gen_potential_and_force(at1, at2, r_g_ref, pff['combining-rule'], False, pff['nonbond-params'], repulsive_only=True)\n", " if use_gradient:\n", " # multicolored line\n", " points = np.array([r_g_ref, U_ref_rep]).T.reshape(-1, 1, 2)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", " # Create a continuous norm to map from data points to colors\n", " #norm = plt.Normalize(-1, max(g_used))\n", " norm = plt.Normalize(-0.2 * max(g_ref), max(g_ref))\n", " cmap = nb_plot_cmap['OW-LI']\n", " lc = mpl.collections.LineCollection(segments, cmap=cmap, norm=norm)\n", " # Set the values used for colormapping\n", " lc.set_array(g_ref)\n", " #lc.set_linewidth(2)\n", " line = ax.add_collection(lc)\n", " legend_handles.append(mpl.lines.Line2D([0], [0], color=nb_plot_color['OW-LI'], lw=1))\n", " legend_labels.append('LJ')\n", " else:\n", " line, = ax.plot(r, U_ref_rep, label=label, color='red', linestyle='-')\n", "\n", " # plot fit\n", " if show_fits:\n", " fit_width = 5\n", " color = nb_plot_color[ia_name]\n", " start = np.argmax(g_used) - fit_width\n", " end = np.argmax(g_used) + fit_width\n", " #print(start, end, popt_rep)\n", " sl = slice(start, end)\n", " x = r[sl]\n", " y = gen_rep_power_potential(r, *popt_rep)[sl]\n", " ax.plot(x, y, label=label, color=color, linestyle='--')\n", "\n", " if show_labels:\n", " ha, va, x_offset, y_offset = ('right', 'top', 0.0, 0.0)\n", " if (f, ia_name) in label_positioning:\n", " print(f, ia_name)\n", " ha, va, x_offset, y_offset = label_positioning[(f, ia_name)]\n", " ax.text(x[fit_width] + x_offset, y[fit_width] + y_offset, f'{-popt_rep[1]:.1f}',\n", " color=color, horizontalalignment=ha, verticalalignment=va)\n", "\n", " \n", " # plot r^-8 and r^-12\n", " if show_steepness:\n", " sl = slice(55, 65)\n", " x = r[sl]\n", " y = gen_rep_power_potential(r, 5e-5, -12)[sl]\n", " ax.plot(x, y, color='grey', linestyle='--')\n", " ax.text(np.mean(x), np.mean(y), r'$r^{-12}$', horizontalalignment='left', verticalalignment='center',)\n", " sl = slice(55, 65)\n", " x = r[sl]\n", " y = gen_rep_power_potential(r, 1.2e-2, -7)[sl]\n", " ax.plot(x, y, color='k', linestyle='--')\n", " ax.text(np.mean(x), np.mean(y)-5, r'$r^{-7}$', horizontalalignment='right', verticalalignment='top',)\n", " \n", " # show skewed grid\n", " if show_fits and show_grid:\n", " x = r\n", " for a in np.logspace(-2, -8, num=10):\n", " y = gen_rep_power_potential(r, a, -12)\n", " ax.plot(x, y, color='grey', linestyle=':', linewidth=0.5)\n", " for a in np.logspace(-1, -5, num=10):\n", " y = gen_rep_power_potential(r, a, -8)\n", " ax.plot(x, y, color='darkgrey', linestyle=':', linewidth=0.5)\n", " \n", " #ax.set_title(ff_short_names[ff_name])\n", " ax.set_xscale('log')\n", " ax.set_yscale('log')\n", " ax.set_xticks(np.arange(0.15, 0.4, 0.05))\n", " ax.set_xlim(0.20, 0.34)\n", " ax.set_ylim(8, 300)\n", " ax.get_xaxis().set_major_formatter(mpl.ticker.ScalarFormatter())\n", " ax.get_xaxis().set_minor_formatter(mpl.ticker.ScalarFormatter())\n", " #legend_order = [0]\n", " ax.legend(legend_handles, legend_labels, loc='lower left')\n", " #ax.legend((legend_handles[ndx] for ndx in legend_order), (legend_labels[ndx] for ndx in legend_order), loc='lower left')\n", " \n", " #ax.yaxis.set_major_formatter(mpl.ticker.NullFormatter())\n", " #ax.yaxis.set_minor_formatter(mpl.ticker.NullFormatter())\n", " ax.set_xlabel(r'$r$ in nm')\n", " ax.set_xlabel(r'$r$ in nm')\n", " ax.set_ylabel(r'$u_\\mathrm{rep}(r)$ in kJ/mol')\n", " fig.savefig(os.path.join('..', 'figures', f\"repulsive-trr-proposal.pdf\"))\n", " plt.show()\n", "\n", "plot_sep_nico(True, False, False, False, True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### potential plot for SI" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def plot_potential_SI(use_gradient, show_attractive_fit_only=True):\n", " \n", " mpl_rc_local = {\n", " 'legend.handlelength': 1.6,\n", " 'legend.labelspacing': 0.2,\n", " }\n", " \n", " with plt.rc_context({**mpl_rc_global, **mpl_rc_local}):\n", " fig, axes = plt.subplots(nrows=1, ncols=2, constrained_layout=True, figsize=(4.4, 2.1), dpi=200)\n", " legend_handles = []\n", " legend_labels = []\n", " for f, (ff_name, ff_inverse_settings) in enumerate(itertools.groupby(sorted(inverse_setup_generator(system_types_inv, force_fields_inv),\n", " key=operator.itemgetter('ff-name'), reverse=False),\n", " key=operator.itemgetter('ff-name'))):\n", " ax = axes[f]\n", " ax.set_title(ff_short_names[ff_name])\n", " for v, inverse_setting in enumerate(ff_inverse_settings):\n", " for i, interaction in enumerate(inverse_setting['interactions']):\n", " ia_name = interaction['name']\n", " mean = ia_name == 'OW-CL'\n", " # skip other mean\n", " if ia_name == 'OW-CL' and v > 0:\n", " continue\n", " # load data\n", " ia_name_used = ia_name + '-mean' if mean else ia_name\n", " (r, g_used, U_used, (popt_att, popt_rep), _, sigma, (sl_att, sl_rep), _, _) = iff_pot_fit_dict[(inverse_setting['name'], ia_name_used, 'rep-att')]\n", " # plot potential\n", " label = nb_plot_name[ia_name]\n", " color = nb_plot_color[ia_name]\n", " if use_gradient:\n", " # multicolored line\n", " points = np.array([r, U_used]).T.reshape(-1, 1, 2)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", " # Create a continuous norm to map from data points to colors\n", " #norm = plt.Normalize(-0.2 * max(g_used), 0.5*max(g_used))\n", " #norm = plt.Normalize(-0.6, 4.5)\n", " norm = plt.Normalize(-0.2 * max(g_used), 0.8*max(g_used))\n", " cmap = nb_plot_cmap[ia_name]\n", " lc = mpl.collections.LineCollection(segments, cmap=cmap, norm=norm)\n", " # Set the values used for colormapping\n", " lc.set_array(g_used)\n", " #lc.set_linewidth(2)\n", " line = ax.add_collection(lc)\n", " if f == 0:\n", " legend_handles.append(mpl.lines.Line2D([0], [0], color=nb_plot_color[ia_name], lw=1))\n", " legend_labels.append(label)\n", " else:\n", " color = nb_plot_color[ia_name]\n", " line, = ax.plot(r, U_used, label=label, color=color, linestyle='-')\n", " if f == 0:\n", " legend_handles.append(line)\n", " legend_labels.append(label)\n", " if show_attractive_fit_only:\n", " ax.plot(r, gen_att_6_potential(r, popt_att), linestyle='--', color=color, zorder=10, linewidth=0.8)\n", " else:\n", " ax.plot(r, gen_att_6_potential(r, popt_att) + gen_rep_power_potential(r, *popt_rep), linestyle='--', color=color, zorder=10, linewidth=0.8)\n", "\n", " ax.set_title(ff_short_names[inverse_setting['ff-name']] + ' ' + nb_plot_name[ia_name])\n", " ax.set_xlim(0.16, 0.75)\n", " ax.set_ylim(-7, 17)\n", " ax.set_xlabel(r'$r$ in nm')\n", " ax.set_title(ff_short_names[ff_name])\n", " axes[0].set_ylabel(r'$u_\\mathrm{att}(r)$ in kJ/mol')\n", " legend_order = [0, 2, 3, 4, 1]\n", " axes[1].legend((legend_handles[ndx] for ndx in legend_order), (legend_labels[ndx] for ndx in legend_order), loc='upper right')\n", " fig.savefig(os.path.join('..', 'figures', f\"fit-attractive.pdf\"))\n", " plt.show()\n", "\n", "plot_potential_SI(use_gradient=True, show_attractive_fit_only=False)\n", "plot_potential_SI(use_gradient=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!cp -a ../figures/fit-attractive.pdf ~/research/output/ion-shortrange-paper/figures/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### single potential plot for presentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def plot_single(show_attr=True):\n", " \n", " mpl_rc_local = {\n", " 'legend.handlelength': 1.6,\n", " 'legend.labelspacing': 0.3,\n", " }\n", " \n", " with plt.rc_context({**mpl_rc_global, **mpl_rc_local}):\n", " fig, ax = plt.subplots(constrained_layout=True, figsize=(2.0, 1.5), dpi=200)\n", " inverse_setting = next(inverse_setup_generator(system_types_inv, force_fields_inv))\n", " interaction = inverse_setting['interactions'][0]\n", " ia_name = interaction['name']\n", " mean = ia_name == 'OW-CL'\n", " # load data\n", " ia_name_used = ia_name + '-mean' if mean else ia_name\n", " (r, g_used, U_used, (popt_att, popt_rep), _, sigma, (sl_att, sl_rep), _, _) = iff_pot_fit_dict[(inverse_setting['name'], ia_name_used, 'rep-att')]\n", " # plot potential\n", " label = nb_plot_name[ia_name]\n", " color = nb_plot_color[ia_name]\n", " ax.plot(r, U_used, label=label, color=color, linestyle='-')\n", " if show_attr:\n", " ax.plot(r, gen_att_6_potential(r, popt_att), linestyle='--')\n", "\n", " ax.set_title(ff_short_names[inverse_setting['ff-name']] + ' ' + nb_plot_name[ia_name])\n", " ax.set_xlim(0, 0.75)\n", " ax.set_ylim(-15, 80)\n", " ax.set_xlabel(r'$r$ in nm')\n", " ax.set_ylabel(r'$U_\\mathrm{}(r)$ in kJ/mol')\n", " plt.show()\n", "\n", "plot_single(False)\n", "plot_single(True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### make parametric fit force fields" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# show fit parameters\n", "df_fit = pd.DataFrame(iff_pot_fit_dict).transpose()\n", "df_fit.columns = \"r, g_used, U_used, popt, p0, sigma, sl, g_mean, U_mean\".split(', ')\n", "df_fit.loc[(slice(None), slice(None), 'LJ-12-6'), 'popt']" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "parametric_fit_force_fields = {}\n", "\n", "def make_fit_pfff():\n", " for pff, pfff, iff in (\n", " ('netz', 'fit-iff-netz', 'altern5-netz-co0.9-nopc'),\n", " ('eccr1', 'fit-iff-eccr1', 'altern5-eccr1-co1.2-nopc'),\n", " ):\n", " parametric_fit_force_fields[pfff] = deepcopy(PARAMETRIC_FORCE_FIELDS[pff])\n", " at_ow = next((at for at in parametric_fit_force_fields[pfff]['atomtypes'] if at['type'] == 'OW'))\n", " sig_eps_ow = at_ow['σ'], at_ow['ε']\n", " parametric_fit_force_field = parametric_fit_force_fields[pfff]\n", " atomtypes_new = []\n", " #'nonbond-params': {\n", " #frozenset(('A1', 'A2')): ('sigma', 'epsilon'),\n", " #frozenset(('CL', 'OW')): ('LJ', 0.42386698, 0.06198347),\n", " nonbond_params = {}\n", " for at in parametric_fit_force_field['atomtypes']:\n", " print(at['type'])\n", " at_new = deepcopy(at)\n", " if at['type'] in {'LI', 'NA', 'K', 'CA', 'CL'}:\n", " print(at['σ'], at['ε'], *sig_eps_ow, parametric_fit_force_field['combining-rule'])\n", " if at['type'] == 'CA':\n", " system_name = f\"water5000-{at['type'].lower()}cl2_50/{iff}\" \n", " ia_name = f\"OW-{at['type']}\"\n", " elif at['type'] == 'CL':\n", " system_name = f\"water5000-nacl50/{iff}\"\n", " ia_name = f\"OW-{at['type']}-mean\"\n", " else:\n", " system_name = f\"water5000-{at['type'].lower()}cl50/{iff}\" \n", " ia_name = f\"OW-{at['type']}\"\n", " A, B = df_fit.at[(system_name, ia_name, 'LJ-12-6'), 'popt']\n", " sig_eps_fit = (A / B)**(1/6), B**2 / (4 * A)\n", " sigma, epsilon = decombinate_LJ(*sig_eps_fit, *sig_eps_ow, parametric_fit_force_field['combining-rule'])\n", " at_new['σ'] = sigma\n", " at_new['ε'] = epsilon\n", " nonbond_params[frozenset({'OW', at['type']})] = ('LJ', *sig_eps_fit)\n", " atomtypes_new.append(at_new)\n", " parametric_fit_force_fields[pfff]['atomtypes'] = atomtypes_new\n", " parametric_fit_force_fields[pfff]['nonbond-params'] = nonbond_params\n", " \n", "make_fit_pfff()\n", "pff_calc_C6_C12(parametric_fit_force_fields)\n", "\n", "parametric_fit_force_fields['fit-iff-netz'], PARAMETRIC_FORCE_FIELDS['netz']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with open('parametric-fit-force-fields.pkl', 'wb') as f:\n", " pickle.dump(parametric_fit_force_fields, f)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with open('parametric-fit-force-fields.pkl', 'rb') as f:\n", " parametric_fit_force_fields = pickle.load(f)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### save potential fits" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# safe fits to table\n", "# oxygen-chloride is average over cations\n", "def safe_fits():\n", " \n", " overwrite = False\n", " \n", " r_table = np.linspace(0, 3, num=1501)\n", " for fit_func_name, fit_func in fit_functions.items():\n", " print(f\"{fit_func_name}\")\n", " for j, inverse_setting in enumerate(inverse_setup_generator(system_types_inv, force_fields_inv)):\n", " print(f\" {inverse_setting['name']}\")\n", " for i, interaction in enumerate(inverse_setting['interactions']):\n", " print(f\" {interaction['name']}\")\n", " ia_name = interaction['name']\n", " ia_name_used = ia_name + '-mean' if i == 1 else ia_name\n", " _, _, _, popt, _, _, _, _, _ = iff_pot_fit_dict[(inverse_setting['name'], ia_name_used, fit_func_name)]\n", " U_fit = fit_func['func'](r_table, *popt)\n", " U_fit = np.nan_to_num(U_fit)\n", " force = -1/2 * (np.diff(U_fit, prepend=0) + np.diff(U_fit, append=0)) / (r_table[1] - r_table[0])\n", " first_U_ndx = max((np.argmax(U_fit)+1, np.argmax(np.nonzero(np.abs(U_fit) > 1e6))+1))\n", " \n", " #f_fill = 0 # physical for constant potential\n", " # not physical but prevents problems with steepest decent\n", " # Could cause problems with TI\n", " f_fill = force[first_U_ndx]\n", " \n", " force[0:first_U_ndx] = f_fill\n", " U_fit[0:first_U_ndx] = U_fit[first_U_ndx]\n", " U_fit = np.nan_to_num(U_fit, nan=U_fit[first_U_ndx])\n", " force = np.nan_to_num(force, nan=f_fill)\n", " table_name = \"table_\" + interaction['name'].replace('-', '_') + '.xvg'\n", " zeros = np.zeros_like(r_table)\n", " # save table temp\n", " table1 = f\"/tmp/{inverse_setting['name'].replace('/', '_')}-{fit_func_name}-{table_name}\"\n", " np.savetxt(table1,\n", " np.stack((r_table, zeros, zeros, zeros, zeros, U_fit, force)).T,\n", " header=f\"{fit_func_name} {str(popt)}\")\n", " # check and save in template\n", " table2 = f\"template/table/{fit_func_name}-{inverse_setting['ff-name']}/{table_name}\"\n", " run_bash(f\"mkdir -p template/table/{fit_func_name}-{inverse_setting['ff-name']}\")\n", " # comparison is inaccurate, due to unstable fitting\n", " skip_or_overwrite(table1, table2, overwrite, compare_numpy=True)\n", " \"\"\"\n", " if not test_files_same(table1, table2):\n", " fig, ax = plt.subplots(figsize=(17, 1.5))\n", " ax.plot(r_table, U_fit, label=table1)\n", " _, _, _, _, _, U, f = np.loadtxt(table2).transpose()\n", " ax.plot(r_table, U, label=table2)\n", " ax.set_ylim(-8, 8)\n", " ax.legend()\n", " plt.show()\n", " \"\"\"\n", " \n", "safe_fits()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### plot potential fits" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def plot_fits():\n", " for j, inverse_setting in enumerate(inverse_setup_generator(system_types_inv, force_fields_inv)):\n", " print(f\"{inverse_setting['name']}\")\n", " for interaction in inverse_setting['interactions']:\n", " print(' ' + interaction['name'])\n", " working_dir = os.path.join(inverse_setting['name'], 'both')\n", "\n", " fig, ax = plt.subplots(figsize=(4, 3))\n", " axins = inset_axes(ax, width=\"50%\", height=\"50%\", loc='upper right')\n", "\n", " for fit_func_name, fit_func in fit_functions.items():\n", " r, g, U, popt, p0, sigma, sl, g_mean, U_mean = iff_pot_fit_dict[(inverse_setting['name'], interaction['name'], fit_func_name)]\n", " U_fit = fit_func['func'](r, *popt)\n", " res = np.trapz(((g * (U_fit - U))**2), x=r)\n", " print(fit_func_name, res)\n", " for ax_ in (ax, axins):\n", " ax_.plot(r, U_fit, label=fit_func_name+f' (R={res:.2f})')\n", " for ax_ in (ax, axins):\n", " ax_.plot(r, U, color='k', label=r'$U_\\mathrm{impr.}$', zorder=-2)\n", " if U_mean is not None:\n", " ax_.plot(r, U_mean, '--', color='grey', label='target mean', zorder=-1)\n", " ax2 = ax.twinx() \n", " ax2.plot(r, 1/sigma**2, linestyle=':', color='grey', label=r'$g$')\n", " # for legend\n", " ax.plot(r, 1000 - 1/sigma**2, linestyle=':', color='grey', label=r'$g$')\n", " ax.set_xlim(0.15, 0.71)\n", " ax.set_ylim(-8, 50)\n", " #ax.set_ylim(-1, 1)\n", " ax.set_xlabel(r\"$r$ in nm\")\n", " ax.set_ylabel(r\"$U$ in kJ/mol\")\n", " ax2.set_ylabel(r\"$g$\")\n", " ax2.set_ylim(0)\n", " axins.set_xlim(r[np.argmin(U_fit)]-0.1, r[np.argmin(U_fit)]+0.1)\n", " axins.set_ylim(min(U_fit)-0.5, min(U_fit)+2.0)\n", " axins.remove()\n", " ax.legend(frameon=False, loc='upper right')\n", " fig.tight_layout()\n", " #fig.savefig(os.path.join('..', 'figures', f\"fit_{inverse_setting['name'].replace('/', '_')}_{interaction['name']}.png\"), dpi=300)\n", " plt.show()\n", "\n", "plot_fits()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### plot quality of fit" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# plot fits\n", "def plot_fit_quality():\n", " ias = ('O-Cat.', 'O-Cl')\n", " xyl = {(fit_func_name, ia): [] for fit_func_name in fit_functions.keys() for ia in ias}\n", "\n", " ff_short_names_fitplot = {\n", " 'iff-altern5-eccr1-co1.2-nopc': 'ECC',\n", " 'iff-altern5-netz-co0.9-nopc': 'Netz',\n", " }\n", "\n", " mpl_rc = {\n", " 'legend.labelspacing': 0.2,\n", " 'legend.columnspacing': 1.5,\n", " 'legend.handlelength': 3.2,\n", " }\n", " gap_extra = 0.25\n", " markers = ('D', (6, 1, 0), '+', '2', '.')\n", "\n", " with plt.rc_context({**mpl_rc_global, **mpl_rc}):\n", " force_fields_inv = {ffn: ff for ffn, ff in force_fields.items() if ffn in ('eccr1-co1.2', 'netz-co0.9')}\n", " \n", " # two different force fields\n", " for f, (ff_inv_name, ff_inv) in enumerate(force_fields_inv.items()):\n", " \n", " # four different system types per force field\n", " for s, inverse_setting in enumerate(inverse_setup_generator(system_types_inv, {ff_inv_name: ff_inv})):\n", " #print(f\"{inverse_setting['name']}\")\n", " \n", " # two interactions\n", " for i, interaction in enumerate(inverse_setting['interactions']):\n", " #print(' ' + interaction['name'])\n", "\n", " # mean for OW-CL once at end\n", " means = (False, True) if s in (3,) and i == 1 else (False,)\n", "\n", " for fit_func_name, fit_func in fit_functions.items():\n", " #print(' ' + fit_func_name)\n", " for mean in means:\n", " ia_name_used = interaction['name'] + '-mean' if mean else interaction['name']\n", " r, g, U, popt, p0, sigma, sl, g_mean, U_mean = iff_pot_fit_dict[(inverse_setting['name'], ia_name_used, fit_func_name)]\n", " U_fit = fit_func['func'](r, *popt)\n", " res = np.sqrt(1/max(r) * np.trapz(((g * (U_fit - U))**2), x=r))\n", " #print(fit_func_name, popt, res)\n", " ia = ias[i]\n", " x = len(system_types_inv) * f + gap_extra * f + s + (f if i == 1 else 0) + int(mean)\n", " label = 'avg' if mean else sys_type_short_names[inverse_setting['refsys-parametric']['type']['name']]\n", " #print(\"s, f, i, x, label, res:\", s, f, i, x, label, res)\n", " xyl[(fit_func_name, ia)].append((x, res, label))\n", " # plot\n", " fig, axes = plt.subplots(ncols=2, figsize=(4.77, 2.0), constrained_layout=True, sharey='row')\n", " fig.set_constrained_layout_pads(w_pad=0.02, h_pad=0.05, hspace=0., wspace=0.)\n", " for i, ia in enumerate(ias):\n", " axes[i].set_title(ia)\n", " slicer1 = slice(0, 4) if i == 0 else slice(0, 5)\n", " slicer2 = slice(4, 8) if i == 0 else slice(5, 10)\n", " xticks = []\n", " for t, (fit_func_name, fit_func) in enumerate(fit_functions.items()):\n", " # plot ECC\n", " #x = np.array(range(len(y[(fit_func_name, ia)])))\n", " x_ecc, y_ecc, label_ecc = zip(*xyl[(fit_func_name, ia)][slicer1])\n", " line, = axes[i].plot(x_ecc, y_ecc, marker=markers[t], markersize=(3 if t == 0 else 6), linestyle=':', label=fit_func['name'])\n", " # plot Netz\n", " x_netz, y_netz, label_netz = zip(*xyl[(fit_func_name, ia)][slicer2])\n", " axes[i].plot(x_netz, y_netz, marker=markers[t], markersize=(3 if t == 0 else 6), linestyle=':', color=line.get_color())\n", " \n", " axes[i].set_xticks((*x_ecc, *x_netz))\n", " axes[i].set_xticklabels((*label_ecc, *label_netz), fontsize=6)\n", "\n", " axes[i].text(0.19, -0.2, \"ECC\", transform=axes[i].transAxes)\n", " axes[i].text(0.7, -0.2, \"Netz\", transform=axes[i].transAxes)\n", " axes[0].text(0.85, 0.82, f\"{xyl[('LJ-12-6', 'O-Cat.')][7][1]:.1f}↑\", transform=axes[0].transAxes)\n", " #ax.set_xlim(0.15, 0.71)\n", " axes[0].set_ylim(0, 2.6)\n", " axes[0].set_ylabel(r\"$\\Delta$ in kJ/mol\")\n", " axes[1].legend(frameon=False, loc='upper center')\n", " fig.savefig('../figures/fit-quality.pdf', dpi=300)\n", " plt.show()\n", "\n", "plot_fit_quality()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#!cp -a ../figures/fit-quality.pdf ~/research/output/ion-shortrange-paper/figures/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### compare different OW-CL potentials" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def comp_ocl():\n", " # two different force fields\n", " for f, (ff_inv_name, ff_inv) in enumerate(force_fields_inv.items()):\n", " print(ff_inv_name)\n", "\n", " fig, ax = plt.subplots(figsize=(6, 3))\n", "\n", " U_mean_list = []\n", " # four different system types per force field\n", " for s, inverse_setting in enumerate(inverse_setup_generator(system_types_inv, {ff_inv_name: ff_inv})):\n", " print(f\"{inverse_setting['name']}\")\n", " \n", " interaction = inverse_setting['interactions'][1]\n", " means = (False, True) if s in (3,) else (False,)\n", "\n", " r, g, U, popt, p0, sigma, sl, g_mean, U_mean = iff_pot_fit_dict[(inverse_setting['name'], interaction['name'], 'data')]\n", " label = sys_type_short_names[inverse_setting['refsys-parametric']['type']['name']]\n", " ax.plot(r, U, '-', label=label)\n", " ax.plot(r, U_mean, '--', color='k', label=\"avg\")\n", " \n", " ax.set_xlim(0.3, 0.78)\n", " ax.set_ylim(-2, 4)\n", " ax.set_title(ff_inv_name)\n", " ax.legend(loc='upper right', frameon=False)\n", " plt.show()\n", " \n", "comp_ocl()" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "# MD" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## systems to run and analyze" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "force_fields_md = {ffn: ff for ffn, ff in force_fields.items() if 'dummy' not in ff['tags']}\n", "#force_fields_md = {ffn: ff for ffn, ff in force_fields.items() if (ffn.startswith('netz') and 'dummy' not in ff['tags'])}\n", "#force_fields_md = {ffn: ff for ffn, ff in force_fields.items() if 'netz' in ffn}\n", "system_types_md = system_types\n", "#system_types_md = {stn: st for stn, st in system_types.items() if stn == 'water-cacl2_'}\n", "\n", "systems_md = (system_types_md, force_fields_md)\n", "pd.DataFrame(system_generator(*systems_md, verbose=False))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## preparation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### prepare MD" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def prepare_md():\n", " for system in system_generator(*systems_md):\n", " print(f\"system {system['name']}\")\n", " with WorkingDir(system['name']):\n", "\n", " # make dirs\n", " run_bash(\"rm -rf topol\")\n", " run_bash(\"mkdir -p equi1 equi2 prod topol\")\n", " if 'npt' in system['tags']:\n", " run_bash(f\"mkdir -p npt-equi3 npt-prod\")\n", " all_folders = [\"equi1\", \"equi2\", \"prod\"] + ([\"npt-equi3\", \"npt-prod\"] if 'npt' in system['tags'] else [])\n", "\n", " # topol.top\n", " save_parametric_force_field_as_top('topol/topol.top', system['force-field'], system['name'], system['moltypes'],\n", " osm_restraints={})\n", " if 'halftabulated' in system['tags']:\n", " ff_no_tabulated = deepcopy(system['force-field'])\n", " ff_no_tabulated['tabulated-potentials'] = []\n", " save_parametric_force_field_as_top('equi1/topol.top', ff_no_tabulated, system['name'], system['moltypes'],\n", " osm_restraints={})\n", " elif 'tabulated' in system['tags']:\n", " raise Exception('not implemented')\n", " else:\n", " run_bash(\"ln -sf ../topol/topol.top equi1/topol.top\")\n", " \n", " # single-*.gro and map*.xml\n", " for mt in system['moltypes']:\n", " name = mt.get('type', mt['name'])\n", " run_bash(f\"cp {template_dir}/gro/single-{name}.gro equi1/single-{mt['name']}.gro\")\n", " run_bash(f\"cp {template_dir}/map/map-{name}.xml topol/map-{mt['name']}.xml\")\n", " \n", " # tables_{}_{}.xvg\n", " if 'halftabulated' in system['tags']:\n", " for table in (f\"table_{pair[0]}_{pair[1]}.xvg\" for pair in itertools.combinations_with_replacement(system['atomtypes-no-h'], 2)\n", " if (pair[0], pair[1]) in system['force-field']['tabulated-potentials']):\n", " run_bash(f\"cp {template_dir}/table/{system['force-field']['name']}/{table} topol/\")\n", " for folder in all_folders:\n", " run_bash(f\"rm -f {folder}/{table}\")\n", " for folder in all_folders[1:]: # equi1 is run with LJ parameters\n", " run_bash(f\"ln -sf ../topol/{table} {folder}/{table}\")\n", " if 'tabulated' in system['tags']:\n", " raise Exception('not implemented')\n", " \n", " # table.xvg\n", " if 'halftabulated' in system['tags']:\n", " run_bash(f\"cp {template_dir}/table/table6-12.xvg topol/table.xvg\")\n", " for folder in all_folders:\n", " run_bash(f\"rm -f {folder}/table.xvg\")\n", " run_bash(f\"ln -sf ../topol/table.xvg {folder}/table.xvg\")\n", " \n", " # index.ndx\n", " if 'halftabulated' in system['tags']:\n", " top = gt.top.Topology()\n", " top.load_simple_top(system['moltypes'])\n", " gt.top.generate_index_file(top, 'topol/index.ndx')\n", " del top\n", " \n", " # grompp.mpd files\n", " for folder in all_folders:\n", " run_bash(f\"cp {template_dir}/mdp/{folder}.mdp {folder}/grompp.mdp\")\n", " # run length\n", " gt.mdp.set_parameter(\"equi1/grompp.mdp\", 'nsteps', int(1e4))\n", " gt.mdp.set_parameter(\"equi2/grompp.mdp\", 'nsteps', int(1e5))\n", " gt.mdp.set_parameter(\"prod/grompp.mdp\", 'nsteps', int(2e5))\n", " if 'npt' in system['tags']:\n", " npt_equi_nsteps = (int(5e5) if system['name'].startswith(('water5000-cacl2_500'))\n", " else int(1e5))\n", " gt.mdp.set_parameter(\"npt-equi3/grompp.mdp\", 'nsteps', npt_equi_nsteps)\n", " gt.mdp.set_parameter(\"npt-prod/grompp.mdp\", 'nsteps', int(2e5))\n", " # set temperature\n", " gt.mdp.set_parameter(\"equi2/grompp.mdp\", 'gen-temp', system['temperature'])\n", " for folder in all_folders[1:]:\n", " mdp_file = folder + '/grompp.mdp'\n", " gt.mdp.set_parameter(mdp_file, 'ref-t', system['temperature'])\n", " # set pressure\n", " if 'npt' in system['tags']:\n", " gt.mdp.set_parameter(\"npt-equi3/grompp.mdp\", 'ref-p', 1.0)\n", " gt.mdp.set_parameter(\"npt-prod/grompp.mdp\", 'ref-p', 1.0)\n", " # set cutoff scheme\n", " cutoff_scheme = 'group' if 'halftabulated' in system['tags'] else 'Verlet'\n", " for folder in all_folders:\n", " mdp_file = folder + '/grompp.mdp'\n", " gt.mdp.set_parameter(mdp_file, 'cutoff-scheme', cutoff_scheme)\n", " gt.mdp.set_parameter('equi1/grompp.mdp', 'cutoff-scheme', 'Verlet') # LJ for equi1\n", " # set cutoffs\n", " co = system['force-field']['cut-off']\n", " for folder in all_folders:\n", " mdp_file = folder + '/grompp.mdp'\n", " for key in ('rlist', 'rcoulomb', 'rvdw'):\n", " gt.mdp.set_parameter(mdp_file, key, co)\n", " # set vdwtype\n", " vdwtype = 'User' if 'halftabulated' in system['tags'] else 'Cut-off'\n", " for folder in all_folders:\n", " mdp_file = folder + '/grompp.mdp'\n", " gt.mdp.set_parameter(mdp_file, 'vdwtype', vdwtype)\n", " gt.mdp.set_parameter(\"equi1/grompp.mdp\", 'vdwtype', 'Cut-off') # LJ for equi1\n", " # set tail correction (dispersion correction)\n", " if 'tail-corr' in system['tags']:\n", " for folder in all_folders:\n", " mdp_file = folder + '/grompp.mdp'\n", " gt.mdp.set_parameter(mdp_file, 'DispCorr', 'EnerPres')\n", " # set energygrps(-table)\n", " if 'halftabulated' in system['tags']:\n", " pairs = tuple((pair for pair in system['force-field'].get('tabulated-potentials', [])\n", " if pair[0] in system['atomtypes']\n", " and pair[1] in system['atomtypes']))\n", " energygrps = ' '.join(list(OrderedSet([pair[0] for pair in pairs]\n", " +[pair[1] for pair in pairs])))\n", " energygrp_table = ' '.join((f\"{pair[0]} {pair[1]}\" for pair in pairs))\n", " for folder in all_folders[1:]: # LJ for equi1\n", " mdp_file = folder + '/grompp.mdp'\n", " gt.mdp.set_parameter(mdp_file, 'energygrps', energygrps)\n", " gt.mdp.set_parameter(mdp_file, 'energygrp-table', energygrp_table)\n", " gt.mdp.set_parameter('equi1/grompp.mdp', 'energygrps', '') # LJ for equi1\n", " gt.mdp.set_parameter('equi1/grompp.mdp', 'energygrp-table', '')\n", " elif 'tabulated' in system['tags']:\n", " raise Exception('not implemented')\n", " else:\n", " for folder in all_folders:\n", " mdp_file = folder + '/grompp.mdp'\n", " gt.mdp.set_parameter(mdp_file, 'energygrps', '')\n", " gt.mdp.set_parameter(mdp_file, 'energygrp-table', '')\n", " \n", " # settings.xml for calculating distributions\n", " cg = ET.Element('cg')\n", " for pair in itertools.combinations_with_replacement(system['atomtypes'], 2):\n", " non_bonded = ET.SubElement(cg, 'non-bonded')\n", " name = ET.SubElement(non_bonded, 'name')\n", " type1 = ET.SubElement(non_bonded, 'type1')\n", " type2 = ET.SubElement(non_bonded, 'type2')\n", " min = ET.SubElement(non_bonded, 'min')\n", " max = ET.SubElement(non_bonded, 'max')\n", " max_intra = ET.SubElement(non_bonded, 'max_intra')\n", " step = ET.SubElement(non_bonded, 'step')\n", " name.text = '-'.join(pair)\n", " type1.text = pair[0]\n", " type2.text = pair[1]\n", " min.text = '0'\n", " max.text = str(system['force-field']['cut-off'])\n", " max_intra.text = '0.2'\n", " step.text = '0.004'\n", " indent(cg)\n", " tree = ET.ElementTree(cg)\n", " tree.write('topol/settings.xml')\n", "prepare_md()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### prepare md thermal expansion" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "pd.DataFrame((sys for sys in system_generator(*systems_md) if 'therm-exp' in sys['tags']))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def prepare_thermal_expansion():\n", " for system in (sys for sys in system_generator(*systems_md) if 'therm-exp' in sys['tags']):\n", " print(f\"system {system['name']}\")\n", " with WorkingDir(system['name']):\n", "\n", " # make dirs\n", " Ts = [system['temperature'] + DeltaT for DeltaT in DeltaTs]\n", " print(f\"temperatures: {Ts}\")\n", " for T in Ts:\n", " run_bash(f\"mkdir -p therm-exp-{T:.0f}\")\n", " all_folders = [f\"therm-exp-{T:.0f}\" for T in Ts]\n", "\n", " # link/copy tables\n", " if 'halftabulated' in system['tags']:\n", " for folder in all_folders:\n", " # table.xvg\n", " run_bash(f\"rm -f {folder}/table.xvg\")\n", " run_bash(f\"ln -sf ../topol/table.xvg {folder}/table.xvg\")\n", " # link to topol/tables_{}_{}.xvg\n", " for table in (f\"table_{pair[0]}_{pair[1]}.xvg\" for pair in itertools.combinations_with_replacement(system['atomtypes-no-h'], 2)\n", " if (pair[0], pair[1]) in system['force-field']['tabulated-potentials']):\n", " run_bash(f\"rm -f {folder}/{table}\")\n", " run_bash(f\"ln -sf ../topol/{table} {folder}/{table}\")\n", " \n", " # grompp.mpd files\n", " for T in Ts:\n", " folder = f\"therm-exp-{T:.0f}\"\n", " # copy template\n", " run_bash(f\"cp {template_dir}/mdp/therm-exp.mdp {folder}/grompp.mdp\")\n", " # run length\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'nsteps', int(1e6))\n", " # set temperature\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'ref-t', T)\n", " # set pressure\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'ref-p', 1.0)\n", " # set cutoff scheme\n", " cutoff_scheme = 'group' if 'halftabulated' in system['tags'] else 'Verlet'\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'cutoff-scheme', cutoff_scheme)\n", " # set cutoffs\n", " co = system['force-field']['cut-off']\n", " for key in ('rlist', 'rcoulomb', 'rvdw'):\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", key, co)\n", " # set vdwtype\n", " vdwtype = 'User' if 'halftabulated' in system['tags'] else 'Cut-off'\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'vdwtype', vdwtype)\n", " # set tail correction (dispersion correction)\n", " if 'tail-corr' in system['tags']:\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'DispCorr', 'EnerPres')\n", " # set energygrps(-table)\n", " if 'halftabulated' in system['tags']:\n", " pairs = tuple((pair for pair in system['force-field'].get('tabulated-potentials', [])\n", " if pair[0] in system['atomtypes']\n", " and pair[1] in system['atomtypes']))\n", " energygrps = ' '.join(list(OrderedSet([pair[0] for pair in pairs]\n", " +[pair[1] for pair in pairs])))\n", " energygrp_table = ' '.join((f\"{pair[0]} {pair[1]}\" for pair in pairs))\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'energygrps', energygrps)\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'energygrp-table', energygrp_table)\n", " elif 'tabulated' in system['tags']:\n", " raise Exception('not implemented')\n", " else:\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'energygrps', '')\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'energygrp-table', '')\n", " \n", "prepare_thermal_expansion()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### prepare dos" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "pd.DataFrame((sys for sys in system_generator(*systems_md) if 'dos' in sys['tags']))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def prepare_dos():\n", " for system in (sys for sys in system_generator(*systems_md) if 'dos' in sys['tags']):\n", " print(f\"system {system['name']}\")\n", " with WorkingDir(system['name']):\n", "\n", " # make dirs\n", " folder = \"npt-prod-vel\"\n", " run_bash(f\"mkdir -p {folder}\")\n", "\n", " # topol, tables, index files, etc should already be present\n", " \n", " # link/copy tables\n", " if 'halftabulated' in system['tags']:\n", " # table.xvg\n", " run_bash(f\"rm -f {folder}/table.xvg\")\n", " run_bash(f\"ln -sf ../topol/table.xvg {folder}/table.xvg\")\n", " # link to topol/tables_{}_{}.xvg\n", " for table in (f\"table_{pair[0]}_{pair[1]}.xvg\" for pair in itertools.combinations_with_replacement(system['atomtypes-no-h'], 2)\n", " if (pair[0], pair[1]) in system['force-field']['tabulated-potentials']):\n", " run_bash(f\"rm -f {folder}/{table}\")\n", " run_bash(f\"ln -sf ../topol/{table} {folder}/{table}\")\n", " \n", " # grompp.mpd file\n", " # copy template\n", " run_bash(f\"cp {template_dir}/mdp/npt-dos.mdp {folder}/grompp.mdp\")\n", " # run length\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'nsteps', int(8e4))\n", " # set temperature\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'ref-t', system['temperature'])\n", " # set pressure\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'ref-p', 1.0)\n", " # set cutoff scheme\n", " cutoff_scheme = 'group' if 'halftabulated' in system['tags'] else 'Verlet'\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'cutoff-scheme', cutoff_scheme)\n", " # set cutoffs\n", " co = system['force-field']['cut-off']\n", " for key in ('rlist', 'rcoulomb', 'rvdw'):\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", key, co)\n", " # set vdwtype\n", " vdwtype = 'User' if 'halftabulated' in system['tags'] else 'Cut-off'\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'vdwtype', vdwtype)\n", " # set tail correction (dispersion correction)\n", " if 'tail-corr' in system['tags']:\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'DispCorr', 'EnerPres')\n", " # set energygrps(-table)\n", " if 'halftabulated' in system['tags']:\n", " pairs = tuple((pair for pair in system['force-field'].get('tabulated-potentials', [])\n", " if pair[0] in system['atomtypes']\n", " and pair[1] in system['atomtypes']))\n", " energygrps = ' '.join(list(OrderedSet([pair[0] for pair in pairs]\n", " +[pair[1] for pair in pairs])))\n", " energygrp_table = ' '.join((f\"{pair[0]} {pair[1]}\" for pair in pairs))\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'energygrps', energygrps)\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'energygrp-table', energygrp_table)\n", " elif 'tabulated' in system['tags']:\n", " raise Exception('not implemented')\n", " else:\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'energygrps', '')\n", " gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'energygrp-table', '')\n", " \n", " run_bash(f\"mkdir -p {folder}/dos\")\n", " params = {\n", " 'nsamples': param_dos['n_samples'],\n", " 'nblocks': param_dos['n_blocks'],\n", " 'nblocksteps': param_dos['n_frames_per_block'],\n", " 'moltypes': [{\n", " 'nmols': moltype['nmols'],\n", " 'atom_masses': [atom['mass'] for atom in moltype['atoms']],\n", " 'rot_treat': moltype['rot_treat'],\n", " 'abc_indicators': moltype['abc_indicators']\n", " } for moltype in system['moltypes']],\n", " 'cross_spectra': []\n", " }\n", "\n", " with open(f'{folder}/dos/params.json', 'w') as f:\n", " json.dump(params, f, indent=4)\n", "prepare_dos()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### fill box" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def fill_boxes():\n", " for system in system_generator(*systems_md):\n", " print(f\"system {system['name']}\")\n", " with WorkingDir(system['name']):\n", " box_edge = system['volume-init']**(1/3)\n", "\n", " # check existing conf.gro\n", " try:\n", " n_atoms_inserted = gt.gro.get_natoms(\"equi1/conf.gro\")\n", " box = gt.gro.get_box(\"equi1/conf.gro\")\n", " except:\n", " n_atoms_inserted = 0\n", " box = [0, 0, 0]\n", " n_atoms_wanted = gt.moltypes.get_natoms(system['moltypes'])\n", " if n_atoms_inserted == n_atoms_wanted:\n", " if np.allclose(box, [box_edge]*3):\n", " print('..conf.gro with correct number of atoms and box existing..')\n", " continue\n", "\n", " # empty box with volume\n", " empty_gro = f\"system\\n0\\n{box_edge} {box_edge} {box_edge}\"\n", " with open(\"equi1/conf.gro\", 'w') as f:\n", " f.write(empty_gro)\n", "\n", " # insert water\n", " n_water = sum((moltype['nmols'] for moltype in system['moltypes'] if moltype['name'] == 'SOL'))\n", " water_type = next((moltype['type'] for moltype in system['moltypes'] if moltype['name'] == 'SOL'))\n", " if n_water > 0:\n", " gro_file = {'water-spce': 'spc216.gro', 'water-tip4p2005': 'tip4p.gro'}[water_type]\n", " run_bash(f\"gmx solvate -cs {gro_file} -box {box_edge} {box_edge} {box_edge} -maxsol {n_water} -scale 0.5 -o equi1/conf.gro\")\n", " run_bash(\"rm -f equi1/\\#conf.gro.*\")\n", "\n", " # insert other molecules\n", " for moltype in (moltype for moltype in system['moltypes'] if moltype['name'] != 'SOL'):\n", " n_mols = moltype['nmols']\n", " mt_name = moltype['name']\n", " run_bash(f\"gmx insert-molecules -f equi1/conf.gro -o equi1/conf.gro -ci equi1/single-{mt_name}.gro -nmol {n_mols} -try 100 -scale 0.65\")\n", " run_bash(\"rm -f equi1/\\#conf.gro.*\")\n", "\n", " # check\n", " n_atoms_inserted = gt.gro.get_natoms(\"equi1/conf.gro\")\n", " if n_atoms_inserted != n_atoms_wanted:\n", " print(n_atoms_inserted, n_atoms_wanted)\n", " raise Exception(\"not enough molecules inserted\")\n", "fill_boxes()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## run on cluster" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### check what has not run yet" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def check_has_run():\n", " for system in system_generator(*systems_md):\n", " print(f\"system {system['name']}\")\n", " working_dir = os.path.join(system['name'])\n", "\n", " with WorkingDir(working_dir):\n", " # check if already done\n", " dist_done = 'dist' not in system['tags'] or os.path.isfile('prod/OW-HW.dist.new')\n", " npt_done = 'npt' not in system['tags'] or os.path.isfile('npt-prod/confout.gro')\n", " npt_dist_done = 'npt-dist' not in system['tags'] or os.path.isfile('npt-prod/OW-HW.dist.new')\n", " npt_msd_done = 'npt' not in system['tags'] or os.path.isfile('npt-prod/msd-SOL.txt')\n", " therm_exp_done = 'therm-exp' not in system['tags'] or all(\n", " (os.path.isfile(f\"therm-exp-{system['temperature']+DeltaT}/confout.gro\") for DeltaT in DeltaTs)\n", " )\n", " dos_done = 'dos' not in system['tags'] or os.path.isfile('npt-prod-vel/dos/dos.json')\n", " for tag, done in (\n", " ('dist', dist_done),\n", " ('npt', npt_done),\n", " ('npt-dist', npt_dist_done),\n", " ('npt', npt_msd_done),\n", " #('therm-exp', therm_exp_done),\n", " ('dos', dos_done),\n", " ):\n", " if not done:\n", " print(f\"{tag} not done but wanted\")\n", " \n", "check_has_run()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### run MD and analyze (RDF, MSD, therm. exp., DOS)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def run_md():\n", " remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'mammut-b', votca=True, mem_per_cpu='3800M')\n", " for system in system_generator(*systems_md):\n", " print(f\"system {system['name']}\")\n", " working_dir = os.path.join(system['name'])\n", " remote_dir = os.path.join(remote_dir_base, system['name'])\n", "\n", " with WorkingDir(working_dir):\n", " # check if already done\n", " dist_done = 'dist' not in system['tags'] or os.path.isfile('prod/OW-HW.dist.new')\n", " npt_done = 'npt' not in system['tags'] or os.path.isfile('npt-prod/confout.gro')\n", " npt_dist_done = 'npt-dist' not in system['tags'] or os.path.isfile('npt-prod/OW-HW.dist.new')\n", " npt_msd_done = 'npt' not in system['tags'] or os.path.isfile('npt-prod/msd-SOL.txt')\n", " therm_exp_done = 'therm-exp' not in system['tags'] or all(\n", " (os.path.isfile(f\"therm-exp-{system['temperature']+DeltaT}/confout.gro\") for DeltaT in DeltaTs)\n", " )\n", " dos_done = 'dos' not in system['tags'] or os.path.isfile('npt-prod-vel/dos/dos.json')\n", " if all((\n", " dist_done,\n", " npt_done,\n", " npt_dist_done,\n", " npt_msd_done,\n", " #therm_exp_done,\n", " dos_done,\n", " )):\n", " print('..all results present locally..')\n", " continue\n", "\n", " # mkdir\n", " run_bash(f\"ssh {remote_host} mkdir -p {remote_dir}\")\n", "\n", " # delete old topology and table files\n", " run_bash(f\"ssh {remote_host} rm -rf {remote_dir}/topol {remote_dir}/*/table*\")\n", "\n", " # copy simulation files to remote\n", " filelist = \"equi1/conf.gro equi1/topol.top */grompp.mdp topol\".split( )\n", " if 'halftabulated' in system['tags']:\n", " filelist.append(\"*/table*\")\n", " if 'dos' in system['tags']:\n", " filelist.append(\"*/dos/params.json\")\n", " gt.remote.push_files(filelist, remote_host, remote_dir, exclude=\"traj*\")\n", "\n", " # strings for the bash script\n", " mapping_files = ';'.join([f\"../topol/map-{moltype['name']}.xml\" for moltype in system['moltypes']])\n", " tabulated_string = \"\"\n", " if 'halftabulated' in system['tags']:\n", " tabulated_string = \"-n ../topol/index.ndx -maxwarn 1\"\n", " calc_nvt = str('nvt' in system['tags'])\n", " calc_npt = str('npt' in system['tags'])\n", " calc_dist = str('dist' in system['tags'])\n", " calc_npt_dist = str('npt-dist' in system['tags'])\n", " therm_exp = str('therm-exp' in system['tags'])\n", " calc_dos = str('dos' in system['tags'])\n", "\n", " # commands to be run on compute nodes\n", " script = remote_header + rf\"\"\"\n", "# gromacs decides for small systems to have less\n", "#NT_ARG=\"-nt $SLURM_JOB_CPUS_PER_NODE\"\n", "NT_ARG=\"\"\n", "# bug in gromacs with sd and gpu https://gitlab.com/gromacs/gromacs/-/issues/3473\n", "NB_ARG=\"-notunepme\"\n", "\n", "pushd equi1\n", " if [[ ! -f confout.gro ]]; then\n", " gmx grompp -p topol.top {tabulated_string}\n", " gmx mdrun $NT_ARG $NB_ARG\n", " fi\n", " rm -f \\#*\n", "popd\n", "\n", "pushd equi2\n", " if [[ ! -f confout.gro ]]; then\n", " gmx grompp -p ../topol/topol.top {tabulated_string} -c ../equi1/confout.gro\n", " gmx mdrun $NT_ARG $NB_ARG\n", " fi\n", " rm -f \\#*\n", "popd\n", "\n", "if [[ {calc_nvt} == True ]]; then\n", " pushd prod\n", " if [[ ! -f confout.gro ]]; then\n", " gmx grompp -p ../topol/topol.top {tabulated_string} -c ../equi2/confout.gro\n", " gmx mdrun -x traj_comp.xtc $NT_ARG $NB_ARG\n", " fi\n", " popd\n", "fi\n", "\n", "if [[ {calc_dist} == True ]]; then\n", " pushd prod\n", " if [[ ! -f OW-HW.dist.new ]]; then\n", " csg_stat --top topol.tpr --options ../topol/settings.xml --trj traj_comp.xtc \\\n", " --cg \"{mapping_files}\" --nt=$SLURM_JOB_CPUS_PER_NODE --ext dist.new\n", " #csg_stat --top topol.tpr --options ../topol/settings.xml --trj traj_comp.xtc \\\n", " #--cg \"{mapping_files}\" --nt=$SLURM_JOB_CPUS_PER_NODE --include-intra --ext dist-incl.new\n", " fi\n", " rm -f \\#*\n", " popd\n", "fi\n", "\n", "if [[ {calc_npt} == True ]]; then\n", " pushd npt-equi3\n", " if [[ ! -f confout.gro ]]; then\n", " gmx grompp -p ../topol/topol.top {tabulated_string} -c ../equi2/confout.gro\n", " gmx mdrun $NT_ARG $NB_ARG\n", " fi\n", " rm -f \\#*\n", " popd\n", "\n", " pushd npt-prod\n", " if [[ ! -f confout.gro ]]; then\n", " gmx grompp -p ../topol/topol.top {tabulated_string} -c ../npt-equi3/confout.gro\n", " gmx mdrun -x traj_comp.xtc $NT_ARG $NB_ARG\n", " fi\n", " popd\n", "fi\n", "\n", "if [[ {calc_npt_dist} == True ]]; then\n", " pushd npt-prod\n", " if [[ ! -f OW-HW.dist.new ]]; then\n", " csg_stat --top topol.tpr --options ../topol/settings.xml --trj traj_comp.xtc \\\n", " --cg \"{mapping_files}\" --nt=$SLURM_JOB_CPUS_PER_NODE --ext dist.new\n", " #csg_stat --top topol.tpr --options ../topol/settings.xml --trj traj_comp.xtc \\\n", " #--cg \"{mapping_files}\" --nt=$SLURM_JOB_CPUS_PER_NODE --include-intra --ext dist-incl.new\n", " fi\n", " rm -f \\#*\n", " popd\n", "fi\n", "\n", "if [[ {calc_npt} == True ]]; then\n", " pushd npt-prod\n", " if [[ ! -f msd-SOL.xvg ]]; then\n", " gmx msd -s topol.tpr -f traj_comp.xtc -o msd-SOL.xvg <<< 'SOL' > msd-SOL.txt\n", " fi\n", " rm -f \\#*\n", " popd\n", "fi\n", "\n", "#if [[ {therm_exp} == True ]]; then\n", " #for dir in therm-exp-*; do\n", " #pushd $dir\n", " #if [[ ! -f confout.gro ]]; then\n", " #gmx grompp -p ../topol/topol.top {tabulated_string} -c ../npt-equi3/confout.gro\n", " #gmx mdrun -x traj_comp.xtc $NT_ARG $NB_ARG\n", " #fi\n", " #popd\n", " #done\n", "#fi\n", "\n", "if [[ {calc_dos} == True ]]; then\n", " if [[ ! -f npt-prod-vel/dos/dos.json ]]; then\n", " pushd npt-prod-vel\n", " gmx grompp -p ../topol/topol.top {tabulated_string} -c ../npt-equi3/confout.gro\n", " gmx mdrun $NT_ARG $NB_ARG -o $JOBTMP/traj-dos.trr\n", " popd\n", "\n", " pushd npt-prod-vel/dos\n", " ~/bin/dos-calc params.json $JOBTMP/traj-dos.trr -v\n", " rm -f $JOBTMP/traj-dos.trr\n", " popd\n", " fi\n", "fi\n", "\n", "\"\"\" + remote_footer\n", "\n", " jobid = gt.remote.run_slurm_script(script, remote_host, remote_dir, dry_run=False)\n", " print(jobid)\n", " if jobid != None:\n", " jobids.append(jobid)\n", "run_md()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### check job status" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "jobids = check_job_stati(jobids, remote_host)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### copy results from cluster" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def copy_from_cluster():\n", " remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'enzo')\n", " for system in system_generator(*systems_md):\n", " print(f\"system {system['name']}\")\n", " working_dir = os.path.join(system['name'])\n", " remote_dir = os.path.join(remote_dir_base, system['name'])\n", " with WorkingDir(working_dir):\n", " filelist = [\"*/*.edr\", \"*prod/*.dist*.new*\", \"*/confout.gro\", \"*/topol.tpr\", \"*/msd-SOL.*\", \"*/dos/dos.json\"]\n", " try:\n", " gt.remote.pull_files(filelist, remote_host, remote_dir)\n", " except subprocess.CalledProcessError:\n", " print('..rsync failed..')\n", "copy_from_cluster()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## equilibration check" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def run_cell():\n", " for system in system_generator(*systems_md):\n", " print(f\"system {system['name']}\")\n", " with WorkingDir(system['name']):\n", " try:\n", " check_equi([\"Volume\"], edr_file=\"npt-equi3/ener.edr\", safe_factor=2.0)\n", " except:\n", " pass\n", " #print('..no data..')\n", "run_cell()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Analysis" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## RMSD table for paper" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "tags": [] }, "outputs": [], "source": [ "def gen_df_rmsd():\n", " index = [ff['name'] for ff in force_fields.values() if 'conc-range' in ff['tags']] # and 'fit' not in ff['tags']]\n", " index_final = [ff_short_names[ff] for ff in index]\n", " index_translation_dict = {ff: ff_final for ff, ff_final in zip(index, index_final)}\n", " columns = ('rdf', 'density', 'diffusion', 'osmotic')\n", " columns_final = (r'{$\\Delta_\\text{RDF}$}', r'{$\\Delta_\\rho$ (\\si{\\gram\\per\\ml})}', r'{$\\Delta_{D/D_0}$}', r'{$\\Delta_\\phi$}')\n", " columns_translation_dict = {prop: prop_final for prop, prop_final in zip(columns, columns_final)}\n", " df_rmsd = pd.DataFrame(index=index, columns=columns)\n", " return df_rmsd, index_translation_dict, columns_translation_dict\n", "\n", "df_rmsd, index_rmsd_transl, columns_rmsd_transl = gen_df_rmsd()\n", "#_, index_rmsd_transl, columns_rmsd_transl = gen_df_rmsd()\n", "df_rmsd" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_rmsd" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_rmsd.to_pickle('df_rmsd.pkl')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_rmsd = pd.read_pickle('df_rmsd.pkl')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# now collect data from below, come back, and execute this cell\n", "def show_df_rmsd(df_rmsd):\n", " df_rmsd = df_rmsd.copy()\n", " df_rmsd.rename(index=index_rmsd_transl, inplace=True)\n", " df_rmsd.rename(columns=columns_rmsd_transl, inplace=True)\n", " formatters = list([lambda x: '{:4.2f}'.format(x)]*4)\n", " formatters[1] = lambda x: '{:5.3f}'.format(x)\n", " print(df_rmsd.astype(float).to_latex(escape=False, formatters=formatters,\n", " column_format=r\"l S[table-format=2.2] S[table-format=2.3] S[table-format=2.3] S[table-format=2.3]\"))\n", " return df_rmsd\n", "\n", "show_df_rmsd(df_rmsd);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## distributions and potentials" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def nb_interactions_from_settings(settings_file):\n", " tree = ET.parse(settings_file)\n", " root = tree.getroot()\n", " nb_interactions = []\n", " for node in root.findall('non-bonded'):\n", " nb_interactions.append({\n", " 'name': node.find('name').text,\n", " 'type1': node.find('type1').text,\n", " 'type2': node.find('type2').text,\n", " 'min': float(node.find('min').text),\n", " 'max': float(node.find('max').text),\n", " 'max_intra': float(node.find('max_intra').text),\n", " 'step': float(node.find('step').text),\n", " })\n", " return nb_interactions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### load distributions NPT" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "npt_system_interaction_dict = {}\n", "\n", "def load_npt_dist_pot():\n", " for system in (sys for sys in system_generator(*systems_md) if 'npt-dist' in sys['tags']):\n", " print(f\"system {system['name']}\")\n", " working_dir = os.path.join(system['name'])\n", "\n", " with WorkingDir(working_dir):\n", " nb_interactions = nb_interactions_from_settings('topol/settings.xml')\n", "\n", " for nb in nb_interactions:\n", " nb_name = nb['name']\n", " print(nb_name, end='\\t')\n", " g_file = f\"npt-prod/{nb_name}.dist.new\"\n", " #g_incl_file = f\"npt-prod/{nb_name}.dist-incl.new\"\n", " with WorkingDir(working_dir):\n", " data = np.loadtxt(g_file, dtype=str, comments=['#', '@'])\n", " r = data[:, 0].astype(float)\n", " g = data[:, 1].astype(float)\n", "\n", " npt_system_interaction_dict[(system['name'], nb['name'], 'r')] = r\n", " npt_system_interaction_dict[(system['name'], nb['name'], 'g')] = g\n", " print('')\n", " \n", "load_npt_dist_pot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### calculate NPT coordination numbers" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_first_peak_coordination(r, g, rho):\n", " try:\n", " peaks, _ = signal.find_peaks(g, prominence=0.02)\n", " mins, _ = signal.find_peaks(-g, prominence=0.02)\n", " ndx_peak = peaks[0]\n", " ndx_min = peaks[0] + np.argmin(g[peaks[0]:mins[0]])\n", " delta_r = r[1] - r[0]\n", " integrand = g[:ndx_min] * r[:ndx_min]**2\n", " n_coord = 4 * np.pi * rho * np.trapz(integrand, x=r[:ndx_min])\n", " return ndx_peak, ndx_min, n_coord\n", " except:\n", " return None, None, None" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def calc_coord_numbers():\n", " for system in (sys for sys in system_generator(*systems_md) if 'npt-dist' in sys['tags']):\n", " print(f\"system {system['name']}\")\n", "\n", " tempf = tempfile.mkstemp(suffix='.xvg')[1]\n", " with WorkingDir(system['name']):\n", " nb_interactions = nb_interactions_from_settings('topol/settings.xml')\n", " run_bash(f\"gmx energy -f npt-prod/ener.edr -o {tempf} <<< 'Volume'\")\n", " data, _ = gt.xvg.load(tempf)\n", " run_bash(f\"rm -f {tempf}\")\n", " volume = data['Volume'].mean()\n", " volume_std = data['Volume'].std()\n", " \n", " for nb in nb_interactions:\n", " nb_name = nb['name']\n", "\n", " # load data\n", " r = npt_system_interaction_dict[(system['name'], nb['name'], 'r')]\n", " g = npt_system_interaction_dict[(system['name'], nb['name'], 'g')]\n", "\n", " # coordination number\n", " num1 = gt.moltypes.count_atomname(system['moltypes'], nb['type1'])\n", " num2 = gt.moltypes.count_atomname(system['moltypes'], nb['type2'])\n", " rho1 = num1 / volume\n", " rho2 = num2 / volume\n", " ndx_peak, ndx_min, n_coord1 = get_first_peak_coordination(r, g, rho1)\n", " ndx_peak, ndx_min, n_coord2 = get_first_peak_coordination(r, g, rho2)\n", " npt_system_interaction_dict[(system['name'], nb['name'], 'n_coord')] = (ndx_peak, ndx_min, n_coord1, n_coord2)\n", "calc_coord_numbers()" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "### plot all NPT distributions" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def plot_npt_dist():\n", "\n", " nb_interaction_names_to_show = [\n", " #'OW-LI',\n", " #'OW-NA',\n", " #'OW-K',\n", " #'OW-CA',\n", " #'OW-CL',\n", " #'OW-OW',\n", " #'OW-HW',\n", " 'CA-CL',\n", " ]\n", " \n", " ff_to_show = [\n", " 'opls-co0.9tc',\n", " 'netz-co0.9tc',\n", " 'eccr1-co1.2',\n", " 'madrid-co1.0tc',\n", " #'iff-altern5-eccr1-co1.2-nopc',\n", " #'iff-altern5-netz-co0.9-nopc'\n", " ]\n", " \n", " molar_mixing_ratios_to_show = [\n", " 0.005,\n", " #0.01,\n", " #0.02,\n", " #0.03,\n", " #0.04,\n", " #0.05,\n", " #0.1,\n", " ]\n", "\n", " with mpl.rc_context(rc={'figure.dpi': 150}):\n", " for system in (sys for sys in system_generator(*systems_md) if 'npt-dist' in sys['tags']):\n", " if system['force-field']['name'] not in ff_to_show:\n", " continue\n", " if system['molar-mixing-ratio'] not in molar_mixing_ratios_to_show:\n", " continue\n", " print(f\"system {system['name']}\")\n", " with WorkingDir(system['name']):\n", " nb_interactions = nb_interactions_from_settings('topol/settings.xml')\n", "\n", " for nb in [nb for nb in nb_interactions if nb['name'] in nb_interaction_names_to_show]:\n", " nb_name = nb['name']\n", "\n", " # load data\n", " r = npt_system_interaction_dict[(system['name'], nb['name'], 'r')]\n", " g = npt_system_interaction_dict[(system['name'], nb['name'], 'g')]\n", "\n", " # plot\n", " fig, ax = plt.subplots(figsize=(3, 2))\n", " ax.plot(r, g, label='g(r)')\n", "\n", " # coordination number\n", " ndx_peak, ndx_min, n_coord1, n_coord2 = npt_system_interaction_dict[(system['name'], nb['name'], 'n_coord')]\n", " ax.text(r[ndx_peak], g[ndx_peak], f\"{n_coord1:.4f}, {n_coord2:.4f}\", ha='center', va='bottom')\n", " ax.axvline(r[ndx_min], linestyle=':', color='k')\n", " \n", " # print numbers\n", " print('first peak height:', max(g))\n", " print('first peak postition:', r[np.argmax(g)])\n", "\n", " ax.set_xlim(0)\n", " ax.set_ylim(0, max(g)+0.2)\n", " ax.set_xlabel(\"r / nm\")\n", " ax.set_ylabel(\"g(r)\")\n", " ax.vlines(system['force-field']['cut-off'], 0.1, 0.9, linestyles='--', color='r')\n", " ax.vlines(system['force-field']['cut-off'], 0.1, 0.9, linestyles=':', color='orange')\n", " ax.set_title(nb_name)\n", " #ax.legend()\n", " fig.tight_layout()\n", " plt.show()\n", "plot_npt_dist()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### plot NPT coordination numbers" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "tags": [] }, "outputs": [], "source": [ "def plot_npt_coordnr():\n", " systems_to_show = [sys for sys in system_generator(*systems_md) if 'npt-dist' in sys['tags']]\n", "\n", " nb_interaction_names_to_show = {\n", " #'OW-CL': {'sys-types': ('water-cacl2_', 'water-kcl', 'water-licl', 'water-nacl')},\n", " 'OW-CL': {'sys-types': ('water-nacl',)},\n", " 'OW-CA': {'sys-types': ('water-cacl2_',)},\n", " 'OW-K': {'sys-types': ('water-kcl',)},\n", " 'OW-LI': {'sys-types': ('water-licl',)},\n", " 'OW-NA': {'sys-types': ('water-nacl',)},\n", " }\n", "\n", " force_fields_to_show = [\n", " 'opls-co0.9tc',\n", " 'eccr1-co1.2',\n", " 'netz-co0.9tc',\n", " 'madrid-co1.0tc',\n", " 'iff-altern5-eccr1-co1.2-nopc',\n", " 'iff-altern5-netz-co0.9-nopc',\n", " ]\n", " r_to_show = list(set(([sys['molar-mixing-ratio'] for sys in systems_to_show])))\n", " system_types_to_show = list(set(([sys['type']['name'] for sys in systems_to_show])))\n", "\n", " df_ncoord = pd.DataFrame(index=pd.MultiIndex.from_product((system_types_to_show, force_fields_to_show, r_to_show)),\n", " columns=nb_interaction_names_to_show)\n", "\n", " for system in systems_to_show:\n", " for nb_name in nb_interaction_names_to_show.keys():\n", " # load data\n", " try:\n", " ndx_peak, ndx_min, n_coord1, n_coord2 = npt_system_interaction_dict[(system['name'], nb_name, 'n_coord')]\n", " df_ncoord.at[(system['type']['name'], system['force-field']['name'], system['molar-mixing-ratio']), nb_name] = n_coord1\n", " except:\n", " #print('.. error ..')\n", " pass\n", "\n", " print(df_ncoord)\n", " with mpl.rc_context(rc=mpl_rc_global):\n", " for nb_name, _nb in nb_interaction_names_to_show.items():\n", " fig, ax = plt.subplots(figsize=(4, 2.5), dpi=300)\n", " for f, ff_name in enumerate(force_fields_to_show):\n", " for _sys_type in _nb['sys-types']:\n", " # plot\n", " data = df_ncoord.loc[(_sys_type, ff_name, slice(None)), nb_name]\n", " data = data.sort_index(level=2)\n", " x = data.index.get_level_values(2)\n", " y = np.array(data)\n", " #marker = 'so
s.\"[d], color=color)\n",
"\n",
" ax.set_title(nb_plot_name[nb_name])\n",
" ax.set_ylim(0)\n",
" ax.set_ylim(0, 6)\n",
"\n",
" #ax.set_xlim(plot_params_nb.get(nb_name, {'xlim': (0, 2)})['xlim'])\n",
" ax.set_xlabel(r\"$r$ / nm\")\n",
" ax.set_ylabel(r\"$g(r)$\")\n",
" ax.legend(frameon=False, labelspacing=0.1, fontsize=8, loc='upper right')\n",
" fig.savefig(os.path.join('../figures', f\"dist_{nb_name}_{system_combination['name'].replace(' ', '-')}.pdf\"))\n",
" fig.savefig(os.path.join('/tmp', f\"dist_{nb_name}_{system_combination['name'].replace(' ', '-')}.png\"), transparent=True, dpi=300)\n",
" plt.show()\n",
"#plot_comparison_dist(show_lit_data=False, show_twobody_entropy=True, show_rdf_coulomb_energy=False)\n",
"plot_comparison_dist(show_lit_data=False, show_twobody_entropy=False, show_rdf_coulomb_energy=False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### literature values peaks"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"lit_rdf_peak1 = {\n",
" 'LI': 0.208,\n",
" 'NA': 0.2356,\n",
" 'K': 0.2798,\n",
" 'CA': 0.2422,\n",
" 'CL': 0.3187,\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {
"tags": []
},
"source": [
"### appendix plot distributions"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def plot_comparison_dist_paper(show_lit_data=False, show_exp_data=True):\n",
" system_combinations_to_compare = [\n",
" {'name': 'all LiCl', 'show-nvt': False, 'show-npt': True,\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-licl50/opls-co0.9tc',\n",
" 'water5000-licl50/eccr1-co1.2',\n",
" 'water5000-licl50/netz-co0.9tc',\n",
" 'water5000-licl50/madrid-co1.0tc',\n",
" 'water5000-licl50/iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-licl50/iff-altern5-netz-co0.9-nopc',\n",
" 'water5000-licl50/Buckingham-iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-licl50/Buckingham-iff-altern5-netz-co0.9-nopc',\n",
" ]],\n",
" 'nb-interactions-to-show': [\n",
" 'OW-LI',\n",
" 'OW-CL',\n",
" ]},\n",
" {'name': 'all NaCl', 'show-nvt': False, 'show-npt': True,\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-nacl50/opls-co0.9tc',\n",
" 'water5000-nacl50/eccr1-co1.2',\n",
" 'water5000-nacl50/netz-co0.9tc',\n",
" 'water5000-nacl50/madrid-co1.0tc',\n",
" 'water5000-nacl50/iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-nacl50/iff-altern5-netz-co0.9-nopc',\n",
" 'water5000-nacl50/Buckingham-iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-nacl50/Buckingham-iff-altern5-netz-co0.9-nopc',\n",
" ]],\n",
" 'nb-interactions-to-show': [\n",
" 'OW-NA',\n",
" 'OW-CL',\n",
" ]},\n",
" {'name': 'all KCl', 'show-nvt': False, 'show-npt': True,\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-kcl50/opls-co0.9tc',\n",
" 'water5000-kcl50/eccr1-co1.2',\n",
" 'water5000-kcl50/netz-co0.9tc',\n",
" 'water5000-kcl50/madrid-co1.0tc',\n",
" 'water5000-kcl50/iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-kcl50/iff-altern5-netz-co0.9-nopc',\n",
" 'water5000-kcl50/Buckingham-iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-kcl50/Buckingham-iff-altern5-netz-co0.9-nopc',\n",
" ]],\n",
" 'nb-interactions-to-show': [\n",
" 'OW-K',\n",
" 'OW-CL',\n",
" ]},\n",
" {'name': 'all CaCl', 'show-nvt': False, 'show-npt': True,\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-cacl2_50/opls-co0.9tc',\n",
" 'water5000-cacl2_50/eccr1-co1.2',\n",
" 'water5000-cacl2_50/netz-co0.9tc',\n",
" 'water5000-cacl2_50/madrid-co1.0tc',\n",
" 'water5000-cacl2_50/iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-cacl2_50/iff-altern5-netz-co0.9-nopc',\n",
" 'water5000-cacl2_50/Buckingham-iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-cacl2_50/Buckingham-iff-altern5-netz-co0.9-nopc',\n",
" ]],\n",
" 'nb-interactions-to-show': [\n",
" 'OW-CA',\n",
" 'OW-CL',\n",
" ]},\n",
" ]\n",
"\n",
" additional_dists = [\n",
" {'name': 'AIMD', 'path': '../received/azade-aimd/li-cl/li-o-gofr-0.04.dat', 'show-for': ['OW-LI']},\n",
" {'name': 'AIMD', 'path': '../received/azade-aimd/na-cl/na-o-gofr-0.04.dat', 'show-for': ['OW-NA']},\n",
" {'name': 'AIMD', 'path': '../received/azade-aimd/k-cl/k-o-gofr-0.04.dat', 'show-for': ['OW-K']},\n",
" {'name': 'AIMD', 'path': '../received/azade-aimd/ca-cl2/ca-o-gofr-0.04.dat', 'show-for': ['OW-CA']},\n",
" {'name': 'AIMD', 'path': '../received/azade-aimd/na-cl/cl-o-gofr-0.04.dat', 'show-for': ['OW-CL']},\n",
" ]\n",
"\n",
" mpl_rc = {\n",
" 'legend.labelspacing': 0.2,\n",
" 'legend.handlelength': 1.8,\n",
" 'legend.columnspacing': 1.0,\n",
" 'legend.handletextpad': 0.5,\n",
" }\n",
" marker_colors = list(mpl.colors.TABLEAU_COLORS.values())\n",
" marker_color_ndx = 0\n",
" marker_color_dict = {}\n",
" with plt.rc_context({**mpl_rc_global, **mpl_rc}):\n",
" \n",
" fig, axes = plt.subplots(figsize=(4.6, 5.6), nrows=4, ncols=2, sharex='col', constrained_layout=True, dpi=200)\n",
" fig.set_constrained_layout_pads(w_pad=0.05, h_pad=0.00, hspace=0.0, wspace=0.0)\n",
" legend_handles, legend_labels = [], []\n",
" \n",
" for c, system_combination in enumerate(system_combinations_to_compare):\n",
" print(f\"system-combination {system_combination['name']}\")\n",
" axrow = axes[c]\n",
" \n",
" for n, nb_name in enumerate(system_combination['nb-interactions-to-show']):\n",
" #print(f\" interaction {nb_name}\")\n",
" ax = axrow[n]\n",
"\n",
" for s, system in enumerate(system_combination['system-combination']):\n",
" #print(f\" system {system['name']}\")\n",
"\n",
" label = ff_short_names.get(system['force-field']['name'], system['force-field']['name'])\n",
" if system_combination['show-nvt']:\n",
" try:\n",
" r = system_interaction_dict[(system['name'], nb_name, 'r')]\n",
" g = system_interaction_dict[(system['name'], nb_name, 'g')]\n",
" linestyle = ['-', '--', ':', '-.'][s%4]\n",
" line, = ax.plot(r, g, linestyle=linestyle, label=label)\n",
" except KeyError:\n",
" print('..no data..')\n",
"\n",
" if system_combination['show-npt']:\n",
" try:\n",
" r = npt_system_interaction_dict[(system['name'], nb_name, 'r')]\n",
" g = npt_system_interaction_dict[(system['name'], nb_name, 'g')]\n",
" #linestyle = ff_linestyles[system['force-field']['name']]\n",
" linestyle = '-'\n",
" color = ff_colors[system['force-field']['name']]\n",
" line, = ax.plot(r, g, linestyle=linestyle, label=label, color=color, linewidth=1.0)\n",
" except KeyError:\n",
" print('..no data..')\n",
" if label not in legend_labels:\n",
" legend_handles.append(line)\n",
" legend_labels.append(label)\n",
" \n",
"\n",
"\n",
" # plot additional\n",
" for a, add_dist in enumerate((ad for ad in additional_dists if nb_name in ad['show-for'])):\n",
" data = np.loadtxt(add_dist['path'])\n",
" x = data.T[0]/10 # votca and vmd both give g(r) value for bin symmetrically around r\n",
" y = data.T[1]\n",
" linestyle = ['--', '-.'][a%2]\n",
" label = add_dist['name']\n",
" line, = ax.plot(x, y, linestyle=linestyle, color='k', label=label)\n",
" if label not in legend_labels:\n",
" legend_handles.append(line)\n",
" legend_labels.append(label)\n",
" \n",
" # plot literature\n",
" if show_lit_data:\n",
" if nb_name in first_peak_rdf_lit_data:\n",
" for data in first_peak_rdf_lit_data[nb_name]:\n",
" peak = data['peak']\n",
" label = data['source-short']\n",
" zot = data['source-zotero']\n",
" if zot in marker_color_dict.keys():\n",
" marker_color_ndx_here = marker_color_dict[zot]\n",
" else:\n",
" marker_color_ndx_here = marker_color_ndx\n",
" marker_color_dict[zot] = marker_color_ndx\n",
" line, = ax.plot([peak[0]], [peak[1]], label=label, marker='x', linestyle='', zorder=10, color=marker_colors[marker_color_ndx_here])\n",
" marker_color_ndx += 1\n",
" if label not in legend_labels:\n",
" legend_handles.append(line)\n",
" legend_labels.append(label)\n",
" if show_exp_data:\n",
" if (ion := nb_name.split('-')[1]) in lit_rdf_peak1.keys():\n",
" ax.axvline(lit_rdf_peak1[ion])\n",
" \n",
" ax.set_ylim(0)\n",
" #ax.set_xlim(0.15, 0.7)\n",
" #ax.set_xlim({'OW-NA': (0.2, 0.49), 'OW-CL': (0.25, 0.44)}[nb_name])\n",
" ax.set_xlim((0.25, 0.6) if nb_name == 'OW-CL' else (0.15, 0.55))\n",
" #ax.set_title(nb_name)\n",
" ax.text(.89, .85, nb_plot_name[nb_name],\n",
" horizontalalignment='right', transform=ax.transAxes)\n",
" \n",
" axrow[0].set_ylabel(r\"$g(r)$ in \" + sys_type_short_names[system['type']['name']])\n",
" axes[-1,0].set_xlabel(r\"$r$ / nm\")\n",
" axes[-1,1].set_xlabel(r\"$r$ / nm\")\n",
" #handles, labels = axes[0,0].get_legend_handles_labels()\n",
" #legend_labels, legend_handles = zip(*sorted(zip(legend_labels, legend_handles), key=lambda t: t[0], reverse=False))\n",
" order = np.array([0, 3, 1, 2, 4, 5, 6])\n",
" legend_handles, legend_labels = [legend_handles[idx] for idx in order], [legend_labels[idx] for idx in order]\n",
" ncol = 5 if show_lit_data else 4\n",
" fig.legend(legend_handles, legend_labels, ncol=ncol, loc='lower center', bbox_to_anchor=(0.50, 1.00),)\n",
" fig.savefig(os.path.join('../figures', f\"dist_all.pdf\"), bbox_inches='tight')\n",
" plt.show()\n",
"plot_comparison_dist_paper(show_lit_data=False, show_exp_data=False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!cp -a ../figures/dist_all.pdf ~/research/output/ion-shortrange-paper/figures/"
]
},
{
"cell_type": "markdown",
"metadata": {
"tags": []
},
"source": [
"### toc plot distributions (not used)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def plot_toc_distributions():\n",
" mpl_rc = {}\n",
" marker_colors = list(mpl.colors.TABLEAU_COLORS.values())\n",
" marker_color_ndx = 0\n",
" marker_color_dict = {}\n",
" with plt.rc_context({**mpl_rc_global, **mpl_rc}):\n",
" \n",
" fig, ax = plt.subplots(figsize=(2*2.54, 2*2.54), constrained_layout=True)\n",
" fig.set_constrained_layout_pads(w_pad=0.00, h_pad=0.00, hspace=0.0, wspace=0.0)\n",
" \n",
" r = npt_system_interaction_dict[('water5000-cacl2_50/netz-co0.9tc', 'OW-CA', 'r')]\n",
" g = npt_system_interaction_dict[('water5000-cacl2_50/netz-co0.9tc', 'OW-CA', 'g')]\n",
" linestyle = '-'\n",
" color = 'k'\n",
" ax.plot(r, g, linestyle=linestyle, color=color, linewidth=1.0)\n",
" ax.set_ylim(0)\n",
" ax.set_xlim(0.0, 0.3)\n",
" ax.set_xlabel(r\"$r$ / nm\")\n",
" ax.set_axis_off()\n",
" fig.savefig(os.path.join('/tmp', f\"toc-dist.svg\"), bbox_inches='tight', format='svg')\n",
" plt.show()\n",
"plot_toc_distributions()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### RMSD RDF"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def rmsd_rdf():\n",
" \n",
" rdf_rmsd_dict = collections.defaultdict(lambda: 0)\n",
" \n",
" reference_dists = {\n",
" 'OW-LI': {'name': 'AIMD', 'path': '../received/azade-aimd/li-cl/li-o-gofr-0.04.dat',},\n",
" 'OW-NA': {'name': 'AIMD', 'path': '../received/azade-aimd/na-cl/na-o-gofr-0.04.dat',},\n",
" 'OW-K': {'name': 'AIMD', 'path': '../received/azade-aimd/k-cl/k-o-gofr-0.04.dat',},\n",
" 'OW-CA': {'name': 'AIMD', 'path': '../received/azade-aimd/ca-cl2/ca-o-gofr-0.04.dat',},\n",
" 'OW-CL': {'name': 'AIMD', 'path': '../received/azade-aimd/na-cl/cl-o-gofr-0.04.dat',},\n",
" }\n",
" \n",
" system_params = deepcopy(SYSTEM_PARAMS)\n",
" system_params['n_salts'] = (25, )\n",
" for system in (sys for sys in system_generator(*systems_md, system_params=system_params) if 'npt-dist' in sys['tags']):\n",
" #print(f\"system {system['name']}\")\n",
" working_dir = os.path.join(system['name'])\n",
"\n",
" with WorkingDir(working_dir):\n",
" nb_interactions = nb_interactions_from_settings('topol/settings.xml')\n",
"\n",
" for nb in (nb for nb in nb_interactions if nb['name'] in ('OW-LI', 'OW-NA', 'OW-K', 'OW-CA', 'OW-CL',)):\n",
" nb_name = nb['name']\n",
" #print(nb_name, end='\\t')\n",
" \n",
" # load rdf data\n",
" r = npt_system_interaction_dict[(system['name'], nb_name, 'r')]\n",
" g = npt_system_interaction_dict[(system['name'], nb_name, 'g')]\n",
" # only up to 0.7 nm\n",
" max_r = 0.7\n",
" r, g = r[r <= max_r], g[r <= max_r]\n",
" # load reference data\n",
" data_ref = np.loadtxt(reference_dists[nb_name]['path'])\n",
" r_ref = data_ref.T[0]/10 # votca and vmd both give g(r) value for bin symmetrically around r\n",
" g_ref = data_ref.T[1]\n",
" g_ref_interpolated = np.interp(r, r_ref, g_ref)\n",
" # calculate and save MSD\n",
" msd = 1/max_r * np.trapz(x=r, y=(g - g_ref_interpolated)**2) # unitless\n",
" #msd = np.trapz(x=r, y=(g - g_ref_interpolated)**2) # not unitless\n",
" weight = 1/4 if nb_name == 'OW-CL' else 1.0\n",
" rdf_rmsd_dict[(system['force-field']['name'], 'rmsd')] += weight * msd\n",
" #rdf_rmsd_dict[(system['force-field']['name'], 'counter')] += weight\n",
" #print('')\n",
" \n",
" for ff_name, ff in force_fields.items():\n",
" if 'dummy' in ff['tags']:\n",
" continue\n",
" df_rmsd.at[ff_name, 'rdf'] = np.sqrt(rdf_rmsd_dict[(ff_name, 'rmsd')]) #/ rdf_rmsd_dict[(ff_name, 'counter')]\n",
" \n",
"rmsd_rdf()\n",
"df_rmsd"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### calculate neutron scattering difference NaCl - LiCl"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fourier(r, f):\n",
" \"\"\"Compute the radially 3D FT of a radially symmetric function.\n",
"\n",
" The frequency grid is also returned. Some special extrapolations are used\n",
" to make the results consistent. This function is isometric meaning it can\n",
" be used to calculate the FT and the inverse FT. That means inputs can also\n",
" be k and f_hat which results in r and f.\n",
"\n",
" Args:\n",
" r: Input grid. Must be evenly spaced. Can start at zero or at Δr, but nowhere\n",
" else.\n",
" f: Input function. Must have same length as r and correspond to its values.\n",
"\n",
" Returns:\n",
" (k, f_hat): The reciprocal grid and the FT of f.\n",
"\n",
" \"\"\"\n",
" Delta_r = r[1] - r[0]\n",
" r0_added = False\n",
" if np.isclose(r[0], Delta_r):\n",
" r = np.concatenate(([0], r))\n",
" f = np.concatenate(([0], f))\n",
" r0_added = True\n",
" elif np.isclose(r[0], 0.0):\n",
" pass\n",
" else:\n",
" raise Exception('this function can not handle this input')\n",
" # if the input is even, np.fft.rfftfreq would end with the Nyquist frequency.\n",
" # But there the imaginary part of the FT is always zero, so we alwas append a zero\n",
" # to obtain a odd grid.\n",
" if len(r) % 2 == 0: # even\n",
" r = np.concatenate((r, [r[-1]+Delta_r]))\n",
" f = np.concatenate((f, [0]))\n",
" n = (len(r)-1)*2-1\n",
" else: # odd\n",
" n = len(r)*2-1\n",
" k = np.fft.rfftfreq(n=n, d=Delta_r)\n",
" with np.errstate(divide='ignore', invalid='ignore'):\n",
" f_hat = -2 / k / 1 * Delta_r * np.imag(np.fft.rfft(r * f, n=n))\n",
" if r0_added:\n",
" f_hat = f_hat[1:]\n",
" k = k[1:]\n",
" return k, f_hat"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"def show_neutron_diff(convolute_sigma=None, zero_start_Delta_G=False, show_fourier=False):\n",
" \n",
" K_XY = {\n",
" \"OW-OW\": 0.034091,\n",
" \"OW-CL\": 0.008101,\n",
" \"CL-CL\": 0.000481,\n",
" \"NA-NA\": 0.0000691,\n",
" \"NA-CL\": 0.000365,\n",
" \"OW-NA\": 0.00307,\n",
" \"LI-LI\": 0.0000189,\n",
" \"LI-CL\": -0.00019,\n",
" \"OW-LI\": -0.0016,\n",
" }\n",
"\n",
" system_combinations_to_compare = [\n",
" {'name': 'ECC 250',\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-nacl250/eccr1-co1.2',\n",
" 'water5000-licl250/eccr1-co1.2',\n",
" ]],\n",
" },\n",
" {'name': 'ECC IMC 250',\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-nacl250/iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-licl250/iff-altern5-eccr1-co1.2-nopc',\n",
" ]],\n",
" },\n",
" {'name': 'HMN 250',\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-nacl250/netz-co0.9tc',\n",
" 'water5000-licl250/netz-co0.9tc',\n",
" ]],\n",
" },\n",
" {'name': 'HMN IMC 250',\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-nacl250/iff-altern5-netz-co0.9-nopc',\n",
" 'water5000-licl250/iff-altern5-netz-co0.9-nopc',\n",
" ]],\n",
" },\n",
" {'name': 'ECC 500',\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-nacl500/eccr1-co1.2',\n",
" 'water5000-licl500/eccr1-co1.2',\n",
" ]],\n",
" },\n",
" {'name': 'ECC IMC 500',\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-nacl500/iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-licl500/iff-altern5-eccr1-co1.2-nopc',\n",
" ]],\n",
" },\n",
" {'name': 'HMN 500',\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-nacl500/netz-co0.9tc',\n",
" 'water5000-licl500/netz-co0.9tc',\n",
" ]],\n",
" },\n",
" {'name': 'HMN IMC 500',\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-nacl500/iff-altern5-netz-co0.9-nopc',\n",
" 'water5000-licl500/iff-altern5-netz-co0.9-nopc',\n",
" ]],\n",
" },\n",
" #][0:4] # change me\n",
" ][4:8] # change me\n",
" \n",
" nbs_to_compare = [\n",
" {\"OW-NA\", \"OW-LI\"},\n",
" {\"NA-CL\", \"LI-CL\"},\n",
" ]\n",
" \n",
" with mpl.rc_context(rc={\n",
" 'figure.dpi': 300,\n",
" #'legend.fontsize': 6,\n",
" 'legend.labelspacing': 0.2,\n",
" 'legend.handlelength': 2.5,\n",
" }):\n",
" fig_out, ax_out = plt.subplots(figsize=(5, 3), constrained_layout=True)\n",
" for system_combination in system_combinations_to_compare:\n",
" print(f\"system-combination {system_combination['name']}\")\n",
"\n",
" #fig, ax = plt.subplots(figsize=(8, 3), constrained_layout=True)\n",
"\n",
" Delta_G_dict = {}\n",
" for s, system in enumerate(system_combination['system-combination']):\n",
" print(f\" system {system['name']}\")\n",
" with WorkingDir(system['name']):\n",
" nb_interactions = nb_interactions_from_settings('topol/settings.xml')\n",
" for nb in (nb for nb in nb_interactions if 'H' not in nb['name']):\n",
" nb_name = nb['name']\n",
" r = npt_system_interaction_dict[(system['name'], nb_name, 'r')]\n",
" g = npt_system_interaction_dict[(system['name'], nb_name, 'g')]\n",
" sign = [-1, 1][s]\n",
" #ax.plot(r, K_XY[nb_name] * (g - 1), label=f\"{system['name']} {nb_name}\", linestyle='--')\n",
" Delta_G_dict[system['name'], nb_name] = sign * K_XY[nb_name] * (g - 1)\n",
" #plt.show()\n",
" \n",
" # print difference function ΔG(r) for some interactions\n",
" fig, ax = plt.subplots(figsize=(5, 2), constrained_layout=True)\n",
" ax.set_title(system_combination['name'])\n",
" for nb in (nb for nb in nb_interactions if 'H' not in nb['name']):\n",
" nb_name = nb['name']\n",
" Delta_G_nb = np.zeros_like(r)\n",
" for s, system in enumerate(system_combination['system-combination']):\n",
" if (system['name'], nb_name) in Delta_G_dict:\n",
" Delta_G_nb += Delta_G_dict[system['name'], nb_name]\n",
" for nb_to_compare in nbs_to_compare:\n",
" if nb_name in nb_to_compare and (system['name'], nb_name_temp := nb_to_compare.difference({nb_name}).pop()) in Delta_G_dict:\n",
" Delta_G_nb += Delta_G_dict[system['name'], nb_name_temp]\n",
" # optional, in paper they all start with 0\n",
" if zero_start_Delta_G:\n",
" Delta_G_nb -= Delta_G_nb[0]\n",
" ax.plot(r, Delta_G_nb, label=nb_name, linestyle='--')\n",
" ax.legend(frameon=False, labelspacing=0.1, loc='upper right')\n",
"\n",
" #ax.set_ylim(-0.001, 0.001)\n",
" ax.set_xlabel(r\"$r$ / nm\")\n",
" ax.set_ylabel(r\"$g(r)$\")\n",
" ax.legend(frameon=False, labelspacing=0.1, fontsize=8, loc='upper right')\n",
" #fig.savefig(os.path.join('../figures', f\"neutron-diff.pdf\"))\n",
" \n",
" Delta_G = sum(Delta_G_dict.values())\n",
" if convolute_sigma is not None:\n",
" Delta_G = ndimage.gaussian_filter1d(Delta_G, convolute_sigma)\n",
" x = r\n",
" y = Delta_G\n",
" if show_fourier:\n",
" x, y = fourier(x, y)\n",
" ax_out.plot(x, y, label=system_combination['name'])\n",
" #ax_out.grid()\n",
" ax_out.legend(frameon=False)\n",
" ax_out.set_xlim(0, 1)\n",
" ax_out.tick_params(direction='in', top=True, right=True)\n",
" if show_fourier:\n",
" ax_out.set_xlim(0, 15)\n",
" plt.show()\n",
" \n",
"show_neutron_diff(convolute_sigma=None, zero_start_Delta_G=False, show_fourier=False)\n",
"#show_neutron_diff(convolute_sigma=None, zero_start_Delta_G=False, show_fourier=True)"
]
},
{
"cell_type": "markdown",
"metadata": {
"tags": []
},
"source": [
"### PMF frequency"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Polynomial.fit is weird!\n",
"x = [1, 2, 3]\n",
"y = [0, -1, 0]\n",
"fit = np.polynomial.Polynomial.fit(x, y, 2)\n",
"fit2 = np.polynomial.polynomial.polyfit(x, y, 2)\n",
"fit, fit.coef, fit.convert(), fit2"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"def pmf_frequency():\n",
" system_combinations_to_compare = [\n",
" {'name': 'all LiCl',\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-licl50/opls-co0.9tc',\n",
" 'water5000-licl50/eccr1-co1.2',\n",
" 'water5000-licl50/netz-co0.9tc',\n",
" 'water5000-licl50/madrid-co1.0tc',\n",
" 'water5000-licl50/iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-licl50/iff-altern5-netz-co0.9-nopc',\n",
" 'water5000-licl50/Buckingham-iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-licl50/Buckingham-iff-altern5-netz-co0.9-nopc',\n",
" ]],\n",
" 'nb-interactions-to-show': [\n",
" 'OW-LI',\n",
" #'OW-CL',\n",
" ]},\n",
" {'name': 'all NaCl',\n",
" 'system-combination': [system for system in system_generator(*systems_md)\n",
" if system['name'] in [\n",
" 'water5000-nacl50/opls-co0.9tc',\n",
" 'water5000-nacl50/eccr1-co1.2',\n",
" 'water5000-nacl50/netz-co0.9tc',\n",
" 'water5000-nacl50/madrid-co1.0tc',\n",
" 'water5000-nacl50/iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-nacl50/iff-altern5-netz-co0.9-nopc',\n",
" 'water5000-nacl50/Buckingham-iff-altern5-eccr1-co1.2-nopc',\n",
" 'water5000-nacl50/Buckingham-iff-altern5-netz-co0.9-nopc',\n",
" ]],\n",
" 'nb-interactions-to-show': [\n",
" 'OW-NA',\n",
" #'OW-CL',\n",
" ]},\n",
" ]\n",
"\n",
" mpl_rc = {\n",
" 'legend.labelspacing': 0.2,\n",
" 'legend.handlelength': 1.8,\n",
" 'legend.columnspacing': 1.0,\n",
" 'legend.handletextpad': 0.5,\n",
" }\n",
" \n",
" pmf_frequency_dict = {}\n",
" \n",
" with plt.rc_context({**mpl_rc_global, **mpl_rc}):\n",
" \n",
" fig, axes = plt.subplots(figsize=(4.6, 1.6), nrows=1, ncols=2, sharex='col', constrained_layout=True, dpi=200)\n",
" fig.set_constrained_layout_pads(w_pad=0.05, h_pad=0.00, hspace=0.0, wspace=0.0)\n",
" legend_handles, legend_labels = [], []\n",
" \n",
" for c, system_combination in enumerate(system_combinations_to_compare):\n",
" print(f\"system-combination {system_combination['name']}\")\n",
" ax = axes.flatten()[c]\n",
" \n",
" nb_name = system_combination['nb-interactions-to-show'][0]\n",
" #print(f\" interaction {nb_name}\")\n",
"\n",
" for s, system in enumerate(system_combination['system-combination']):\n",
" #print(f\" system {system['name']}\")\n",
"\n",
" # PMF\n",
" label = ff_short_names.get(system['force-field']['name'], system['force-field']['name'])\n",
" r = npt_system_interaction_dict[(system['name'], nb_name, 'r')]\n",
" g = npt_system_interaction_dict[(system['name'], nb_name, 'g')]\n",
" with np.errstate(divide='ignore'):\n",
" # oconst.k_gro = const.k * const.N_A * 1e-3\n",
" pmf = -oconst.k_gro * 300 * np.log(g) # [pmf] = kJ/mol\n",
" pmf_SI = -const.k * 300 * np.log(g) # [pmf_SI] = J\n",
" r_SI = r / 1e9 # [r_SI] = m\n",
" \n",
" # PMF fit\n",
" ndx_min = np.argmin(pmf)\n",
" pm = 1\n",
" fit_range = slice(ndx_min - pm, ndx_min + pm + 1)\n",
" pm_plot = 10\n",
" plot_range = slice(ndx_min - pm_plot, ndx_min + pm_plot + 1)\n",
" fit = np.polynomial.Polynomial.fit(r[fit_range], pmf[fit_range], 2)\n",
" \n",
" # force constant\n",
" k = 2 * fit.convert().coef[2] # [k] = kJ/mol/nm²\n",
" m1 = 18\n",
" m2 = system['moltypes'][1]['atoms'][0]['mass']\n",
" mu = m1 * m2 / (m1 + m2) # [mu] = u\n",
" f = 1 / (2 * np.pi) * np.sqrt(k / mu) # [f] = 1/ps\n",
" pmf_frequency_dict[system['name']] = f\n",
" f_SI = f * 1e12\n",
" \n",
" # wavenumber\n",
" nu_tilde = f_SI / const.c # [nu_tilde] = 1/m\n",
" #print(nu_tilde / 100)\n",
" \n",
" linestyle = '-'\n",
" color = ff_colors[system['force-field']['name']]\n",
" line, = ax.plot(r, pmf, linestyle=linestyle, label=label, color=color, linewidth=1.0)\n",
" if label not in legend_labels:\n",
" legend_handles.append(line)\n",
" legend_labels.append(label)\n",
" # plot PMF fit\n",
" ax.plot(r[plot_range], fit(r[plot_range]), linestyle=':', color=line.get_color(), linewidth=1.0)\n",
" #ax.axvline(x=r[ndx_min], color=line.get_color(), linestyle=':')\n",
"\n",
" #ax.set_xlim((0.15, None))\n",
" ax.text(.89, .2, nb_plot_name[nb_name],\n",
" horizontalalignment='right', transform=ax.transAxes)\n",
"\n",
" axes[0].set_ylabel(r\"$w(r)$ in kJ/mol\")\n",
" axes[0].set_xlim((0.15, 0.25))\n",
" axes[1].set_xlim((0.20, 0.30))\n",
" axes[0].set_ylim(-8, 1)\n",
" axes[1].set_ylim(-6, 1)\n",
" for ax in axes:\n",
" ax.set_xlabel(r\"$r$ / nm\")\n",
" #handles, labels = axes[0,0].get_legend_handles_labels()\n",
" #legend_labels, legend_handles = zip(*sorted(zip(legend_labels, legend_handles), key=lambda t: t[0], reverse=False))\n",
" #order = np.array([0, 3, 1, 2, 4, 5, 6])\n",
" #legend_handles, legend_labels = [legend_handles[idx] for idx in order], [legend_labels[idx] for idx in order]\n",
" ncol = 4\n",
" fig.legend(legend_handles, legend_labels, ncol=ncol, loc='lower center', bbox_to_anchor=(0.50, 1.00),)\n",
" fig.savefig('../figures/pmf_fit.pdf', bbox_inches='tight')\n",
" plt.show()\n",
" return pmf_frequency_dict\n",
"pmf_frequency_dict = pmf_frequency()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!cp -a ../figures/pmf_fit.pdf ~/research/output/ion-shortrange-paper/figures/"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## compare density, compressibility with literature data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### load"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"molar_mixing_ratios = list(OrderedSet([sys['molar-mixing-ratio'] for sys in system_generator(*systems_md)]))\n",
"\n",
"n_blocks = 5\n",
"block_len = 80 # in ps\n",
"system_types_to_show = list(OrderedSet(([sys['type']['name'] for sys in system_generator(*systems_md)])))\n",
"columns = ['density', 'concentration', 'compressibility', 'mass-fraction']\n",
"index = pd.MultiIndex.from_product((\n",
" system_types_to_show,\n",
" [ffn for ffn, ff in force_fields.items() if 'conc-range' in ff['tags']],\n",
" molar_mixing_ratios,\n",
" range(n_blocks)\n",
"))\n",
"df_dens = pd.DataFrame(columns=columns, index=index, dtype=float)\n",
"df_dens.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def load_dens():\n",
" for system in (sys for sys in system_generator(*systems_md) if 'conc-range' in sys['tags']):\n",
" print(f\"system {system['name']}\")\n",
" working_dir = os.path.join(system['name'])\n",
" \n",
" mass_fraction = system['mass-fraction']\n",
" df_dens.loc[(system['type']['name'], system['force-field']['name'], system['molar-mixing-ratio'], slice(None)), 'mass-fraction'] = mass_fraction\n",
"\n",
" with WorkingDir(working_dir):\n",
" try:\n",
" run_bash(f\"gmx energy -f npt-prod/ener.edr -o energy-temp.xvg <<< 'volume\\ndensity'\")\n",
" data, header = gt.xvg.load('energy-temp.xvg')\n",
" except:\n",
" print('..no data..')\n",
" continue\n",
" run_bash(\"rm -f energy-temp.xvg\")\n",
" # volume, concentration\n",
" for block in range(n_blocks):\n",
" block_start = block * block_len\n",
" block_end = block_start + block_len\n",
" block_data = data[data['Time (ps)'].between(block_start, block_end)]\n",
" volume = block_data['Volume'].mean()\n",
" df_dens.at[(system['type']['name'], system['force-field']['name'], system['molar-mixing-ratio'], block), 'volume'] = volume\n",
" density = block_data['Density'].mean() * 1e-3\n",
" try:\n",
" n_nacl = system['moltypes'][1]['nmols']\n",
" except IndexError:\n",
" n_nacl = 0.0\n",
" concentration = n_nacl / const.N_A / (volume * 1e-24)\n",
" df_dens.at[(system['type']['name'], system['force-field']['name'], system['molar-mixing-ratio'], block), 'concentration'] = concentration\n",
" df_dens.at[(system['type']['name'], system['force-field']['name'], system['molar-mixing-ratio'], block), 'density'] = density\n",
"\n",
" # isothermal compressibility\n",
" for block in range(n_blocks):\n",
" block_start = block * block_len\n",
" block_end = block_start + block_len\n",
" try:\n",
" run_bash(f\"gmx energy -f npt-prod/ener.edr -o temp.xvg -fluct_props -driftcorr -b {block_start} -e {block_end} <<< 'Temperature\\nVolume' > temp.txt\")\n",
" except:\n",
" print('..no data..')\n",
" continue\n",
" with open('temp.txt', 'r') as f:\n",
" for line in f.readlines():\n",
" if line.startswith('Isothermal Compressibility Kappa'):\n",
" isothermal_compressibility = float(line.split('=')[1].split('(')[0])\n",
" run_bash(\"rm -f temp.xvg temp.txt\")\n",
" df_dens.at[(system['type']['name'], system['force-field']['name'], system['molar-mixing-ratio'], block), 'compressibility'] = isothermal_compressibility\n",
"load_dens()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_dens.tail()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"with open('df_dens.pkl', 'wb') as f:\n",
" pickle.dump(df_dens, f)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"with open('df_dens.pkl', 'rb') as f:\n",
" df_dens = pickle.load(f)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### literature data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# molar mixing ratio - density\n",
"\n",
"# CaCl2, KCl, NaCL literature data https://handymath.com/cgi-bin/nacltble.cgi?submit=Entry\n",
"# \"Perry's Chemical Engineers' Handbook\" by Robert H. Perry, Don Green, 7th Edition page 140 (2-99)\n",
"# densities in g/ml\n",
"# % is mass fraction (95 percent sure)\n",
"\n",
"# LiCl data from tanaka 1991\n",
"\n",
"density_w_lit_dict = {\n",
" 'water-cacl2_': {\n",
" 'mass-fraction': np.array((2, 4, 8, 12, 16, 20, 25, 30, 35, 40)) / 100,\n",
" 'density': {\n",
" 293.15: np.array((1.0148, 1.0316, 1.0659, 1.1015, 1.1386, 1.1775, 1.2284, 1.2816, 1.3373, 1.3957)),\n",
" 303.15: np.array((1.0120, 1.0286, 1.0626, 1.0978, 1.1345, 1.1730, 1.2236, 1.2764, 1.3316, 1.3895))\n",
" },\n",
" 'molar-mass': sum((at['mass'] for at in (atomtypes[atomname] for atomname in ('CA', 'CL', 'CL')))),\n",
" },\n",
" 'water-kcl': {\n",
" 'mass-fraction': np.array((1, 2, 4, 8, 12, 16, 20, 24)) / 100,\n",
" 'density': {\n",
" 298.15: np.array((1.00342, 1.00977, 1.02255, 1.04847, 1.07506, 1.10245, 1.13072, 1.15995)),\n",
" 313.15: np.array((0.99847, 1.00471, 1.01727, 1.04278, 1.06897, 1.09600, 1.12399, 1.15299))\n",
" },\n",
" 'molar-mass': sum((at['mass'] for at in (atomtypes[atomname] for atomname in ('K', 'CL')))),\n",
" },\n",
" 'water-licl': {\n",
" 'molality': np.array((0.05, 0.10, 0.50, 1.00, 2.00, 3.00, 4.00, 6.00, 8.00, 10.00, 12.00, 14.00, 16.00, 18.00, 20.00))/1e3, # mol / g\n",
" 'density': {\n",
" 298.15: np.array((998.28, 999.47, 1009.02, 1020.08, 1041.63, 1061.53, 1080.18, 1114.82, 1146.26, 1175.89, 1206.03, 1231.39, 1255.85, 1278.67, 1298.72))/1000,\n",
" 303.15: np.array((996.90, 998.08, 1007.60, 1018.71, 1040.14, 1060.04, 1078.69, 1113.33, 1144.71, 1174.31, 1204.38, 1229.68, 1254.04, 1276.78, 1296.77))/1000,\n",
" },\n",
" 'molar-mass': sum((at['mass'] for at in (atomtypes[atomname] for atomname in ('LI', 'CL')))),\n",
" },\n",
" 'water-nacl': {\n",
" 'mass-fraction': np.array((1, 2, 4, 8, 12, 16, 20, 24, 26)) / 100,\n",
" 'density': {\n",
" 298.15: np.array((1.00409, 1.01112, 1.02530, 1.05412, 1.08365, 1.11401, 1.14533, 1.17776, 1.19443)),\n",
" 313.15: np.array((0.99908, 1.00593, 1.01977, 1.04798, 1.07699, 1.10688, 1.13774, 1.16971, 1.18614)),\n",
" },\n",
" 'molar-mass': sum((at['mass'] for at in (atomtypes[atomname] for atomname in ('NA', 'CL')))),\n",
" },\n",
"}\n",
"\n",
"def mass_fraction_from_molality(b, M):\n",
" return 1 / (1 + (1 / (b*M)))\n",
"\n",
"def concentration_from_mass_fraction(w, M, rho):\n",
" return w / M * (rho * 1000)\n",
"\n",
"def interpolate_density(density_dict, T):\n",
" \"\"\"Interpolate density array between two temperatures.\"\"\"\n",
" density_new = np.zeros_like(tuple(density_dict.values())[0])\n",
" T1, T2 = tuple(density_dict.keys())\n",
" for i, (d1, d2) in enumerate(zip(*density_dict.values())):\n",
" density_new[i] = np.interp(T, (T1, T2), (d1, d2))\n",
" return density_new"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"# plot literature densities\n",
"def plot_dens_lit():\n",
" \n",
" xaxis = 'c'\n",
" \n",
" with mpl.rc_context(rc={\n",
" 'legend.labelspacing': 0.1\n",
" }):\n",
" fig, ax = plt.subplots(figsize=(5, 3))\n",
" \n",
" for sys_type, sys_type_data in density_w_lit_dict.items():\n",
" if 'mass-fraction' in sys_type_data:\n",
" w = sys_type_data['mass-fraction']\n",
" else:\n",
" w = mass_fraction_from_molality(sys_type_data['molality'], sys_type_data['molar-mass'])\n",
" densities = interpolate_density(sys_type_data['density'], 300)\n",
" c = concentration_from_mass_fraction(w, sys_type_data['molar-mass'], densities)\n",
" if xaxis == 'c':\n",
" ax.plot(c, densities, '.-', label=f\"{sys_type}\")\n",
" elif xaxis == 'w':\n",
" ax.plot(w * 100, densities, '.-', label=f\"{sys_type}\")\n",
"\n",
" if xaxis == 'c':\n",
" ax.set_xlabel(r\"$c$(salt) in mol/l\")\n",
" elif xaxis == 'w':\n",
" ax.set_xlabel(r\"$w$(salt)\")\n",
" ax.set_xlim(0)\n",
" ax.set_ylabel(r\"$\\rho$ in g/mol\")\n",
" ax.legend(frameon=False)\n",
" fig.tight_layout()\n",
" plt.show()\n",
"plot_dens_lit()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# plot densities vs concentration\n",
"def plot_dens_vs_w():\n",
" ylim_dict = {\n",
" 'water-cacl2_': (0.99, 1.40),\n",
" 'water-kcl': (0.99, 1.25),\n",
" 'water-licl': (0.99, 1.12),\n",
" 'water-nacl': (0.99, 1.2),\n",
" }\n",
"\n",
" mpl_rc = {\n",
" 'legend.labelspacing': 0.5,\n",
" 'legend.columnspacing': 1.5,\n",
" 'legend.handlelength': 1.8,\n",
" }\n",
"\n",
" with plt.rc_context({**mpl_rc_global, **mpl_rc}):\n",
" fig, axes = plt.subplots(figsize=(4.62, 2.5), nrows=2, ncols=2, constrained_layout=True, sharex='all', dpi=200)\n",
" fig.set_constrained_layout_pads(w_pad=0.03, h_pad=0.00, hspace=0.0, wspace=0.0)\n",
" for s, sys_type in enumerate((st for st in system_types if st != 'water-pure')):\n",
" print(s, sys_type)\n",
" ax = axes.flat[s]\n",
" \n",
" for f, ff in enumerate((ff for ff in force_fields.values() if 'conc-range' in ff['tags'])):\n",
" #print(ff['name'])\n",
" index = (sys_type, ff['name'], slice(None), slice(None))\n",
" x = df_dens.loc[index, 'concentration'].groupby(level=2).mean().to_numpy()\n",
" y = df_dens.loc[index, 'density'].groupby(level=2).mean().to_numpy()\n",
" yerr = df_dens.loc[index, 'density'].groupby(level=2).std().to_numpy()\n",
" ax.errorbar(x[1:], y[1:], yerr=yerr[1:], marker='.', linestyle=':', label=ff_short_names[ff['name']], color=ff_colors[ff['name']])\n",
" print(x[1:3])\n",
"\n",
" # literature\n",
" sys_type_data = density_w_lit_dict[sys_type]\n",
" if 'mass-fraction' in sys_type_data:\n",
" w = sys_type_data['mass-fraction']\n",
" else:\n",
" w = mass_fraction_from_molality(sys_type_data['molality'], sys_type_data['molar-mass'])\n",
" densities = interpolate_density(sys_type_data['density'], 300)\n",
" c = concentration_from_mass_fraction(w, sys_type_data['molar-mass'], densities)\n",
" ax.plot(c, densities, '.-', color='k', label=\"exp.\")\n",
"\n",
" #ax.set_xlim(0, max(x)+0.2)\n",
" ax.set_xlim(0, 5.1)\n",
" ax.set_ylim(ylim_dict[sys_type])\n",
" ax.text(.05, .83, sys_type_short_names[sys_type],\n",
" horizontalalignment='left', transform=ax.transAxes)\n",
" for ax in axes[1]:\n",
" ax.set_xlabel(r\"$c$(Salt) in mol/l\")\n",
" for ax in axes[:, 0]:\n",
" ax.set_ylabel(r\"$\\rho$ in g/ml\")\n",
" \n",
" handles, labels = axes[-1][-1].get_legend_handles_labels()\n",
" #labels, handles = zip(*sorted(zip(labels, handles), key=lambda t: t[0], reverse=False))\n",
" order = [1, 4, 2, 3, 5, 6, 0]\n",
" handles, labels = [handles[idx] for idx in order], [labels[idx] for idx in order]\n",
" fig.legend(handles, labels, ncol=4, loc='lower center', bbox_to_anchor=(0.52, 0.97),)\n",
" fig.savefig(os.path.join('../figures', f\"densities.pdf\"), bbox_inches='tight')\n",
" plt.show()\n",
"plot_dens_vs_w()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!cp -a ../figures/densities.pdf ~/research/output/ion-shortrange-paper/figures/"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### RMSD density"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def rmsd_density():\n",
" \n",
" dens_rmsd_dict = collections.defaultdict(lambda: 0)\n",
"\n",
" for s, sys_type in enumerate((st for st in system_types if st != 'water-pure')):\n",
" print(s, sys_type)\n",
" \n",
" # experimental from literature\n",
" sys_type_data = density_w_lit_dict[sys_type]\n",
" if 'mass-fraction' in sys_type_data:\n",
" w = sys_type_data['mass-fraction']\n",
" else:\n",
" w = mass_fraction_from_molality(sys_type_data['molality'], sys_type_data['molar-mass'])\n",
" rho_ref = interpolate_density(sys_type_data['density'], 300)\n",
" c_ref = concentration_from_mass_fraction(w, sys_type_data['molar-mass'], rho_ref)\n",
"\n",
" for f, ff in enumerate((ff for ff in force_fields.values() if 'conc-range' in ff['tags'])):\n",
" #print(ff['name'])\n",
" index = (sys_type, ff['name'], slice(None), slice(None))\n",
" c = np.array(df_dens.loc[index, 'concentration'].groupby(axis=0, level=2).mean())\n",
" rho = np.array(df_dens.loc[index, 'density'].groupby(axis=0, level=2).mean())\n",
" # ignore first and last point, excluding 0 and up to 3 mol/l\n",
" c = c[1:-1]\n",
" rho = rho[1:-1]\n",
" # msd\n",
" msd = np.sum((rho - np.interp(c, c_ref, rho_ref))**2)\n",
" dens_rmsd_dict[(ff['name'], 'rmsd')] += msd\n",
" #dens_rmsd_dict[(ff['name'], 'counter')] += 1.0\n",
" \n",
" for ff_name, ff in force_fields.items():\n",
" if 'dummy' in ff['tags']:\n",
" continue\n",
" #print(ff_name)\n",
" #print(dens_rmsd_dict[(ff_name, 'counter')])\n",
" df_rmsd.at[ff_name, 'density'] = np.sqrt(dens_rmsd_dict[(ff_name, 'rmsd')]) # / dens_rmsd_dict[(ff_name, 'counter')]\n",
" \n",
"rmsd_density()\n",
"df_rmsd"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true,
"tags": []
},
"outputs": [],
"source": [
"# not working currently\n",
"def plot_compress():\n",
" with mpl.rc_context(rc={'figure.dpi': 150}):\n",
" fig, ax = plt.subplots(figsize=(5, 3))\n",
" for c, column in enumerate(df_dens.columns):\n",
" x = df_conc.mean(axis=0, level=0)[column]\n",
" xerr = df_conc.std(axis=0, level=0)[column]\n",
" y = df_compress.mean(axis=0, level=0)[column]\n",
" yerr = df_compress.std(axis=0, level=0)[column]\n",
" #ax.errorbar(y.index, y, yerr=yerr, fmt='s:', label=column)\n",
" ax.errorbar(x, y, xerr=xerr, yerr=yerr, marker='so>'[c%3], linestyle=':', label=label_dict[column])\n",
" ax.set_xlim(0)\n",
" ax.set_xlabel(r\"$r$(NaCL)\")\n",
" ax.set_ylabel(r\"$\\beta_T$ in 1/Pa\")\n",
" ax.legend()\n",
" fig.tight_layout()\n",
" fig.savefig(os.path.join('../figures', f\"water-nacl-compress.png\"), dpi=300)\n",
" plt.show()\n",
"plot_compress()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## water diffusion"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### load"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"molar_mixing_ratios = list(OrderedSet([sys['molar-mixing-ratio'] for sys in system_generator(*systems_md)]))\n",
"\n",
"system_types_to_show = list(OrderedSet(([sys['type']['name'] for sys in system_generator(*systems_md)])))\n",
"columns = ['D', 'Derr']\n",
"index = pd.MultiIndex.from_product((\n",
" system_types_to_show,\n",
" [ffn for ffn, ff in force_fields.items() if 'conc-range' in ff['tags']],\n",
" molar_mixing_ratios\n",
"))\n",
"df_diff = pd.DataFrame(columns=columns, index=index, dtype=float)\n",
"df_diff.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def correct_diffusivity_fse(D, T, boxlength, water_model):\n",
" \"\"\"finite size correction for diffusivity\n",
" explained nicely here: https://www.tandfonline.com/doi/full/10.1080/08927022.2020.1810685\n",
" D in 10^{-5} cm^2/s\n",
" T in K\n",
" boxlength in nm\n",
" \"\"\"\n",
" xi = 2.83729 # dimensionless for cubic periodic boundary conditions\n",
" eta = {\n",
" 'water-spce': 7.29e-4,\n",
" 'water-tip4p2005': 8.55e-4,\n",
" }[water_model] # kg/(s*m)\n",
" D_corr = (D / 1e9 + xi * const.k * T / (6 * np.pi * eta * boxlength/1e9)) * 1e9\n",
" return D_corr\n",
"\n",
"\n",
"def load_diff():\n",
" for system in (sys for sys in system_generator(*systems_md) if 'conc-range' in sys['tags']):\n",
" #print(f\"system {system['name']}\")\n",
" working_dir = os.path.join(system['name'])\n",
" \n",
" mass_fraction = system['mass-fraction']\n",
" df_dens.loc[(system['type']['name'], system['force-field']['name'], system['molar-mixing-ratio'], slice(None)), 'mass-fraction'] = mass_fraction\n",
"\n",
" with WorkingDir(working_dir):\n",
" with open('npt-prod/msd-SOL.txt', 'r') as f:\n",
" for line in f.readlines():\n",
" if line.startswith('D['):\n",
" D, Derr = map(float, line.split(']')[1].split(')')[0].split(' (+/- '))\n",
" boxlength, _, _ = gt.gro.get_box('npt-prod/confout.gro')\n",
" water_model = PARAMETRIC_FORCE_FIELDS[system['force-field']['parametric-ff']]['water-model']\n",
" D_corr = correct_diffusivity_fse(D, system['temperature'], boxlength, water_model)\n",
" df_diff.at[(system['type']['name'], system['force-field']['name'], system['molar-mixing-ratio']), 'D'] = D\n",
" df_diff.at[(system['type']['name'], system['force-field']['name'], system['molar-mixing-ratio']), 'Derr'] = Derr\n",
" df_diff.at[(system['type']['name'], system['force-field']['name'], system['molar-mixing-ratio']), 'Dcorr'] = D_corr\n",
"load_diff()\n",
"df_diff"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# from separate manual simulations\n",
"D_pure = {\n",
" # D, D_err, boxlength\n",
" 'water-spce': {'D': 2.4385, 'D_err': 0.0378, 'boxlength': 5.31181},\n",
" 'water-tip4p2005': {'D': 2.1116, 'D_err': 0.0587, 'boxlength': 5.31485},\n",
"}\n",
"for water_ff, data_dict in D_pure.items():\n",
" D_corr = correct_diffusivity_fse(data_dict['D'], 300, data_dict['boxlength'], water_ff)\n",
" data_dict['D_corr'] = D_corr\n",
" print(D_corr)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### literature data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# from müller hertz 1996\n",
"# by nmr, 25 °C\n",
"water_diff_lit_data = {\n",
" 'water-cacl2_': {\n",
" 'molality': np.array([0.0, 1.00, 2.02, 3.02, 4.08, 5.29, 6.02, 7.10, 8.18, 9.01, 10.01, 10.11, 11.02]),\n",
" 'diffusion-coefficient': np.array([2.30, 1.88, 1.43, 1.07, 0.749, 0.494, 0.384, 0.255, 0.180, 0.139, 0.101, 0.0993, 0.0773]),\n",
" },\n",
" 'water-kcl': {\n",
" 'molality': np.array([0.0, 1.02, 2.00, 3.05, 4.00, 4.60]),\n",
" 'diffusion-coefficient': np.array([2.30, 2.38, 2.38, 2.34, 2.31, 2.30]),\n",
" },\n",
" 'water-licl': {\n",
" 'molality': np.array([0.0, 1.00, 2.03, 3.10, 4.06, 5.02, 6.06, 7.02, 7.98, 9.19, 10.04, 11.04, 12.01, 12.96, 14.07, 15.21,\n",
" 16.31, 16.93, 18.00, 18.92, 20.03]),\n",
" 'diffusion-coefficient': np.array([2.30, 2.09, 1.84, 1.63, 1.44, 1.28, 1.12, 0.991, 0.876, 0.730, 0.656, 0.565, 0.489, 0.424,\n",
" 0.369, 0.318, 0.276, 0.259, 0.219, 0.200, 0.175]),\n",
" },\n",
" 'water-nacl': {\n",
" 'molality': np.array([0.0, 1.00, 2.00, 3.00, 4.00, 5.00, 5.90]),\n",
" 'diffusion-coefficient': np.array([2.30, 2.17, 2.02, 1.87, 1.71, 1.57, 1.43]),\n",
" },\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"def plot_diff_water(use_finite_size_correction=True):\n",
" ylim_dict = {\n",
" 'water-cacl2_': (0.08, 1.25),\n",
" 'water-kcl': (0.08, 1.25),\n",
" 'water-licl': (0.3, 1.15),\n",
" 'water-nacl': (0.3, 1.15),\n",
" }\n",
"\n",
" mpl_rc = {\n",
" 'legend.labelspacing': 0.5,\n",
" 'legend.columnspacing': 1.5,\n",
" 'legend.handlelength': 1.8,\n",
" }\n",
"\n",
" with plt.rc_context({**mpl_rc_global, **mpl_rc}):\n",
" fig, axes = plt.subplots(figsize=(4.62, 2.5), nrows=2, ncols=2, constrained_layout=True, sharex='col', sharey='row', dpi=200)\n",
" fig.set_constrained_layout_pads(w_pad=0.03, h_pad=0.00, hspace=0.0, wspace=0.0)\n",
" for s, sys_type in enumerate((st for st in system_types if st != 'water-pure')):\n",
" #print(s, sys_type['name'])\n",
" ax = axes.flat[s]\n",
" \n",
" for f, ff in enumerate((ff for ffn, ff in force_fields.items() if 'conc-range' in ff['tags'])):\n",
" #print(ff['name'])\n",
" index = (sys_type, ff['name'], slice(None))\n",
" index_pure = ('water-pure', ff['name'], 0.0)\n",
" concentration = df_dens.loc[index, 'concentration'].groupby(axis=0, level=2).mean()\n",
" x = concentration.to_numpy()\n",
" if use_finite_size_correction:\n",
" y = (df_diff.loc[index, 'Dcorr'] / df_diff.at[index_pure, 'Dcorr']).to_numpy()\n",
" yerr = (df_diff.loc[index, 'Derr'] / df_diff.at[index_pure, 'Dcorr']).to_numpy()\n",
" #yerr = df_diff.loc[index, 'Derr'].to_numpy()\n",
" else:\n",
" y = (df_diff.loc[index, 'D'] / df_diff.at[index_pure, 'D']).to_numpy()\n",
" yerr = (df_diff.loc[index, 'Derr'] / df_diff.at[index_pure, 'D']).to_numpy()\n",
" ax.errorbar(x, y, yerr=yerr, marker='.', linestyle=':', label=ff_short_names[ff['name']], color=ff_colors[ff['name']])\n",
"\n",
" # literature\n",
" molality = water_diff_lit_data[sys_type]['molality']\n",
" # from molality to concentration we can use crc data\n",
" osmp_lit_dict_st = osmp_lit_dict[sys_type]\n",
" concentration = np.interp(molality,\n",
" osmp_lit_dict_st['crc-liquid-data']['molality'],\n",
" osmp_lit_dict_st['crc-liquid-data']['concentration'], left=np.nan, right=np.nan)\n",
" x_lit = concentration\n",
" y_lit = water_diff_lit_data[sys_type]['diffusion-coefficient']\n",
" y_lit /= y_lit[0]\n",
" ax.plot(x_lit, y_lit, '-', marker='.', color='k', label=\"exp.\")\n",
"\n",
" #ax.set_xlim(0, max(x)+0.2)\n",
" ax.set_xlim(0, 5.1)\n",
" ax.set_ylim(ylim_dict[sys_type])\n",
" ax.text(.05, .10, sys_type_short_names[sys_type],\n",
" horizontalalignment='left', transform=ax.transAxes)\n",
" \n",
" for ax in axes[1]:\n",
" ax.set_xlabel(r\"$c$(Salt) in mol/l\")\n",
" for ax in axes[:, 0]:\n",
" ax.set_ylabel(r\"$D / D_0$\")\n",
" \n",
" handles, labels = axes[-1][-1].get_legend_handles_labels()\n",
" #labels, handles = zip(*sorted(zip(labels, handles), key=lambda t: t[0], reverse=False))\n",
" order = [1, 4, 2, 3, 5, 6, 0]\n",
" handles, labels = [handles[idx] for idx in order], [labels[idx] for idx in order]\n",
" fig.legend(handles, labels, ncol=4, loc='lower center', bbox_to_anchor=(0.52, 0.97),)\n",
" fig.savefig(os.path.join('../figures', f\"water-diffusivity.pdf\"), bbox_inches='tight')\n",
" plt.show()\n",
" \n",
"plot_diff_water(use_finite_size_correction=True)\n",
"#plot_diff_water(use_finite_size_correction=False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!cp -a ../figures/water-diffusivity.pdf ~/research/output/ion-shortrange-paper/figures/"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### RMSD diffusion"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true,
"tags": []
},
"outputs": [],
"source": [
"def rmsd_diffusion():\n",
" \n",
" diff_rmsd_dict = collections.defaultdict(lambda: 0)\n",
"\n",
" for s, sys_type in enumerate((st for st in system_types if st != 'water-pure')):\n",
" print(s, sys_type)\n",
" \n",
" # experimental from literature\n",
" molality = water_diff_lit_data[sys_type]['molality']\n",
" # from molality to concentration we can use crc data\n",
" osmp_lit_dict_st = osmp_lit_dict[sys_type]\n",
" concentration = np.interp(molality,\n",
" osmp_lit_dict_st['crc-liquid-data']['molality'],\n",
" osmp_lit_dict_st['crc-liquid-data']['concentration'], left=np.nan, right=np.nan)\n",
" x_lit = concentration\n",
" y_lit = water_diff_lit_data[sys_type]['diffusion-coefficient']\n",
" y_lit /= y_lit[0]\n",
"\n",
" for f, ff in enumerate((ff for ff in force_fields.values() if 'conc-range' in ff['tags'])):\n",
" #print(ff['name'])\n",
" index = (sys_type, ff['name'], slice(None))\n",
" index_pure = ('water-pure', ff['name'], 0.0)\n",
" concentration = df_dens.loc[index, 'concentration'].groupby(axis=0, level=2).mean()\n",
" x = np.array(concentration)\n",
" y = np.array(df_diff.loc[index, 'Dcorr'] / df_diff.at[index_pure, 'Dcorr'])\n",
" # ignore first and last point, excluding 0 and up to 3 mol/l\n",
" x = x[1:-1]\n",
" y = y[1:-1]\n",
" # msd\n",
" msd = np.sum((y - np.interp(x, x_lit, y_lit))**2)\n",
" diff_rmsd_dict[(ff['name'], 'msd')] += msd\n",
" #diff_rmsd_dict[(ff['name'], 'counter')] += 1.0\n",
" \n",
" for ff_name, ff in force_fields.items():\n",
" if 'dummy' in ff['tags']:\n",
" continue\n",
" df_rmsd.at[ff_name, 'diffusion'] = np.sqrt(diff_rmsd_dict[(ff_name, 'msd')]) # / diff_rmsd_dict[(ff_name, 'counter')]\n",
" \n",
"rmsd_diffusion()\n",
"df_rmsd"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## residence times"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### residence settings"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"residence_settings_names = [\n",
" \"allNA_0.33-400-2.0-T\",\n",
" \"allCL_0.38-400-2.0-T\",\n",
" #\"allNA_0.33-200-2.0-T\", # checking time dependence\n",
" #\"allNA_0.33-60-2.0-T\",\n",
" #\"allNA_0.33-400-2.0-F\", # checking outer_spans dependence\n",
" #\"allNA_0.33-400-2.0-F-d\", # checking delay dependence\n",
" #\"1NA_0.33-400-2.0-T\", # checking single ion shell dependence\n",
"]\n",
"\n",
"residence_settings = []\n",
"for sel in (\n",
" {'name': 'allNA_0.33', 'selection': 'atomname OW and within 0.33 of resname NA'},\n",
" {'name': 'allCL_0.38', 'selection': 'atomname OW and within 0.38 of resname CL'},\n",
" {'name': '1NA_0.33', 'selection': 'atomname OW and within 0.33 of atomnr 15001'},\n",
"):\n",
" for time in (60, 200, 400):\n",
" for max_f in (2.0,): # common value from literature\n",
" for outer_spans in (True, False):\n",
" for delay in (True, False):\n",
" name = f\"{sel['name']}-{time}-{max_f:.1f}-{'T' if outer_spans else 'F'}{'-d' if delay else ''}\"\n",
" if name in residence_settings_names:\n",
" residence_setting = {\n",
" 'name': name,\n",
" 'sel': sel,\n",
" 'time': time,\n",
" 'max_f': max_f,\n",
" 'outer_spans': outer_spans,\n",
" 'delay': delay,\n",
" }\n",
" residence_settings.append(residence_setting)\n",
"pd.DataFrame(residence_settings)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### calculate residence acf on cluster"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def run_acf():\n",
" remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'enzo', ntasks=1)\n",
" for system in (sys for sys in systems if 'resacf' in sys['tags']):\n",
" print(f\"system {system['name']}\")\n",
" for residence_setting in residence_settings:\n",
" print(residence_setting['name'])\n",
" working_dir = os.path.join(system['name'], 'resacf', residence_setting['name'])\n",
" remote_dir = os.path.join(remote_dir_base, system['name'], 'resacf', residence_setting['name'])\n",
" with WorkingDir(working_dir):\n",
" # check if already done\n",
" if os.path.isfile('residence-acf.xvg'):\n",
" print('..results present locally..')\n",
" continue\n",
"\n",
" # mkdir\n",
" run_bash(f\"ssh {remote_host} mkdir -p {remote_dir}\")\n",
"\n",
" # flags\n",
" outer_flag = \"\"\n",
" if residence_setting['outer_spans']:\n",
" outer_flag = \"--outer-spans\"\n",
" delay_flag = \"\"\n",
" if residence_setting['delay']:\n",
" delay_flag = \"--delay\"\n",
"\n",
" # commands to be run on compute nodes\n",
" script = remote_header + rf\"\"\"\n",
"if [[ ! -f residence-acf.xvg ]]; then\n",
" gmx select -f ../../prod/traj_comp.xtc -s ../../prod/topol.tpr -oi select.dat -e {residence_setting['time']} -select '{residence_setting['sel']['selection']}'\n",
" ~/bin/resacf.py select.dat residence-acf.xvg --end {residence_setting['time']} --max-false {residence_setting['max_f']} {outer_flag} {delay_flag}\n",
"fi\n",
"\"\"\" + remote_footer\n",
"\n",
" jobid = gt.remote.run_slurm_script(script, remote_host, remote_dir, dry_run=False)\n",
" print(jobid)\n",
" if jobid != None:\n",
" jobids.append(jobid)\n",
"run_acf()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### check job status"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"jobids = check_job_stati(jobids, remote_host)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### copy results from cluster"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for system in (sys for sys in systems if 'resacf' in sys['tags']):\n",
" print(f\"system {system['name']}\")\n",
" for residence_setting in residence_settings:\n",
" print(residence_setting['name'])\n",
" working_dir = os.path.join(system['name'], 'resacf', residence_setting['name'])\n",
" remote_dir = os.path.join(remote_dir_base, system['name'], 'resacf', residence_setting['name'])\n",
"\n",
" with WorkingDir(working_dir):\n",
" filelist = [\"residence-acf.xvg\"]\n",
" try:\n",
" gt.remote.pull_files(filelist, remote_host, remote_dir)\n",
" except:\n",
" print('..no data..')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### load acf"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# keys are (system, residence_setting_name)\n",
"acf_dict = {}\n",
"\n",
"for system in (sys for sys in systems if 'resacf' in sys['tags']):\n",
" print(f\"system {system['name']}\")\n",
" for residence_setting in residence_settings:\n",
" print(residence_setting['name'])\n",
" working_dir = os.path.join(system['name'], 'resacf', residence_setting['name'])\n",
" with WorkingDir(working_dir):\n",
" try:\n",
" # load data\n",
" t, acf, acf_std = np.loadtxt('residence-acf.xvg', dtype=float, comments=['#', '@']).T\n",
" # store data\n",
" acf_dict[(system['name'], residence_setting['name'])] = (t, acf, acf_std)\n",
" except:\n",
" print('..no data..')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### integrate acf"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"tau_dict = {}\n",
"\n",
"for system in (sys for sys in systems if 'resacf' in sys['tags']):\n",
" print(f\"system {system['name']}\")\n",
" for residence_setting in residence_settings:\n",
" print(residence_setting['name'])\n",
" try:\n",
" # load data\n",
" t, acf, acf_std = acf_dict[(system['name'], residence_setting['name'])]\n",
" except:\n",
" print('..no data..')\n",
" continue\n",
" # fit data\n",
" #(tau,), _ = optimize.curve_fit(exp_decay, t, acf, p0=1)\n",
" # integrate\n",
" tau = np.trapz(x=t, y=acf)\n",
" # store fit\n",
" tau_dict[(system['name'], residence_setting['name'])] = tau"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### compare residence settings"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"residence_settings_to_show = [rs for rs in residence_settings if rs['name'] in [\n",
" 'allNA_0.33-60-2.0-T',\n",
" 'allNA_0.33-400-2.0-T',\n",
" '1NA_0.33-400-2.0-T',\n",
" #'allCL_0.38-400-2.0-T',\n",
"]]\n",
"label_dict = {\n",
" 'allNA_0.33-60-2.0-T': r'400 ps',\n",
" 'allNA_0.33-400-2.0-T': r'60 ps',\n",
" '1NA_0.33-400-2.0-T': r'400 ps, 1 Na',\n",
"}\n",
"linestyles = ['-', '--', ':', '-.']\n",
"\n",
"\n",
"for system in (sys for sys in systems if 'resacf' in sys['tags']):\n",
" print(f\"system {system['name']}\")\n",
" \n",
" fig, ax = plt.subplots(figsize=(4, 3))\n",
"\n",
" for r, residence_setting in enumerate(residence_settings_to_show):\n",
" print(residence_setting['name'])\n",
" try:\n",
" t, acf, acf_std = acf_dict[(system['name'], residence_setting['name'])]\n",
" tau = tau_dict[(system['name'], residence_setting['name'])]\n",
" label = label_dict.get(residence_setting['name'], 'foo')\n",
" label += fr\", $\\tau = {tau:.1f}$ ps\"\n",
" line, = ax.plot(t, acf, label=label, linestyle=linestyles[r%4])\n",
" ax.fill_between(t, acf-acf_std, acf+acf_std, alpha=0.3, color=line.get_color())\n",
" except:\n",
" print('..no data..')\n",
" \n",
" ax.set_xlim(0, 25)\n",
" ax.set_ylim(0)\n",
" ax.set_xlabel(r'$t$')\n",
" ax.set_ylabel(r'$S_R(t)$')\n",
" ax.legend(frameon=False)\n",
" fig.tight_layout()\n",
" fig.savefig(f\"../figures/resacf-{system['name'].replace('/', '-')}.png\", dpi=300)\n",
" plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### compare systems (force fields)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"residence_settings_to_show = [rs for rs in residence_settings if rs['name'] in [\n",
" #'allNA_0.33-60-2.0-T',\n",
" 'allNA_0.33-400-2.0-T',\n",
" 'allCL_0.38-400-2.0-T',\n",
"]]\n",
"\n",
"label_dict = {\n",
" 'water5000-nacl50/azade-co0.7': 'Azade',\n",
" 'water5000-nacl50/opls-co0.7-q0.75': r'ECC OPLS',\n",
" 'water5000-nacl50/opls-co0.7-q1.0': r'OPLS',\n",
" 'water5000-nacl50/eccr1-co0.7': 'ECCR',\n",
" 'water5000-nacl50/altern1-co0.7-q0.75': r'ECC OPLS IMC',\n",
" 'water5000-nacl50/altern2-co0.7-q0.75': r'ECCR IMC impr.',\n",
" 'water5000-nacl50/fuentes1-co0.7': 'NaCl/ε',\n",
"}\n",
"linestyles = ['-', '--', ':', '-.']\n",
"mpl_rc = {\n",
" 'figure.dpi': 150,\n",
" #'legend.title_fontsize': 8,\n",
" #'legend.fontsize': 8,\n",
" #'legend.labelspacing': 0.1,\n",
"}\n",
"\n",
"with mpl.rc_context(mpl_rc):\n",
" for residence_setting in residence_settings_to_show:\n",
" print(residence_setting['name'])\n",
"\n",
" fig, ax = plt.subplots(figsize=(4,2.5))\n",
"\n",
" for s, system in enumerate((sys for sys in systems if 'resacf' in sys['tags'])):\n",
" print(f\"system {system['name']}\")\n",
" try:\n",
" t, acf, acf_std = acf_dict[(system['name'], residence_setting['name'])]\n",
" tau = tau_dict[(system['name'], residence_setting['name'])]\n",
" label = label_dict.get(system['name'], 'foo')\n",
" label += fr\", $\\tau = {tau:.1f}$ ps\"\n",
" line, = ax.plot(t, acf, label=label, linestyle=linestyles[s%4])\n",
" ax.fill_between(t, acf-acf_std, acf+acf_std, alpha=0.3)\n",
" #ax.plot(t, exp_decay(t, tau), linestyle=':', color=line.get_color())\n",
" print(tau)\n",
" except:\n",
" print('..no data..')\n",
"\n",
" ax.set_xlim(0, 25)\n",
" ax.set_ylim(0, 1.0)\n",
" ax.set_xlabel(r'$t$ in ps')\n",
" ax.set_ylabel(r'$S_R(t)$')\n",
" # reorder legend\n",
" handles, labels = plt.gca().get_legend_handles_labels()\n",
" #order = [3,0,2,1]\n",
" #order = [0,1,2,3]\n",
" #ax.legend([handles[idx] for idx in order],[labels[idx] for idx in order], frameon=False)\n",
" ax.legend(frameon=False)\n",
" fig.tight_layout()\n",
" fig.savefig(f\"../figures/resacf-{residence_setting['name']}.png\", dpi=300)\n",
" plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## thermal expansion"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"n_blocks = 5\n",
"block_len = 360 # in ps\n",
"system_types_to_show = list(OrderedSet(([sys['type']['name'] for sys in system_generator(*systems_md)])))\n",
"columns = ['volume']\n",
"index = pd.MultiIndex.from_product((\n",
" system_types_to_show,\n",
" [ff for ff in force_fields],\n",
" DeltaTs,\n",
" range(n_blocks)\n",
"))\n",
"df_te = pd.DataFrame(columns=columns, index=index, dtype=float)\n",
"df_te"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"df_therm_exp = pd.DataFrame()\n",
"\n",
"def load_therm_exp():\n",
" for system in (sys for sys in system_generator(*systems_md) if 'therm-exp' in sys['tags']):\n",
" print(f\"system {system['name']}\")\n",
" working_dir = os.path.join(system['name'])\n",
" \n",
" mass_fraction = system['mass-fraction']\n",
" df_te.loc[(system['type']['name'], system['force-field']['name'], slice(None), slice(None)), 'mass-fraction'] = mass_fraction\n",
"\n",
" with WorkingDir(working_dir):\n",
" for DeltaT in DeltaTs:\n",
" T = system['temperature'] + DeltaT\n",
" folder = f\"therm-exp-{T:.0f}\"\n",
" try:\n",
" run_bash(f\"gmx energy -f {folder}/ener.edr -b 200 -o /tmp/energy-temp.xvg <<< 'volume'\")\n",
" data, header = gt.xvg.load('/tmp/energy-temp.xvg')\n",
" run_bash(\"rm -f /tmp/energy-temp.xvg\")\n",
" except:\n",
" print('..no data..')\n",
" continue\n",
" # volume, concentration\n",
" for block in range(n_blocks):\n",
" block_start = block * block_len\n",
" block_end = block_start + block_len\n",
" block_data = data[data['Time (ps)'].between(block_start, block_end)]\n",
" volume = block_data['Volume'].mean()\n",
" df_te.at[(system['type']['name'], system['force-field']['name'], DeltaT, block), 'volume'] = volume\n",
"load_therm_exp()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"df_te.loc[('water-nacl', 'netz-co0.9tc', slice(None), slice(None)), slice(None)]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def calc_therm_exp():\n",
" for system in (sys for sys in system_generator(*systems_md) if 'therm-exp' in sys['tags']):\n",
" print(f\"system {system['name']}\")\n",
" \n",
" for DeltaT_pair in ((DeltaTs[i], DeltaTs[i+1]) for i in range(len(DeltaTs)-1)):\n",
" for block in range(n_blocks):\n",
" V1 = df_te.at[(system['type']['name'], system['force-field']['name'], DeltaT_pair[0], block), 'volume']\n",
" V2 = df_te.at[(system['type']['name'], system['force-field']['name'], DeltaT_pair[1], block), 'volume']\n",
" alpha = (V2 - V1) / (DeltaT_pair[1] - DeltaT_pair[0]) / np.mean((V1, V2))\n",
" df_te.at[(system['type']['name'], system['force-field']['name'], DeltaT_pair[0], block), 'alpha'] = alpha\n",
"calc_therm_exp()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"df_te.loc[(slice(None), slice(None), slice(None), slice(None)), 'alpha'].mean(axis=0, level=(0, 1, 2))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"mpl_rc = {'figure.dpi': 120}\n",
"with plt.rc_context({**mpl_rc_global, **mpl_rc}):\n",
" for systype in OrderedSet(sys['type']['name'] for sys in system_generator(*systems_md) if 'therm-exp' in sys['tags']):\n",
" print(f\"system type {systype}\")\n",
"\n",
" fig, ax = plt.subplots(constrained_layout=True, figsize=(3, 2.2))\n",
" ffs = [ff_short_names[ff] if ff in ff_short_names else ff for ff in force_fields]\n",
" x = range(len(ffs))\n",
" ax.set_xticks(x)\n",
" ax.set_xticklabels(ffs, rotation=90)\n",
" alphas = []\n",
" alphas_std = []\n",
" for ff in force_fields:\n",
" print(f\" force-field {ff}\")\n",
" #for DeltaT_pair in ((DeltaTs[i], DeltaTs[i+1]) for i in range(len(DeltaTs)-1)):\n",
" #print(f\" DeltaT_pair\", DeltaT_pair)\n",
" alpha = float(df_te.loc[(systype, ff, slice(None), slice(None)), 'alpha'].mean(axis=0, level=(0, 1)))\n",
" alpha_std = float(df_te.loc[(systype, ff, slice(None), slice(None)), 'alpha'].std(axis=0, level=(0, 1)))\n",
" #print(f\" alpha {alpha:.5f} ± {alpha_std:.5f}\")\n",
" alphas.append(alpha)\n",
" alphas_std.append(alpha_std)\n",
" ax.bar(x, alphas, yerr=alpha_std)\n",
" plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# osmotic pressure"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## settings"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"system_types_osmp = {stn: st for stn, st in system_types.items() if stn != 'water-pure'}\n",
"#system_types_osmp = {stn: st for stn, st in system_types.items() if stn.startswith('water-cacl2_')}\n",
"force_fields_osmp = {ffn: ff for ffn, ff in force_fields.items() if ('fit' not in ff['tags'] and 'dummy' not in ff['tags'])}\n",
"#force_fields_osmp = {ffn: ff for ffn, ff in force_fields.items() if ('fit' not in ff['tags'] and 'dummy' not in ff['tags'] and ffn.startswith('iff-altern5-netz'))}\n",
"#force_fields_osmp = {ffn: ff for ffn, ff in force_fields.items() if ('fit' not in ff['tags'] and 'dummy' not in ff['tags'] and ffn.startswith('netz'))}\n",
"\n",
"systems_osmp = (system_types_osmp, force_fields_osmp)\n",
"pd.DataFrame(system_generator(*systems_osmp, verbose=False))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"osmp_methods = {\n",
" 'osmp-xy-k4000': {'k': 4000, 'scale': 'xy'}\n",
"}\n",
"pd.DataFrame(osmp_methods)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## create DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"# build empty DataFrame\n",
"iterables = [[sys['name'] for sys in system_generator(*systems_osmp)],\n",
" [osmp_name for osmp_name in osmp_methods],\n",
" ['pre', 'prod']]\n",
"index = pd.MultiIndex.from_product(iterables, names=['system', 'osmp_method', 'run'])\n",
"df_osmp = pd.DataFrame(index=index, columns=[\"Pi_low\", \"Pi_high\", \"Pi_low_err\", \"Pi_high_err\", \"Pi\", \"Pi_err\",\n",
" \"x\", \"x_err\", \"x_inner\", \"x_inner_err\", \"p_tot\", \"p_ex\", \"p_in\",\n",
" \"c_inner\", \"c_inner_err\"], dtype=np.float64)\n",
"df_osmp.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## preparations"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### prepare files"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def prepare_osmotic_pressure():\n",
" for system in system_generator(*systems_osmp):\n",
" print(\"system\", system['name'])\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
"\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
" with WorkingDir(working_dir):\n",
" # make dirs\n",
" run_bash(\"mkdir -p topol equi1 equi2 equi3 pre equi4 prod\")\n",
" all_folders = (\"equi1\", \"equi2\", \"equi3\", \"pre\", \"equi4\", \"prod\")\n",
" \n",
" # single-*.gro\n",
" for mt in system['moltypes']:\n",
" name = mt.get('type', mt['name'])\n",
" run_bash(f\"cp {template_dir}/gro/single-{name}.gro equi1/single-{mt['name']}.gro\")\n",
" \n",
" # copy confout from NPT prod\n",
" run_bash(f\"cp ../npt-prod/confout.gro equi1/npt-confout.gro\")\n",
" \n",
" # table_?_?.xvg\n",
" if 'halftabulated' in system['tags']:\n",
" for table in (f\"table_{pair[0]}_{pair[1]}.xvg\" for pair in itertools.combinations_with_replacement(system['atomtypes-no-h'], 2)\n",
" if (pair[0], pair[1]) in system['force-field']['tabulated-potentials']):\n",
" run_bash(f\"cp {template_dir}/table/{system['force-field']['name']}/{table} topol/\")\n",
" for folder in all_folders:\n",
" run_bash(f\"rm -f {folder}/{table}\")\n",
" for folder in all_folders[1:]: # equi1 is run with LJ parameters\n",
" run_bash(f\"ln -sf ../topol/{table} {folder}/{table}\")\n",
" if 'tabulated' in system['tags']:\n",
" raise Exception('not implemented')\n",
" \n",
" # table.xvg\n",
" if 'halftabulated' in system['tags']:\n",
" run_bash(f\"cp {template_dir}/table/table6-12.xvg topol/table.xvg\")\n",
" for folder in all_folders[1:]:\n",
" run_bash(f\"rm -f {folder}/table.xvg\")\n",
" run_bash(f\"ln -s ../topol/table.xvg {folder}/table.xvg\")\n",
" \n",
" # .mpd files\n",
" run_bash(f\"cp {template_dir}/mdp/equi1.mdp equi1/grompp.mdp\")\n",
" run_bash(f\"cp {template_dir}/mdp/equi2.mdp equi2/grompp.mdp\")\n",
" run_bash(f\"cp {template_dir}/mdp/npt-equi3.mdp equi3/grompp.mdp\")\n",
" run_bash(f\"cp {template_dir}/mdp/npt-prod.mdp pre/grompp.mdp\")\n",
" run_bash(f\"cp {template_dir}/mdp/npt-equi3.mdp equi4/grompp.mdp\")\n",
" run_bash(f\"cp {template_dir}/mdp/npt-prod.mdp prod/grompp.mdp\")\n",
" # run length\n",
" gt.mdp.set_parameter(\"equi1/grompp.mdp\", 'nsteps', int(1e4))\n",
" gt.mdp.set_parameter(\"equi2/grompp.mdp\", 'nsteps', int(1e5))\n",
" gt.mdp.set_parameter(\"equi3/grompp.mdp\", 'nsteps', int(2e5))\n",
" gt.mdp.set_parameter(\"pre/grompp.mdp\", 'nsteps', int(5e5))\n",
" gt.mdp.set_parameter(\"equi4/grompp.mdp\", 'nsteps', int(1e6))\n",
" gt.mdp.set_parameter(\"prod/grompp.mdp\", 'nsteps', int(5e6))\n",
" # set temperature\n",
" gt.mdp.set_parameter(\"equi2/grompp.mdp\", 'gen-temp', system['temperature'])\n",
" for folder in all_folders[1:]:\n",
" gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'ref-t', system['temperature'])\n",
" # set pressure\n",
" for folder in all_folders[2:]:\n",
" gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'ref-p', '200.0 0.0')\n",
" gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'pcoupltype', 'semiisotropic')\n",
" gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'tau-p', '5')\n",
" gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'compressibility', '4.5e-5 0.0')\n",
" # set cutoff scheme\n",
" cutoff_scheme = 'group' if 'halftabulated' in system['tags'] else 'Verlet'\n",
" for folder in all_folders:\n",
" gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'cutoff-scheme', cutoff_scheme)\n",
" gt.mdp.set_parameter('equi1/grompp.mdp', 'cutoff-scheme', 'Verlet') # LJ for equi1\n",
" # set cutoffs\n",
" co = system['force-field']['cut-off']\n",
" for folder in all_folders:\n",
" for key in ('rlist', 'rcoulomb', 'rvdw'):\n",
" gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", key, co)\n",
" if 'tail-corr' in system['tags']:\n",
" for folder in all_folders:\n",
" mdp_file = folder + '/grompp.mdp'\n",
" gt.mdp.set_parameter(mdp_file, 'DispCorr', 'EnerPres')\n",
" # set vdwtype\n",
" vdwtype = 'User' if 'halftabulated' in system['tags'] else 'Cut-off'\n",
" for folder in all_folders:\n",
" gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'vdwtype', vdwtype)\n",
" gt.mdp.set_parameter(\"equi1/grompp.mdp\", 'vdwtype', 'Cut-off') # LJ for equi1\n",
" # set energygrps(-table)\n",
" pairs = tuple((pair for pair in system['force-field'].get('tabulated-potentials', [])\n",
" if pair[0] in system['atomtypes']\n",
" and pair[1] in system['atomtypes']))\n",
" energygrps = ' '.join(list(OrderedSet([pair[0] for pair in pairs]\n",
" +[pair[1] for pair in pairs])))\n",
" energygrp_table = ' '.join((f\"{pair[0]} {pair[1]}\" for pair in pairs))\n",
" for folder in all_folders:\n",
" gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'energygrps', energygrps)\n",
" gt.mdp.set_parameter(f\"{folder}/grompp.mdp\", 'energygrp-table', energygrp_table)\n",
" gt.mdp.set_parameter('equi1/grompp.mdp', 'energygrps', '') # LJ for equi1\n",
" gt.mdp.set_parameter('equi1/grompp.mdp', 'energygrp-table', '')\n",
"prepare_osmotic_pressure()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### elongate and fill box"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def fill_osmp_boxes():\n",
" # insert scale\n",
" scale = 0.57\n",
"\n",
" def N_from_rho_M_V(density, molar_mass, volume):\n",
" \"\"\"\n",
" density in g/mL\n",
" molar_mass in g/mol\n",
" volume in nm^3\n",
" \"\"\"\n",
" # m = V * ρ\n",
" mass = (volume / 1e21) * density # in g\n",
" # n = m / M\n",
" amount_of_substance = mass / molar_mass # in mol\n",
" return amount_of_substance * const.N_A # in entities\n",
"\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name, 'equi1')\n",
"\n",
" with WorkingDir(working_dir):\n",
" box_edge, _, _ = gt.gro.get_box('npt-confout.gro')\n",
" new_moltype = system['moltypes'][0]\n",
" new_name = new_moltype['name']\n",
" new_mass = gt.moltypes.get_mol_mass(system['moltypes'], 0, single_mol=True)\n",
" new_volume = box_edge**3 # total minus center slab\n",
" # density of water ~ 1 g/ml\n",
" new_nmols = int(N_from_rho_M_V(1.0, new_mass, new_volume))\n",
" print(new_volume, new_mass, new_nmols)\n",
" n_atoms_wanted = gt.moltypes.get_natoms(system['moltypes']) + new_nmols * len(new_moltype['atoms'])\n",
" new_box = (box_edge, box_edge, box_edge*2)\n",
"\n",
" # check for existing conf.gro\n",
" try:\n",
" n_atoms_existing = gt.gro.get_natoms(\"conf.gro\")\n",
" box_existing = gt.gro.get_box(\"conf.gro\")\n",
" except:\n",
" n_atoms_existing = 0\n",
" box_existing = [0, 0, 0]\n",
" if n_atoms_existing == n_atoms_wanted and np.allclose(box_existing, new_box):\n",
" print('..conf.gro with correct number of atoms and box existing..')\n",
" continue\n",
"\n",
" # if not the first, copy\n",
" if osmp_nr != 0:\n",
" run_bash(f\"cp ../../{list(osmp_methods.keys())[0]}/equi1/conf.gro conf.gro\")\n",
" continue\n",
"\n",
" # partially enlarge box\n",
" run_bash(f\"gmx editconf -f npt-confout.gro -box {new_box[0]} {new_box[1]} {new_box[2]-0.4} -o elongated.gro\")\n",
" # insert water molecules\n",
" run_bash(f\"gmx insert-molecules -f elongated.gro \"\n",
" f\"-ci single-{new_name}.gro -nmol {new_nmols} -o inserted.gro \"\n",
" f\"-try 300 -scale {scale}\")\n",
" # check if enoughp inserted\n",
" n_atoms_inserted = gt.gro.get_natoms(\"inserted.gro\")\n",
" if n_atoms_inserted != n_atoms_wanted:\n",
" print(n_atoms_inserted, n_atoms_wanted)\n",
" raise Exception(\"not enough molecules inserted\")\n",
" # fully enlarge box\n",
" run_bash(f\"gmx editconf -f inserted.gro -box {new_box[0]} {new_box[1]} {new_box[2]} -o conf.gro\")\n",
" run_bash(f\"rm -f elongated.gro inserted.gro\")\n",
" run_bash(\"rm -f \\#*\")\n",
"fill_osmp_boxes()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### create osmp_system"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def create_osmp_systems():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name, 'equi1')\n",
"\n",
" with WorkingDir(working_dir):\n",
" new_moltype = deepcopy(system['moltypes'][0])\n",
" new_natoms_total = gt.gro.get_natoms(\"conf.gro\")\n",
" old_natoms_total = gt.gro.get_natoms(\"npt-confout.gro\")\n",
" new_additional_mols = (new_natoms_total - old_natoms_total) // len(system['moltypes'][0]['atoms'])\n",
" new_moltype['nmols'] = new_additional_mols\n",
" # osmotic pressure system\n",
" osmp_system = deepcopy(system)\n",
" osmp_system['name'] += '/' + osmp_name\n",
" # moltypes with new mols\n",
" osmp_system['moltypes'].append(new_moltype)\n",
" # write to file\n",
" with open(\"../osmp_system.pkl\", 'wb') as f:\n",
" pickle.dump(osmp_system, f)\n",
"create_osmp_systems()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### prepare topol.top"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def prepare_osmp_topol():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" with open(\"osmp_system.pkl\", 'rb') as f:\n",
" osmp_system = pickle.load(f)\n",
"\n",
" box_edge, _, _ = gt.gro.get_box('equi1/npt-confout.gro')\n",
" restr_r = box_edge / 2\n",
" with open(\"osmp_system.pkl\", 'rb') as f:\n",
" osmp_system = pickle.load(f)\n",
" # topol.top\n",
" save_parametric_force_field_as_top('topol/topol.top', system['force-field'], system['name'], osmp_system['moltypes'],\n",
" osm_restraints={\n",
" osmp_system['moltypes'][1]['name']: {'ai': 1, 'funct': 2, 'g': 5, 'r': restr_r, 'k': osmp_method['k']},\n",
" osmp_system['moltypes'][2]['name']: {'ai': 1, 'funct': 2, 'g': 5, 'r': restr_r, 'k': osmp_method['k']}\n",
" })\n",
" if 'halftabulated' in system['tags']:\n",
" ff_no_tabulated = deepcopy(system['force-field'])\n",
" ff_no_tabulated['tabulated-potentials'] = []\n",
" save_parametric_force_field_as_top('equi1/topol.top', ff_no_tabulated, system['name'], osmp_system['moltypes'],\n",
" osm_restraints={\n",
" osmp_system['moltypes'][1]['name']: {'ai': 1, 'funct': 2, 'g': 5, 'r': restr_r, 'k': osmp_method['k']},\n",
" osmp_system['moltypes'][2]['name']: {'ai': 1, 'funct': 2, 'g': 5, 'r': restr_r, 'k': osmp_method['k']}\n",
" })\n",
"prepare_osmp_topol()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### prepare restraints.gro"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def prepare_osmp_restraint_gro():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" with open(\"osmp_system.pkl\", 'rb') as f:\n",
" osmp_system = pickle.load(f)\n",
"\n",
" box_edge, _, _ = gt.gro.get_box('equi1/npt-confout.gro')\n",
" restr_center = np.round(box_edge, 3) # precision of restraints.gro file\n",
" with open(\"osmp_system.pkl\", 'rb') as f:\n",
" osmp_system = pickle.load(f)\n",
" # generate restraints.gro\n",
" run_bash(f\"cp equi1/conf.gro topol/restraint.gro\")\n",
" top = gt.top.Topology()\n",
" top.load_simple_top(osmp_system['moltypes'])\n",
" # load gro file\n",
" top.load_gro_file_pos_vel(\"topol/restraint.gro\")\n",
" box = gt.gro.get_box(\"topol/restraint.gro\")\n",
" # modify coordinates\n",
" for atom in top.moltypes()[0].atoms() + top.moltypes()[-1].atoms():\n",
" atom.pos = np.zeros(3)\n",
" for atom in top.moltypes()[1].atoms() + top.moltypes()[2].atoms():\n",
" atom.pos = np.array([0.0, 0.0, restr_center])\n",
" # save gro file\n",
" top.save_gro_file(\"topol/restraint.gro\", box)\n",
"prepare_osmp_restraint_gro()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### prepare index.ndx"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def prepare_osmp_index():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" # generate simple index file\n",
" if 'halftabulated' in system['tags']:\n",
" with open(\"osmp_system.pkl\", 'rb') as f:\n",
" osmp_system = pickle.load(f)\n",
" top = gt.top.Topology()\n",
" top.load_simple_top(osmp_system['moltypes'])\n",
" gt.top.generate_index_file(top, 'index.ndx')\n",
"prepare_osmp_index()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## osmotic pressure pre-run"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### md, parallelized"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def osmp_pre_run():\n",
" remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'enzo')\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
"\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
" remote_dir = os.path.join(remote_dir_base, system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" # test local files\n",
" pre_done = os.path.isfile('pre/done')\n",
" if all((pre_done, )):\n",
" print('..results present locally..')\n",
" continue\n",
"\n",
" # mkdir\n",
" run_bash(f\"ssh {remote_host} mkdir -p {remote_dir}\")\n",
"\n",
" # copy simulation files to remote\n",
" if 'halftabulated' in system['tags']:\n",
" filelist = [\"equi1/conf.gro */grompp.mdp topol equi1/topol.top */table* index.ndx\"]\n",
" else:\n",
" filelist = [\"equi1/conf.gro */grompp.mdp topol\"]\n",
" gt.remote.push_files(filelist, remote_host, remote_dir, exclude=\"traj*\")\n",
"\n",
" index_string = \"\"\n",
" equi1_topol = \"../topol/topol.top\"\n",
" if 'halftabulated' in system['tags']:\n",
" index_string = \"-n ../index.ndx\"\n",
" equi1_topol = \"topol.top\"\n",
"\n",
" # commands to be run on compute nodes\n",
" script = remote_header + rf\"\"\"\n",
"pushd equi1\n",
" if [[ ! -f confout.gro ]]; then\n",
" gmx grompp -p {equi1_topol} -r ../topol/restraint.gro\n",
" gmx mdrun\n",
" fi\n",
" rm -f \\#*\n",
"popd\n",
"\n",
"pushd equi2\n",
" if [[ ! -f confout.gro ]]; then\n",
" gmx grompp {index_string} -maxwarn 1 -p ../topol/topol.top -r ../topol/restraint.gro -c ../equi1/confout.gro\n",
" gmx mdrun\n",
" fi\n",
" rm -f \\#*\n",
"popd\n",
"\n",
"pushd equi3\n",
" if [[ ! -f confout.gro ]]; then\n",
" gmx grompp {index_string} -p ../topol/topol.top -r ../topol/restraint.gro -maxwarn 2 -c ../equi2/confout.gro\n",
" gmx mdrun\n",
" fi\n",
" rm -f \\#*\n",
"popd\n",
"\n",
"\n",
"pushd pre\n",
" if [[ ! -f confout.gro ]]; then\n",
" gmx grompp {index_string} -p ../topol/topol.top -r ../topol/restraint.gro -maxwarn 2 -c ../equi3/confout.gro\n",
" gmx mdrun\n",
" fi\n",
" rm -f \\#*\n",
"popd\n",
"\n",
"touch pre/done\n",
"\"\"\" + remote_footer\n",
"\n",
" jobid = gt.remote.run_slurm_script(script, remote_host, remote_dir, dry_run=False)\n",
" print(jobid)\n",
" if jobid != None:\n",
" jobids.append(jobid)\n",
"osmp_pre_run()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### check job status"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"jobids = check_job_stati(jobids, remote_host)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### calc Pi, one core"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"!mkdir -p scripts\n",
"with open('scripts/calcPi.py', 'w') as f:\n",
" f.write(r\"\"\"#!/usr/bin/env python3\n",
" \n",
"import gromacstools as gt\n",
"import numpy as np\n",
"import subprocess\n",
"from scipy import constants as const\n",
"\n",
"# own constants\n",
"class oconst: pass\n",
"oconst.bar_per_md_pressure = 10**28 * const.u\n",
"\n",
"n_blocks = 5\n",
"\n",
"gt.general.run_bash(\"gmx energy -f ener.edr -o box-xy.xvg <<< 'Box-x\\nBox-y'\")\n",
"data, _ = gt.xvg.load('box-xy.xvg')\n",
"gt.general.run_bash(\"rm box-xy.xvg\")\n",
"data['A'] = data['Box-X'] * data['Box-Y']\n",
"A = data['A'].mean()\n",
"\n",
"data_low, header_low = gt.xvg.load(\"wallf-low.xvg\")\n",
"data_high, header_high = gt.xvg.load(\"wallf-high.xvg\")\n",
"f_low_raw = np.abs(data_low['y'].iloc[:-1])\n",
"f_high_raw = np.abs(data_high['y'].iloc[:-1])\n",
"len_block_low = len(f_low_raw) // n_blocks\n",
"len_block_high = len(f_high_raw) // n_blocks\n",
"f_low_blocks = []\n",
"f_high_blocks = []\n",
"for i in range(n_blocks):\n",
" f_low_blocks.append(np.mean(f_low_raw[len_block_low*i:len_block_low*(i+1)]))\n",
" f_high_blocks.append(np.mean(f_high_raw[len_block_high*i:len_block_high*(i+1)]))\n",
"f_low = np.mean(f_low_blocks)\n",
"f_high = np.mean(f_high_blocks)\n",
"f_low_err = np.std(f_low_blocks)\n",
"f_high_err = np.std(f_high_blocks)\n",
"Pi_low = f_low / A * oconst.bar_per_md_pressure\n",
"Pi_high = f_high / A * oconst.bar_per_md_pressure\n",
"Pi_low_err = f_low_err / A * oconst.bar_per_md_pressure\n",
"Pi_high_err = f_high_err / A * oconst.bar_per_md_pressure\n",
"np.savez_compressed('Pi.npz', Pi_low=Pi_low, Pi_low_err=Pi_low_err, Pi_high=Pi_high, Pi_high_err=Pi_high_err)\n",
"\"\"\")\n",
"!chmod a+x scripts/calcPi.py\n",
"gt.remote.push_files(['scripts/calcPi.py'], remote_host, remote_dir_base)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"!mkdir -p scripts\n",
"with open('scripts/calcPressure.py', 'w') as f:\n",
" f.write(r\"\"\"#!/usr/bin/env python3\n",
" \n",
"import gromacstools as gt\n",
"import numpy as np\n",
"import subprocess\n",
"from scipy import constants as const\n",
"\n",
"\n",
"gt.general.run_bash(\"gmx energy -f ener.edr -o pressure.xvg <<< 'Pres-XX\\nPres-YY'\")\n",
"data, _ = gt.xvg.load('pressure.xvg')\n",
"gt.general.run_bash(\"rm pressure.xvg\")\n",
"p_tot = 1/2 * (data['Pres-XX'].mean() + data['Pres-YY'].mean())\n",
"np.savez_compressed('p_tot.npz', p_tot=p_tot)\n",
"\"\"\")\n",
"!chmod a+x scripts/calcPressure.py\n",
"gt.remote.push_files(['scripts/calcPressure.py'], remote_host, remote_dir_base)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"!mkdir -p scripts\n",
"with open('scripts/calcX.py', 'w') as f:\n",
" f.write(r\"\"\"#!/usr/bin/env python3\n",
" \n",
"import gromacstools as gt\n",
"import numpy as np\n",
"import subprocess\n",
"from scipy import constants as const\n",
"\n",
"# own constants\n",
"class oconst: pass\n",
"oconst.bar_per_md_pressure = 10**28 * const.u\n",
"\n",
"gt.general.run_bash(\"gmx energy -f ener.edr -o box-xy.xvg <<< 'Box-x\\nBox-y'\")\n",
"data, _ = gt.xvg.load('box-xy.xvg')\n",
"gt.general.run_bash(\"rm box-xy.xvg\")\n",
"data['A'] = data['Box-X'] * data['Box-Y']\n",
"A = data['A'].mean()\n",
"del data\n",
"\n",
"_, _, box_edge = gt.gro.get_box('confout.gro')\n",
"restr_r = box_edge / 4\n",
"restr_center = np.round(box_edge / 2, 3)\n",
"wall_low = restr_center - restr_r\n",
"wall_high = restr_center + restr_r\n",
"inner_wall_low = restr_center - 0.5*restr_r\n",
"inner_wall_high = restr_center + 0.5*restr_r\n",
"volume_full = A * (wall_high - wall_low)\n",
"volume_inner = A * (inner_wall_high - inner_wall_low)\n",
"\n",
"x = {}\n",
"x_err = {}\n",
"c = {}\n",
"c_err = {}\n",
"for region in ('full', 'inner'):\n",
" filename = {'full': \"size.xvg\", 'inner': \"size-inner.xvg\"}[region]\n",
" volume = {'full': volume_full, 'inner': volume_inner}[region]\n",
" data, header = gt.xvg.load(filename)\n",
" if data.columns[1].startswith('atomname CA'):\n",
" data.columns = ['t', 'CA', 'CL', 'SOL']\n",
" data['x'] = 1/2 * (data['CA'] + 1/2*data['CL']) / (1/2 * (data['CA'] + 1/2*data['CL']) + data['SOL'])\n",
" data['c'] = 1/2 * (data['CA'] + 1/2*data['CL']) / volume\n",
" elif data.columns[1].startswith(tuple((f'atomname {ion}' for ion in ['NA', 'K', 'LI']))):\n",
" data.columns = ['t', 'CATION', 'CL', 'SOL']\n",
" data['x'] = 1/2 * (data['CATION'] + data['CL']) / (1/2 * (data['CATION'] + data['CL']) + data['SOL'])\n",
" data['c'] = 1/2 * (data['CATION'] + data['CL']) / volume\n",
" else:\n",
" raise Exception('Unknown system!')\n",
" x[region] = data['x'].mean()\n",
" x_err[region] = data['x'].std()\n",
" c[region] = data['c'].mean()\n",
" c[region] *= 1 / const.N_A / 1e-24 # now mol/L\n",
" c_err[region] = data['c'].std()\n",
" c_err[region] *= 1 / const.N_A / 1e-24 # now mol/L\n",
" \n",
"np.savez_compressed('x.npz',\n",
"x=x['full'],\n",
"x_err=x_err['full'],\n",
"x_inner=x['inner'],\n",
"x_inner_err=x_err['inner'],\n",
"c=c['full'],\n",
"c_err=c_err['full'],\n",
"c_inner=c['inner'],\n",
"c_inner_err=c_err['inner'],\n",
")\n",
"\"\"\")\n",
"!chmod a+x scripts/calcX.py\n",
"gt.remote.push_files(['scripts/calcX.py'], remote_host, remote_dir_base)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def osmp_pre_Pi():\n",
" remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'enzo')\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
"\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
" remote_dir = os.path.join(remote_dir_base, system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" # test local files\n",
" Pi_done = os.path.isfile('pre/Pi.npz')\n",
" p_tot_done = os.path.isfile('pre/p_tot.npz')\n",
" x_done = os.path.isfile('pre/x.npz')\n",
" if all((Pi_done, p_tot_done, x_done)):\n",
" print('..results present locally..')\n",
" continue\n",
"\n",
" # wallforce parameters\n",
" box_edge, _, _ = gt.gro.get_box('equi1/npt-confout.gro')\n",
" restr_r = box_edge / 2\n",
" restr_center = np.round(box_edge, 3) # precision of restraints.gro file\n",
" wall_low = restr_center - restr_r\n",
" wall_high = restr_center + restr_r\n",
" restr_k = osmp_method['k']\n",
" inner_wall_low = restr_center - 0.5*restr_r\n",
" inner_wall_high = restr_center + 0.5*restr_r\n",
" cation = system['moltypes'][1]['name']\n",
" anion = system['moltypes'][2]['name']\n",
"\n",
" # commands to be run on compute nodes\n",
" script = remote_header + rf\"\"\"\n",
"pushd pre\n",
" if [[ ! -f wallf-low.xvg ]]; then\n",
" $HOME/bin/wallforce -quiet -f traj_comp.xtc -s topol.tpr -axis zn -wallr {wall_low} -wallk {restr_k} -o wallf-low.xvg <<< \"name {cation} or name {anion}\"\n",
" fi\n",
" if [[ ! -f wallf-high.xvg ]]; then\n",
" $HOME/bin/wallforce -quiet -f traj_comp.xtc -s topol.tpr -axis z -wallr {wall_high} -wallk {restr_k} -o wallf-high.xvg <<< \"name {cation} or name {anion}\"\n",
" fi\n",
" rm -f \\#*\n",
"popd\n",
"\n",
"pushd pre\n",
" if [[ ! -f size.xvg ]]; then\n",
" echo -e 'atomname {cation} and z > {wall_low} and z < {wall_high}\\n'\\\n",
" 'atomname {anion} and z > {wall_low} and z < {wall_high}\\n'\\\n",
" 'res_com of resname SOL and z > {wall_low} and z < {wall_high}'\\\n",
" | gmx select -f traj_comp.xtc -s topol.tpr -os size.xvg\n",
" fi\n",
" rm -f \\#*\n",
"popd\n",
"\n",
"pushd pre\n",
" if [[ ! -f size-inner.xvg ]]; then\n",
" echo -e 'atomname {cation} and z > {inner_wall_low} and z < {inner_wall_high}\\n'\\\n",
" 'atomname {anion} and z > {inner_wall_low} and z < {inner_wall_high}\\n'\\\n",
" 'res_com of resname SOL and z > {inner_wall_low} and z < {inner_wall_high}'\\\n",
" | gmx select -f traj_comp.xtc -s topol.tpr -os size-inner.xvg\n",
" fi\n",
" rm -f \\#*\n",
"popd\n",
"\n",
"pushd pre\n",
" rm -f traj_comp.xtc\n",
"popd\n",
"\n",
"pushd equi3\n",
" if [[ ! -f ener-coarse.edr ]]; then\n",
" gmx eneconv -f ener.edr -o ener-coarse.edr -dt 20\n",
" fi\n",
"popd\n",
"\n",
"pushd pre\n",
" if [[ ! -f Pi.npz ]]; then\n",
" ../../../../scripts/calcPi.py\n",
" fi\n",
"popd\n",
"\n",
"pushd pre\n",
" if [[ ! -f p_tot.npz ]]; then\n",
" ../../../../scripts/calcPressure.py\n",
" fi\n",
"popd\n",
"\n",
"pushd pre\n",
" if [[ ! -f x.npz ]]; then\n",
" ../../../../scripts/calcX.py\n",
" fi\n",
"popd\n",
"\"\"\" + remote_footer\n",
"\n",
" jobid = gt.remote.run_slurm_script(script, remote_host, remote_dir, dry_run=False)\n",
" print(jobid)\n",
" if jobid != None:\n",
" jobids.append(jobid)\n",
"osmp_pre_Pi()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### check job status"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"jobids = check_job_stati(jobids, remote_host)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### copy results from cluster"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def copy_from_cluster():\n",
" remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'enzo')\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
" remote_dir = os.path.join(remote_dir_base, system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" exclude = \"traj*\"\n",
" filelist = [\"*/ener-coarse.edr\", \"pre/Pi.npz\", \"pre/p_tot.npz\", \"pre/x.npz\", \"pre/done\"]\n",
" try:\n",
" gt.remote.pull_files(filelist, remote_host, remote_dir, exclude=exclude)\n",
" except:\n",
" print('.. pulling failed ..')\n",
" \n",
"copy_from_cluster()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### equilibration check"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def equi_check():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" try:\n",
" check_equi([\"Volume\"], edr_file=\"equi3/ener-coarse.edr\", safe_factor=3.0)\n",
" except:\n",
" print('..no data..')\n",
"\n",
"equi_check()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Pi in data frame"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"# f: force\n",
"# A: area\n",
"# Pi: osmotic pressure\n",
"# high, low: respective walls\n",
" \n",
"def load_Pi_pre():\n",
" n_blocks = 5\n",
"\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" data = np.load('pre/Pi.npz')\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'Pi_low'] = data['Pi_low']\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'Pi_high'] = data['Pi_high']\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'Pi_low_err'] = data['Pi_low_err']\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'Pi_high_err'] = data['Pi_high_err']\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'Pi'] = 1/2 * (data['Pi_low'] + data['Pi_high'])\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'Pi_err'] = 1/2 * np.sqrt(data['Pi_low_err']**2 + data['Pi_high_err']**2)\n",
"\n",
"load_Pi_pre()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"df_osmp.loc[(slice(None), slice(None), 'pre'), :]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### get p_tot (Pres-XX, Pres-YY)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def get_p_tot():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" data = np.load('pre/p_tot.npz')\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'p_tot'] = data['p_tot']\n",
"get_p_tot()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_osmp.tail()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### calc p_in and p_ex"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def calc_p_in_ex():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" p_tot = df_osmp.at[(system['name'], osmp_name, 'pre'), 'p_tot']\n",
" Pi_low = df_osmp.at[(system['name'], osmp_name, 'pre'), 'Pi_low']\n",
" Pi_high = df_osmp.at[(system['name'], osmp_name, 'pre'), 'Pi_high'] \n",
" Pi = (Pi_low + Pi_high) / 2\n",
" # p_tot = (p_in + p_ex) / 2\n",
" # Pi = p_in - p_ex\n",
" p_ex = p_tot - Pi / 2\n",
" p_in = p_ex + Pi\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'p_ex'] = p_ex\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'p_in'] = p_in\n",
" \n",
"calc_p_in_ex()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### actual mole fraction"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def calc_x():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" data = np.load('pre/x.npz')\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'x'] = data['x']\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'x_err'] = data['x_err']\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'x_inner'] = data['x_inner']\n",
" df_osmp.at[(system['name'], osmp_name, 'pre'), 'x_inner_err'] = data['x_inner_err']\n",
" \n",
"calc_x()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def plot_Pi_pre():\n",
" cmap = plt.get_cmap('rainbow')\n",
"\n",
" # Literature data: CRC handbook of chem and phys (25°C)\n",
" lit_molality = np.arange(0.1, 1.1, 0.1) # mol / kg (?)\n",
" lit_activity_coeff = np.array([0.778, 0.735, 0.710, 0.693, 0.681, 0.673, 0.667, 0.662, 0.659, 0.657])\n",
" M0 = 18.0154 # g / mol\n",
" lit_mole_fraction = 1 / (1 + 1/(M0 / 1000*lit_molality))\n",
" V_M = 18.0685 # cm^3 (of water 25°)\n",
" lit_osmotic_pressure = - const.R * 300 / V_M * np.log(lit_activity_coeff * lit_molality)\n",
" lit_x = lit_mole_fraction\n",
" lit_y = lit_osmotic_pressure\n",
"\n",
"\n",
" for stn, st in system_types_osmp.items():\n",
" print(stn)\n",
" fig, ax = plt.subplots(figsize=(6, 3), constrained_layout=True)\n",
" for f, (ffn, ff) in enumerate(force_fields_osmp.items()):\n",
" print(ffn)\n",
" system_names = tuple((sys['name'] for sys in system_generator(*systems_osmp)\n",
" if ffn == sys['force-field']['name']\n",
" if stn == sys['type']['name']))\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" #print(' osmp_name', osmp_name)\n",
"\n",
" rows = (system_names, osmp_name, 'pre')\n",
" x = df_osmp.loc[(rows, 'x_inner')]\n",
" xerr = df_osmp.loc[(rows, 'x_inner_err')]\n",
" y = df_osmp.loc[(rows, 'Pi')]\n",
" yerr = df_osmp.loc[(rows, 'Pi_err')]\n",
" ax.errorbar(x=x, y=y, xerr=xerr, yerr=yerr,\n",
" color=cmap(f/6), label=ff_short_names[ffn], linestyle=':')\n",
"\n",
" ax.legend()\n",
" ax.set_xlim(0)\n",
" ax.set_ylim(0)\n",
" ax.set_title(sys_type_short_names[stn])\n",
" plt.show()\n",
"\n",
"plot_Pi_pre()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## production run"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### set production pressure"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\\begin{align}\n",
"p_{tot} &= p_{in} + p_{ex}\\\\\n",
"\\Pi &= p_{in} - p_{ex}\\\\\n",
"p_{tot} &= 2 * p_{ex} + \\Pi\\\\\n",
"p_{ex} &= 1 bar\\\\\n",
"%p_{ex} &= 1/2 * (p_{tot} - \\Pi)\\\\\n",
"\\end{align}\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\\begin{align}\n",
"p_{tot} &= (p_{in} + p_{ex}) / 2 \\\\\n",
"\\Pi &= p_{in} - p_{ex}\\\\\n",
"p_{tot} &= p_{ex} + \\Pi / 2\\\\\n",
"p_{ex} &= 1 bar\\\\\n",
"%p_{ex} &= 1/2 * (p_{tot} - \\Pi)\\\\\n",
"\\end{align}\n",
" "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def set_p():\n",
" for system in system_generator(*systems_osmp):\n",
" print(\"system\", system['name'])\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
"\n",
" with WorkingDir(system['name'] + \"/\" + osmp_name):\n",
" row = (system['name'], osmp_name, 'pre')\n",
" p_ex = 1.0\n",
" Pi = 1/2 * (df_osmp.at[row, 'Pi_low'] + df_osmp.at[row, 'Pi_high'])\n",
" p_tot = p_ex + Pi/2\n",
" print(p_tot)\n",
" print(2 * p_ex + Pi)\n",
" if osmp_method['scale'] == 'xy':\n",
" gt.mdp.set_parameter(\"equi4/grompp.mdp\", 'ref-p', str(p_tot) + \" 0.0\")\n",
" gt.mdp.set_parameter(\"prod/grompp.mdp\", 'ref-p', str(p_tot) + \" 0.0\")\n",
" else:\n",
" raise Exception('not implemented')\n",
" \n",
"set_p()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### md, parallelized"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def run_osmp_md_prod():\n",
" remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'enzo')\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
"\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
" remote_dir = os.path.join(remote_dir_base, system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" # test local files\n",
" prod_done = os.path.isfile('prod/done')\n",
" if all((prod_done, )):\n",
" print('..results present locally..')\n",
" continue\n",
"\n",
" # wallforce parameters\n",
" box_edge, _, _ = gt.gro.get_box('equi1/npt-confout.gro')\n",
" restr_r = box_edge / 2\n",
" restr_center = np.round(box_edge, 3) # precision of restraints.gro file\n",
" wall_low = restr_center - restr_r\n",
" wall_high = restr_center + restr_r\n",
" restr_k = osmp_method['k']\n",
" inner_wall_low = restr_center - 0.5*restr_r\n",
" inner_wall_high = restr_center + 0.5*restr_r\n",
" cation = system['moltypes'][1]['name']\n",
" anion = system['moltypes'][2]['name']\n",
"\n",
" # mkdir\n",
" run_bash(f\"ssh {remote_host} mkdir -p {remote_dir}\")\n",
"\n",
" # copy simulation files to remote\n",
" if 'tabulated' in system['tags']:\n",
" filelist = [\"{equi4,prod}/grompp.mdp topol {equi4,prod}/table* index.ndx\"]\n",
" else:\n",
" filelist = [\"{equi4,prod}/grompp.mdp topol\"]\n",
" gt.remote.push_files(filelist, remote_host, remote_dir, exclude=\"traj*\")\n",
"\n",
" index_string = \"\"\n",
" if 'halftabulated' in system['tags']:\n",
" index_string = \"-n ../index.ndx\"\n",
"\n",
" # commands to be run on compute nodes\n",
" script = remote_header + rf\"\"\"\n",
" pushd equi4\n",
" if [[ ! -f confout.gro ]]; then\n",
" gmx grompp {index_string} -p ../topol/topol.top -r ../topol/restraint.gro -maxwarn 2 -c ../pre/confout.gro\n",
" gmx mdrun\n",
" fi\n",
" rm -f \\#*\n",
" popd\n",
"\n",
"\n",
" pushd prod\n",
" if [[ ( ! -f confout.gro ) && -f state.cpt ]]; then\n",
" gmx mdrun -cpi state\n",
" elif [[ ( ! -f confout.gro ) && ( ! -f state.cpt) ]]; then\n",
" gmx grompp {index_string} -p ../topol/topol.top -r ../topol/restraint.gro -maxwarn 2 -c ../equi4/confout.gro\n",
" gmx mdrun\n",
" elif [[ -f confout.gro ]]; then\n",
" echo \"md done\"\n",
" else\n",
" echo \"Weird state. This should never happen.\"\n",
" exit 1\n",
" fi\n",
" rm -f \\#*\n",
" popd\n",
" \n",
" touch prod/done\n",
" \"\"\" + remote_footer\n",
"\n",
" jobid = gt.remote.run_slurm_script(script, remote_host, remote_dir, dry_run=False)\n",
" print(jobid)\n",
" if jobid != None:\n",
" jobids.append(jobid)\n",
" \n",
"run_osmp_md_prod()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### check job status"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"jobids = check_job_stati(jobids, remote_host)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### calc Pi, one core"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def osmp_prod_run_Pi():\n",
" remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'enzo')\n",
" for system in system_generator(*systems_osmp):\n",
" if system['name'] != 'water5000-cacl2_200/madrid-co1.0tc':\n",
" continue\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
"\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
" remote_dir = os.path.join(remote_dir_base, system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" # test local files\n",
" Pi_done = os.path.isfile('prod/Pi.npz')\n",
" p_tot_done = os.path.isfile('prod/p_tot.npz')\n",
" x_done = os.path.isfile('prod/x.npz')\n",
" if all((Pi_done, p_tot_done, x_done)):\n",
" print('..results present locally..')\n",
" #continue\n",
"\n",
" # wallforce parameters\n",
" box_edge, _, _ = gt.gro.get_box('equi1/npt-confout.gro')\n",
" restr_r = box_edge / 2\n",
" restr_center = np.round(box_edge, 3) # precision of restraints.gro file\n",
" wall_low = restr_center - restr_r\n",
" wall_high = restr_center + restr_r\n",
" restr_k = osmp_method['k']\n",
" inner_wall_low = restr_center - 0.5*restr_r\n",
" inner_wall_high = restr_center + 0.5*restr_r\n",
" cation = system['moltypes'][1]['name']\n",
" anion = system['moltypes'][2]['name']\n",
"\n",
" # commands to be run on compute nodes\n",
" script = remote_header + rf\"\"\"\n",
"source /home/mbernhardt/software/miniconda3/etc/profile.d/conda.sh\n",
"conda activate base\n",
"export PYTHONPATH=$PYTHONPATH:/home/mbernhardt/software/gromacstools\n",
"\n",
"pushd prod\n",
" if [[ ! -f wallf-low.xvg ]]; then\n",
" $HOME/bin/wallforce -quiet -f traj_comp.xtc -s topol.tpr -axis zn -wallr {wall_low} -wallk {restr_k} -o wallf-low.xvg <<< \"name {cation} or name {anion}\"\n",
" fi\n",
" if [[ ! -f wallf-high.xvg ]]; then\n",
" $HOME/bin/wallforce -quiet -f traj_comp.xtc -s topol.tpr -axis z -wallr {wall_high} -wallk {restr_k} -o wallf-high.xvg <<< \"name {cation} or name {anion}\"\n",
" fi\n",
" rm -f \\#*\n",
"popd\n",
"\n",
"pushd prod\n",
" if [[ ! -f size.xvg ]]; then\n",
" echo -e 'atomname {cation} and z > {wall_low} and z < {wall_high}\\n'\\\n",
" 'atomname {anion} and z > {wall_low} and z < {wall_high}\\n'\\\n",
" 'res_com of resname SOL and z > {wall_low} and z < {wall_high}'\\\n",
" | gmx select -f traj_comp.xtc -s topol.tpr -os size.xvg\n",
" fi\n",
" rm -f \\#*\n",
"popd\n",
"\n",
"pushd prod\n",
" if [[ ! -f size-inner.xvg ]]; then\n",
" echo -e 'atomname {cation} and z > {inner_wall_low} and z < {inner_wall_high}\\n'\\\n",
" 'atomname {anion} and z > {inner_wall_low} and z < {inner_wall_high}\\n'\\\n",
" 'res_com of resname SOL and z > {inner_wall_low} and z < {inner_wall_high}'\\\n",
" | gmx select -f traj_comp.xtc -s topol.tpr -os size-inner.xvg\n",
" fi\n",
" rm -f \\#*\n",
"popd\n",
"\n",
"pushd prod\n",
" rm -f traj_comp.xtc\n",
"popd\n",
"\n",
"pushd equi4\n",
" if [[ ! -f ener-coarse.edr ]]; then\n",
" gmx eneconv -f ener.edr -o ener-coarse.edr -dt 20\n",
" fi\n",
"popd\n",
"\n",
"pushd prod\n",
" if [[ ! -f Pi.npz ]]; then\n",
" ../../../../scripts/calcPi.py\n",
" fi\n",
"popd\n",
"\n",
"pushd prod\n",
" if [[ ! -f p_tot.npz ]]; then\n",
" ../../../../scripts/calcPressure.py\n",
" fi\n",
"popd\n",
"\n",
"pushd prod\n",
" if [[ ! -f x.npz ]]; then\n",
" ../../../../scripts/calcX.py\n",
" fi\n",
"popd\n",
"\"\"\" + remote_footer\n",
"\n",
" jobid = gt.remote.run_slurm_script(script, remote_host, remote_dir, dry_run=True)\n",
" print(jobid)\n",
" if jobid != None:\n",
" jobids.append(jobid)\n",
"osmp_prod_run_Pi()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### check job status"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"jobids = check_job_stati(jobids, remote_host)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### copy results from cluster"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def copy_from_cluster_prod():\n",
" remote_dir_base, remote_header, remote_footer = gen_remote_stuff(remote_host, 'enzo')\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
" remote_dir = os.path.join(remote_dir_base, system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" exclude = \"traj*\"\n",
" filelist = [\"equi4/ener-coarse.edr\", \"prod/Pi.npz\", \"prod/p_tot.npz\", \"prod/x.npz\", \"prod/done\"]\n",
" gt.remote.pull_files(filelist, remote_host, remote_dir, exclude=exclude)\n",
"\n",
"copy_from_cluster_prod()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### equilibration check"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def equi_check():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" try:\n",
" check_equi([\"Volume\"], edr_file=\"equi4/ener-coarse.edr\", safe_factor=3.0)\n",
" except:\n",
" pass\n",
" \n",
"equi_check()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def show_en():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" print(\"equilibration 4\")\n",
" show_energy_graphs([\"Temperature\", \"Volume\"], edr_file=\"equi4/ener-coarse.edr\")\n",
" #print(\"production\")\n",
" #show_energy_graphs([\"Temperature\", \"Volume\"], edr_file=\"pre/ener.edr\")\n",
" \n",
"show_en()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Pi in data frame"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"# f: force\n",
"# A: area\n",
"# Pi: osmotic pressure\n",
"# high, low: respective walls\n",
"\n",
"def load_Pi(): \n",
" n_blocks = 5\n",
"\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" try:\n",
" data = np.load('prod/Pi.npz')\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'Pi_low'] = data['Pi_low']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'Pi_high'] = data['Pi_high']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'Pi_low_err'] = data['Pi_low_err']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'Pi_high_err'] = data['Pi_high_err']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'Pi'] = np.nanmean((data['Pi_low'], data['Pi_high']))\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'Pi_err'] = 1/2 * np.sqrt(np.nansum((data['Pi_low_err']**2, data['Pi_high_err']**2)))\n",
" except FileNotFoundError:\n",
" print(\".. no data ..\")\n",
" df_osmp.loc[(system['name'], osmp_name, 'prod'), ('Pi_low', 'Pi_high', 'Pi_low_err', 'Pi_high_err', 'Pi', 'Pi_err')] = np.nan\n",
"load_Pi()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### get p_tot (Pres-XX, Pres-YY)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def get_p_tot():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" try:\n",
" p_tot = np.load('prod/p_tot.npz')['p_tot']\n",
" except FileNotFoundError:\n",
" print(\".. no data ..\")\n",
" p_tot = np.nan\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'p_tot'] = p_tot\n",
"\n",
"get_p_tot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### calc p_in and p_ex"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def calc_p_in_ex():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" p_tot = df_osmp.at[(system['name'], osmp_name, 'prod'), 'p_tot']\n",
" Pi_low = df_osmp.at[(system['name'], osmp_name, 'prod'), 'Pi_low']\n",
" Pi_high = df_osmp.at[(system['name'], osmp_name, 'prod'), 'Pi_high'] \n",
" #Pi = (Pi_low + Pi_high) / 2\n",
" Pi = np.nanmean([Pi_low, Pi_high])\n",
" # p_tot = (p_in + p_ex) / 2\n",
" # Pi = p_in - p_ex\n",
" p_ex = 1/2 * (p_tot - Pi)\n",
" p_in = p_ex + Pi\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'p_ex'] = p_ex\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'p_in'] = p_in\n",
"\n",
"calc_p_in_ex()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_osmp.tail()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_osmp.loc['water5000-cacl2_200/madrid-co1.0tc', 'osmp-xy-k4000', 'prod']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### actual mole fraction and concentration"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def calc_x():\n",
" for system in system_generator(*systems_osmp):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" print(' osmp_name', osmp_name)\n",
" working_dir = os.path.join(system['name'], osmp_name)\n",
"\n",
" with WorkingDir(working_dir):\n",
" try:\n",
" data = np.load('prod/x.npz')\n",
" except FileNotFoundError:\n",
" print('.. no data ..')\n",
" df_osmp.loc[(system['name'], osmp_name, 'prod'), ('x', 'x_err', 'x_inner', 'x_inner_err')] = data['x']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'x'] = data['x']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'x_err'] = data['x_err']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'x_inner'] = data['x_inner']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'x_inner_err'] = data['x_inner_err']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'c'] = data['c']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'c_err'] = data['c_err']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'c_inner'] = data['c_inner']\n",
" df_osmp.at[(system['name'], osmp_name, 'prod'), 'c_inner_err'] = data['c_inner_err']\n",
"\n",
"calc_x()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## save and load dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_osmp.to_pickle('df_osmp.pkl')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_osmp = pd.read_pickle('df_osmp.pkl')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"df_osmp.loc[(slice(None), slice(None), 'prod'), slice(None)]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"df_osmp_prod = df_osmp.loc[(slice(None), slice(None), 'prod'), slice(None)]\n",
"df_osmp_prod[df_osmp_prod.isna().any(axis=1)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## plot"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### plot simple"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"def plot_Pi_prod(show_pre=False):\n",
" cmap = plt.get_cmap('rainbow')\n",
"\n",
" # Literature data: CRC handbook of chem and phys (25°C)\n",
" lit_molality = np.arange(0.1, 1.1, 0.1) # mol / kg (?)\n",
" lit_activity_coeff = np.array([0.778, 0.735, 0.710, 0.693, 0.681, 0.673, 0.667, 0.662, 0.659, 0.657])\n",
" M0 = 18.0154 # g / mol\n",
" lit_mole_fraction = 1 / (1 + 1/(M0 / 1000*lit_molality))\n",
" V_M = 18.0685 # cm^3 (of water 25°)\n",
" lit_osmotic_pressure = - const.R * 300 / V_M * np.log(lit_activity_coeff * lit_molality)\n",
" lit_x = lit_mole_fraction\n",
" lit_y = lit_osmotic_pressure\n",
"\n",
"\n",
" for stn, st in system_types_osmp.items():\n",
" print(stn)\n",
" fig, ax = plt.subplots(figsize=(6, 3), constrained_layout=True, dpi=300)\n",
" for f, (ffn, ff) in enumerate(force_fields_osmp.items()):\n",
" print(ffn)\n",
" system_names = tuple((sys['name'] for sys in system_generator(*systems_osmp)\n",
" if ffn == sys['force-field']['name']\n",
" if stn == sys['type']['name']))\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" #print(' osmp_name', osmp_name)\n",
"\n",
" rows = (system_names, osmp_name, 'prod')\n",
" x = df_osmp.loc[(rows, 'x_inner')]\n",
" xerr = df_osmp.loc[(rows, 'x_inner_err')]\n",
" y = df_osmp.loc[(rows, 'Pi')]\n",
" yerr = df_osmp.loc[(rows, 'Pi_err')]\n",
" ax.errorbar(x=x, y=y, xerr=xerr, yerr=yerr,\n",
" color=cmap(f/6), label=ff_short_names[ffn], linestyle='--')\n",
" if show_pre:\n",
" rows = (system_names, osmp_name, 'pre')\n",
" x = df_osmp.loc[(rows, 'x_inner')]\n",
" xerr = df_osmp.loc[(rows, 'x_inner_err')]\n",
" y = df_osmp.loc[(rows, 'Pi')]\n",
" yerr = df_osmp.loc[(rows, 'Pi_err')]\n",
" ax.errorbar(x=x, y=y, xerr=xerr, yerr=yerr,\n",
" color=cmap(f/6), label=None, linestyle=':')\n",
"\n",
" ax.legend()\n",
" ax.set_xlim(0)\n",
" ax.set_ylim(0)\n",
" ax.set_title(sys_type_short_names[stn])\n",
" ax.grid()\n",
" plt.show()\n",
"\n",
"plot_Pi_prod(show_pre=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_osmp.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def plot_coeff():\n",
" ylim_dict = {\n",
" 'water-cacl2_': (0.0, 4.2),\n",
" 'water-kcl': (0.0, 1.5),\n",
" 'water-licl': (0.0, 1.95),\n",
" 'water-nacl': (0.0, 1.65),\n",
" }\n",
"\n",
" mpl_rc = {\n",
" 'legend.labelspacing': 0.5,\n",
" 'legend.columnspacing': 1.5,\n",
" 'legend.handlelength': 3.0,\n",
" }\n",
" ff_to_show = {\n",
" 'opls-co0.9tc',\n",
" 'eccr1-co1.2',\n",
" 'netz-co0.9tc',\n",
" 'madrid-co1.0tc',\n",
" #'netz-co0.9',\n",
" 'iff-altern5-eccr1-co1.2-nopc',\n",
" 'iff-altern5-netz-co0.9-nopc',\n",
" 'Buckingham-iff-altern5-eccr1-co1.2-nopc',\n",
" }\n",
"\n",
" with plt.rc_context({**mpl_rc_global, **mpl_rc}):\n",
" fig, axes = plt.subplots(figsize=(4.67, 2.5), nrows=2, ncols=2, constrained_layout=True, sharex='all', dpi=200)\n",
" #fig, axes = plt.subplots(figsize=(10.67, 5.5), nrows=2, ncols=2, constrained_layout=True, sharex='all', dpi=200)\n",
" fig.set_constrained_layout_pads(w_pad=0.05, h_pad=0.00, hspace=0.0, wspace=0.0)\n",
" for s, (stn, st) in enumerate(system_types_osmp.items()):\n",
" print(s, stn)\n",
" ax = axes.flat[s]\n",
" n_ions = sum(st['n_cation_anion'])\n",
" for f, (ffn, ff) in enumerate(force_fields_osmp.items()):\n",
" #print(f\" force field {ffn}\")\n",
" system_names = tuple((sys['name'] for sys in system_generator(*systems_osmp)\n",
" if ffn == sys['force-field']['name']\n",
" if sys['type']['name'] == stn))\n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" #print(' osmp_name', osmp_name)\n",
"\n",
" rows = (system_names, osmp_name, 'prod')\n",
" x = df_osmp.loc[(rows, 'c_inner')]\n",
" xerr = df_osmp.loc[(rows, 'c_inner_err')]\n",
" print(n_ions)\n",
" vant_Hoff_Pi = vant_Hoff_osmotic_pressure(x, n_ions, 300)\n",
" y = df_osmp.loc[(rows, 'Pi')] / vant_Hoff_Pi\n",
" yerr = df_osmp.loc[(rows, 'Pi_err')] / vant_Hoff_Pi\n",
" if len(x) > 0 and ffn in ff_to_show:\n",
" ax.errorbar(x=x, y=y, xerr=xerr, yerr=yerr, color=ff_colors[ffn],\n",
" label=ff_short_names[ffn], linestyle=':', marker='.')\n",
" #ax.grid()\n",
" # lit data\n",
" lit_x, lit_y = get_osmotic_coeff_lit(osmp_lit_dict, stn, 'Guendouzi 2001')\n",
" ax.plot(lit_x, lit_y, marker='.', linestyle='-', color='k', label='exp.', zorder=0, markersize=4)\n",
" \n",
" vant_Hoff_x = np.linspace(0, 5, 10)\n",
" #vant_Hoff_y = vant_Hoff_osmotic_pressure(vant_Hoff_x, n_inos, 300)\n",
" vant_Hoff_y = np.ones_like(vant_Hoff_x)\n",
" #ax.plot(vant_Hoff_x, vant_Hoff_y, marker='', linestyle='-', color='grey', label=\"van't Hoff\")\n",
" ax.axhline(1, marker='', linestyle='-', color='grey', linewidth=0.5) # label=\"van't Hoff\")\n",
" ax.set_xlim(0, 5.5)\n",
" ax.set_ylim(ylim_dict[stn])\n",
" ax.text(.05, .83, sys_type_short_names[stn],\n",
" horizontalalignment='left', transform=ax.transAxes)\n",
"\n",
" axes[0, 0].set_ylabel(r\"$\\phi$\")\n",
" axes[1, 0].set_ylabel(r\"$\\phi$\")\n",
" axes[1, 0].set_xlabel(r\"$c$ in mol/l\")\n",
" axes[1, 1].set_xlabel(r\"$c$ in mol/l\")\n",
"\n",
" handles, labels = ax.get_legend_handles_labels()\n",
" #labels, handles = zip(*sorted(zip(labels, handles), key=lambda t: t[0], reverse=False))\n",
" order = [1, 4, 2, 3, 5, 6, 0]\n",
" handles, labels = [handles[idx] for idx in order], [labels[idx] for idx in order]\n",
" fig.legend(handles, labels, ncol=4, loc='lower center', bbox_to_anchor=(0.54, 0.97),)\n",
" fig.savefig('../figures/osmotic-coefficients.pdf', bbox_inches='tight')\n",
" plt.show()\n",
" \n",
"plot_coeff()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!cp -a ../figures/osmotic-coefficients.pdf ~/research/output/ion-shortrange-paper/figures/"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### osmp RMSD"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true,
"tags": []
},
"outputs": [],
"source": [
"def rmsd_osmp():\n",
" \n",
" diff_rmsd_dict = collections.defaultdict(lambda: 0)\n",
"\n",
" for s, (stn, st) in enumerate(system_types_osmp.items()):\n",
" print(s, stn)\n",
" n_ions = sum(st['n_cation_anion'])\n",
" \n",
" # experimental from literature\n",
" x_lit, y_lit = get_osmotic_coeff_lit(osmp_lit_dict, stn, 'Guendouzi 2001')\n",
"\n",
" for f, (ffn, ff) in enumerate(force_fields_osmp.items()):\n",
" #print(ffn)\n",
" system_names = tuple((sys['name'] for sys in system_generator(*systems_osmp)\n",
" if ffn == sys['force-field']['name']\n",
" if sys['type']['name'] == stn))\n",
" osmp_name = 'osmp-xy-k4000'\n",
" osmp_method = osmp_methods[osmp_name]\n",
" rows = (system_names, osmp_name, 'prod')\n",
" x = df_osmp.loc[(rows, 'c_inner')]\n",
" xerr = df_osmp.loc[(rows, 'c_inner_err')]\n",
" vant_Hoff_Pi = vant_Hoff_osmotic_pressure(x, n_ions, 300)\n",
" y = df_osmp.loc[(rows, 'Pi')] / vant_Hoff_Pi\n",
" # ignore first and last point, excluding 0 and up to 3 mol/l\n",
" x = x[1:-1]\n",
" y = y[1:-1]\n",
" # msd\n",
" msd = np.sum((y - np.interp(x, x_lit, y_lit))**2)\n",
" diff_rmsd_dict[(ff['name'], 'msd')] += msd\n",
" #diff_rmsd_dict[(ff['name'], 'counter')] += 1.0\n",
" \n",
" for ff_name, ff in force_fields.items():\n",
" if 'dummy' in ff['tags']:\n",
" continue\n",
" df_rmsd.at[ff_name, 'osmotic'] = np.sqrt(diff_rmsd_dict[(ff_name, 'msd')]) # / diff_rmsd_dict[(ff_name, 'counter')]\n",
" #df_rmsd.at['Buckingham-iff-altern5-eccr1-co1.2-nopc', 'osmotic'] = np.nan\n",
" #df_rmsd.at['Buckingham-iff-altern5-netz-co0.9-nopc', 'osmotic'] = np.nan\n",
" \n",
"rmsd_osmp()\n",
"df_rmsd"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"# water pressure outside slab vs. concentration\n",
"cmap = plt.get_cmap('rainbow')\n",
"\n",
"fig, ax = plt.subplots(figsize=(8, 4))\n",
"for f, force_field in enumerate((ff for ff in force_fields if 'conc-range' in ff['tags'])):\n",
" print(force_field['name'])\n",
" system_names = [sys['name'] for sys in systems if force_field['name'] == sys['force-field']['name']]\n",
" \n",
" for osmp_nr, (osmp_name, osmp_method) in enumerate(osmp_methods.items()):\n",
" #print(' osmp_name', osmp_name)\n",
" \n",
" rows = (system_names, osmp_name, 'pre')\n",
" x = df_osmp.loc[(rows, 'x_inner')]\n",
" y = df_osmp.loc[(rows, 'p_ex')]\n",
" if len(x) > 0:\n",
" ax.plot(x, y, color=cmap((f+2)/4), label=force_field['name'] + ' pre', marker='x', linestyle=':')\n",
" rows = (system_names, osmp_name, 'prod')\n",
" x = df_osmp.loc[(rows, 'x_inner')]\n",
" y = df_osmp.loc[(rows, 'p_ex')]\n",
" if len(x) > 0:\n",
" ax.plot(x, y, color=cmap(f/4), label=force_field['name'] + ' prod', marker='D', linestyle=':')\n",
"ax.legend()\n",
"ax.set_title('outside pressure')\n",
"ax.set_xlabel('x(MET)')\n",
"ax.set_ylabel('p_ex in bar')\n",
"#ax.set_xlim(0)\n",
"#ax.set_ylim(0)\n",
"#fig.savefig(\"figures/p_ex.png\", dpi=150)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# DoS and 2PT"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## systems_2pt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"force_fields_2pt = {ffn: ff for ffn, ff in force_fields.items() if 'fit' not in ff['tags'] and 'dummy' not in ff['tags']}\n",
"system_types_2pt = system_types\n",
"\n",
"systems_2pt = (system_types_2pt, force_fields_2pt)\n",
"def sys_gen_2pt():\n",
" for system in (sys for sys in system_generator(*systems_2pt, verbose=False) if {'dos', 'conc-range'}.issubset(sys['tags'])):\n",
" yield system\n",
" \n",
"pd.DataFrame(sys_gen_2pt())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## dos processing functions"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def create_nocomp_spectra(dos_json):\n",
" # definitions\n",
" dos_nocomp_dict = {\n",
" 'trn': ['trn_x', 'trn_y', 'trn_z'],\n",
" 'rot': ['rot_x', 'rot_y', 'rot_z'],\n",
" 'vib': ['vib_x', 'vib_y', 'vib_z'],\n",
" 'roto': ['roto_a', 'roto_b', 'roto_c']\n",
" }\n",
"\n",
" # sum no-component spectra from component spectra\n",
" for h, moltype in enumerate(dos_json['moltypes']):\n",
" # loop dos_types to build\n",
" for dos_type_nocomp, dos_types_comp in dos_nocomp_dict.items():\n",
" dos_nocomp_data = None\n",
" # loop dos_types to sum\n",
" for dos_type_comp in dos_types_comp:\n",
" dos_comp_data = np.array(moltype['spectra'][dos_type_comp])\n",
" if dos_nocomp_data is None:\n",
" dos_nocomp_data = dos_comp_data.copy()\n",
" else:\n",
" dos_nocomp_data += dos_comp_data\n",
" moltype['spectra'][dos_type_nocomp] = dos_nocomp_data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def get_volume_from_npt(edr_filename):\n",
" \"\"\"returns the average volume from a gromacs .edr file\"\"\"\n",
" with tempfile.NamedTemporaryFile(suffix='.xvg') as fp:\n",
" run_bash(f\"gmx energy -f {edr_filename} -o {fp.name} <<< 'volume'\")\n",
" data, _ = gt.xvg.load(fp.name)\n",
" return np.mean(data['Volume'])\n",
"\n",
"def get_volume_from_nvt(gro_filename):\n",
" \"\"\"returns the volume from a gromacs .gro file\n",
" \n",
" not working for .gro trajectories\"\"\"\n",
" box = gt.gro.get_box(gro_filename)\n",
" return np.prod(box)\n",
"\n",
"def load_moments_of_inertia_into_moltypes(moltypes, dos_file, n_samples):\n",
" \"\"\"modifies a moltypes list, to contain moments of inertia from dos-calc\"\"\"\n",
" with open(dos_file, 'r') as f:\n",
" dos_json = json.load(f)\n",
" for moltype_nr, moltype in enumerate(moltypes):\n",
" # moi\n",
" df = pd.DataFrame(index=range(3), columns=range(n_samples))\n",
" df.index.name = 'axis'\n",
" df.columns.name = 'sample'\n",
" moltype['moments_of_inertia'] = df \n",
" moltype['moments_of_inertia'].iloc[:] = np.array(dos_json['moltypes'][moltype_nr]['moments_of_inertia']).T\n",
" # moi_std\n",
" df = pd.DataFrame(index=range(3), columns=range(n_samples))\n",
" df.index.name = 'axis'\n",
" df.columns.name = 'sample'\n",
" moltype['moments_of_inertia_std'] = df \n",
" moltype['moments_of_inertia_std'].iloc[:] = np.array(dos_json['moltypes'][moltype_nr]['moments_of_inertia_std']).T\n",
"\n",
"# seems overcomplicated\n",
"def load_doses_into_moltypes(moltypes, dos_file, n_samples, dos_names, components=False, cross=False):\n",
" \"\"\"modifies a moltypes list, to contain DoS spectra from dos-calc\"\"\"\n",
" with open(dos_file, 'r') as f:\n",
" dos_json = json.load(f)\n",
" create_nocomp_spectra(dos_json)\n",
" frequencies = dos_json['frequencies']\n",
" \n",
" # create data frame per moltype\n",
" for moltype_nr, moltype in enumerate(moltypes):\n",
" columns = pd.MultiIndex.from_arrays([['frequencies'], [0]], names=['dos', 'sample'])\n",
" df = pd.DataFrame(columns=columns)\n",
" df[('frequencies', 0)] = frequencies\n",
" moltype['doses'] = df\n",
" \n",
" # create empty columns\n",
" for sample in range(n_samples):\n",
" for dos in dos_names:\n",
" for h, moltype in enumerate(moltypes):\n",
" if (dos['tags'] == [] \n",
" or ('comp' in dos['tags'] and components)\n",
" or ('cross' in dos['tags'] and cross)):\n",
" moltype['doses'][(dos['name'], sample)] = None\n",
" \n",
" for moltype_nr, moltype in enumerate(moltypes):\n",
" for dos in dos_names:\n",
" if (dos['tags'] == [] \n",
" or ('comp' in dos['tags'] and components)\n",
" or ('cross' in dos['tags'] and cross)):\n",
" moltype['doses'].loc[:, (dos['name'], slice(None))] = np.array(dos_json['moltypes'][moltype_nr]['spectra'][dos['name']]).T"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def show_dos_integrals(moltypes, dos_names, components=False, cross=False, temperature=None):\n",
" for h, moltype in enumerate(moltypes):\n",
" frequencies = moltype['doses']['frequencies'][0]\n",
" prefactor = 1\n",
" if temperature is not None:\n",
" prefactor = 1 / (1/2 * oconst.k_gro * temperature)\n",
" for dos in dos_names:\n",
" if (dos['tags'] == [] \n",
" or ('comp' in dos['tags'] and components)\n",
" or ('cross' in dos['tags'] and cross)):\n",
" for sample in moltype['doses'][dos['name']].columns:\n",
" print(dos['name'], sample,\n",
" prefactor * np.trapz(moltype['doses'][dos['name']][sample], frequencies))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## load dos"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def load_dos():\n",
" moltypes_with_dos_dict = {}\n",
" for system in sys_gen_2pt():\n",
" print(f\"system {system['name']}\")\n",
"\n",
" moltypes = deepcopy(system['moltypes'])\n",
" for moltype in moltypes:\n",
" moltype['doses'] = None\n",
"\n",
" with WorkingDir(system['name']):\n",
" try:\n",
" load_doses_into_moltypes(moltypes, 'npt-prod-vel/dos/dos.json', param_dos['n_samples'], dos_names, components=True)\n",
" load_moments_of_inertia_into_moltypes(moltypes, 'npt-prod-vel/dos/dos.json', param_dos['n_samples'])\n",
" except:\n",
" print(\".. loading failed ..\")\n",
"\n",
" moltypes_with_dos_dict[system['name']] = moltypes\n",
" return moltypes_with_dos_dict\n",
"\n",
"moltypes_with_dos_dict = load_dos()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## fit ion dos with three lorentz (cauchy)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# weird old version\n",
"#def lorentz(f, a, f0, γ):\n",
" #w = 2 * np.pi * f\n",
" #w0 = 2 * np.pi * f0\n",
" #return a / ((w**2 - w0**2)**2 + γ**2 * w0**2)\n",
"\n",
"def lorentz(f, a, f0, γ):\n",
" w = 2 * np.pi * f\n",
" w0 = 2 * np.pi * f0\n",
" return a / np.pi * ( γ / ((w - w0)**2 + γ**2) )\n",
"\n",
"def two_lorentz(f, a0, f00, γ0, a1, f01, γ1):\n",
" return lorentz(f, a0, f00, γ0) + lorentz(f, a1, f01, γ1)\n",
"\n",
"def three_lorentz(f, a0, f00, γ0, a1, f01, γ1, a2, f02, γ2):\n",
" return lorentz(f, a0, f00, γ0) + lorentz(f, a1, f01, γ1) + lorentz(f, a2, f02, γ2)\n",
"\n",
"fit_func = {\n",
" 'cation': {\n",
" 'water-cacl2_': three_lorentz,\n",
" 'water-kcl': two_lorentz,\n",
" 'water-licl': three_lorentz,\n",
" 'water-nacl': two_lorentz,\n",
" },\n",
" 'anion': collections.defaultdict(lambda: two_lorentz)\n",
"}\n",
"\n",
"p0 = {\n",
" 'cation': {\n",
" 'water-cacl2_': (2e-2, 1, 10, 8e-2, 6, 10, 2e-2, 10, 10),\n",
" 'water-kcl': (4e-2, 1, 10, 2e-2, 5, 10),\n",
" 'water-licl': (1e-2, 1, 30, 8e-2, 4, 30, 2e-2, 10, 30),\n",
" #'water-nacl': (1e-2, 1, 30, 8e-2, 4, 30, 2e-2, 8, 30),\n",
" 'water-nacl': (2e-2, 1, 10, 2e-2, 10, 10),\n",
" },\n",
" 'anion': collections.defaultdict(lambda: (4e-2, 1.2, 3, 2e-2, 5, 3))\n",
"}\n",
"\n",
"max_γ = 60\n",
"bounds = {\n",
" 'cation': {\n",
" 'water-cacl2_': ((0, 0, 0, 0, 3, 0, 0, 7, 0), (np.inf, 3, max_γ, np.inf, 7, max_γ, np.inf, np.inf, max_γ)),\n",
" 'water-kcl': ((0, 0, 0, 0, 2.5, 0), (np.inf, 2.5, max_γ, np.inf, np.inf, max_γ)),\n",
" 'water-licl': ((0, 0, 0, 0, 2.5, 0, 0, 9, 0), (np.inf, 2.5, max_γ, np.inf, 9, max_γ, np.inf, np.inf, max_γ)),\n",
" #'water-nacl': ((0, 0, 0, 0, 2.5, 0, 0, 7, 0), (np.inf, 2.5, max_γ, np.inf, 7, max_γ, np.inf, 12, max_γ)),\n",
" 'water-nacl': ((0, 0, 0, 0, 2.5, 0), (np.inf, 2.5, max_γ, np.inf, np.inf, max_γ)),\n",
" },\n",
" 'anion': collections.defaultdict(lambda: ((0, 0, 0, 0, 3, 0), (np.inf, 3, np.inf, np.inf, np.inf, np.inf))),\n",
" #'anion': collections.defaultdict(lambda: ((0, 0, 0, 0, 0, 0), (np.inf, np.inf, np.inf, np.inf, np.inf, np.inf))),\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"oconst.rec_cm_per_THz * 2.2"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def fit_dos():\n",
" popt_dict = {'cation': {'opt': {}, 'err': {}}, 'anion': {'opt': {}, 'err': {}}}\n",
"\n",
" for system in (sys for sys in sys_gen_2pt() if sys['type']['name'] != 'water-pure'):\n",
" print(f\"system {system['name']}\")\n",
"\n",
" for i, ion in enumerate(('cation', 'anion')):\n",
" #print(ion)\n",
" moltypes_with_dos = moltypes_with_dos_dict[system['name']]\n",
" moltype = moltypes_with_dos[i+1]\n",
"\n",
" frequencies = np.array(moltype['doses'][('frequencies', 0)])\n",
" dos_samples = np.array(moltype['doses'].loc[:, ('trn', slice(None))]).T\n",
" dos_mean = dos_samples.mean(axis=0)\n",
" #dos_std = dos_samples.std(axis=0)\n",
" #sigma = dos_std\n",
" #sigma = np.sqrt(frequencies + frequencies[1])\n",
" sigma = None\n",
" popt, pcov = optimize.curve_fit(fit_func[ion][system['type']['name']], frequencies, dos_mean, p0=p0[ion][system['type']['name']], maxfev=1e5, sigma=sigma,\n",
" bounds=bounds[ion][system['type']['name']], method='trf')\n",
" perr = np.sqrt(np.diag(pcov))\n",
" # TODO\n",
" #popt, perr = sorted()\n",
" if ion == 'cation':\n",
" #print('a', popt[0::3])\n",
" print('f0', popt[1::3] * oconst.rec_cm_per_THz)\n",
" #print('γ', popt[2::3])\n",
" #print('popt', popt)\n",
" #print('bounds', bounds[ion][system['type']['name']])\n",
" popt_dict[ion]['opt'][system['name']] = popt\n",
" popt_dict[ion]['err'][system['name']] = perr\n",
" return popt_dict\n",
" \n",
"popt_dict = fit_dos()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## plot"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### experimental data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# experimental data from Schwaab 2019\n",
"# cm^{-1}\n",
"freq_exp = {\n",
" 'water-cacl2_': 146,\n",
" # i think they are wrong. You would expect it to be at least sqrt(2) times K+, due to double electric force.\n",
" # even higher because of lower ion radius increases electric force further (from Wikipedia r_k+ = 152 pm, r_ca2+ = 114 pm)\n",
" # stronger force expected from coulombs law: 1/r² -> 1/114**2 / (1/152**2) = 1.7777\n",
" # total effect radius and charge -> 1.886\n",
" # take into account screening?\n",
" 'water-kcl': 151,\n",
" 'water-licl': 400,\n",
" 'water-nacl': 172,\n",
"}\n",
"\n",
"freq_exp2 = {\n",
" 'water-cacl2_': 320, # from SI Fig. 3\n",
"}\n",
"\n",
"cation_radii = {\n",
" 'water-cacl2_': 0.114,\n",
" 'water-kcl': 0.152,\n",
" 'water-licl': 0.09,\n",
" 'water-nacl': 0.116,\n",
"}\n",
"\n",
"cation_charge = {\n",
" 'water-cacl2_': 2,\n",
" 'water-kcl': 1,\n",
" 'water-licl': 1,\n",
" 'water-nacl': 1,\n",
"}\n",
"\n",
"cation_mass = {\n",
" 'water-cacl2_': atomtypes['CA']['mass'],\n",
" 'water-kcl': atomtypes['K']['mass'],\n",
" 'water-licl': atomtypes['LI']['mass'],\n",
" 'water-nacl': atomtypes['NA']['mass'],\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### frequency from charge, mass, and radius"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"# not sure if we can expect this to be valid from the theory\n",
"# at the equi. position F_e = -F_rep\n",
"def theo_freq():\n",
" fig, ax = plt.subplots(constrained_layout=True)\n",
"\n",
" systypes = OrderedSet(sys['type']['name'] for sys in sys_gen_2pt() if sys['type']['name'] != 'water-pure')\n",
" freq_predict = {st: np.sqrt(1/cation_mass[st]*cation_charge[st]/cation_radii[st]**2) for st in systypes}\n",
" x = [freq_predict[st] for st in systypes]\n",
" y = [freq_exp2[st] if st in freq_exp2 else freq_exp[st] for st in systypes]\n",
" ax.scatter(x, y)\n",
" ax.set_xticks(x)\n",
" ax.set_xticklabels([f\"{freq_predict[st]:.1f}\\n{sys_type_short_names[st]}\" for st in systypes])\n",
"\n",
" ax.set_xlim(0)\n",
" ax.set_ylim(0)\n",
" plt.show()\n",
" \n",
"theo_freq()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### show dos"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true,
"tags": []
},
"outputs": [],
"source": [
"# show ion dos\n",
"def plot_dos():\n",
" params = {\n",
" 'legend.handlelength': 2.0,\n",
" 'legend.fontsize': 8,\n",
" 'legend.labelspacing': 0.05,\n",
" 'figure.dpi': 96,\n",
" }\n",
"\n",
" xlim_cations = {\n",
" 'water-licl': (0, 800),\n",
" 'water-nacl': (0, 400),\n",
" 'water-kcl': (0, 300),\n",
" 'water-cacl2_': (0, 500),\n",
" }\n",
" force_fields_to_show = {ffn: ff for ffn, ff in force_fields_2pt.items() if ffn not in ('netz-co0.9',)}\n",
" system_types_to_show = {stn: st for stn, st in system_types_2pt.items() if stn not in ('water-pure',)}\n",
"\n",
" linestyles = ['-', '--', '-.', ':']\n",
"\n",
" with mpl.rc_context(rc={**mpl_rc_global, **params}):\n",
" for t, (systype_name, systype) in enumerate(system_types_to_show.items()):\n",
" print(f\"system type {systype_name}\")\n",
" fig, axes = plt.subplots(ncols=2, figsize=(4.6, 1.5), constrained_layout=True, dpi=200)\n",
" \n",
"\n",
" for i, ion in enumerate(('cation', 'anion')):\n",
" #print(f\"ion {ion}\")\n",
" ax = axes[i]\n",
" for s, system in enumerate((sys for sys in system_generator({systype_name: systype}, force_fields_to_show) if 'dos' in sys['tags'])):\n",
" #print(f\"system {system['name']}\")\n",
" moltypes_with_dos = moltypes_with_dos_dict[system['name']]\n",
" moltype = moltypes_with_dos[i+1]\n",
"\n",
" frequencies = np.array(moltype['doses'][('frequencies', 0)])\n",
" dos_samples = np.array(moltype['doses'].loc[:, ('trn', slice(None))]).T\n",
" dos_mean = dos_samples.mean(axis=0)\n",
" dos_min = dos_samples.min(axis=0)\n",
" dos_max = dos_samples.max(axis=0)\n",
" #linestyle = linestyles[s%4]\n",
" linestyle = '-'\n",
" color = ff_colors[system['force-field']['name']]\n",
" line, = ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" dos_mean / oconst.rec_cm_per_THz,\n",
" linestyle=linestyle,\n",
" linewidth=1.0,\n",
" color=color,\n",
" label=ff_short_names[system['force-field']['name']],\n",
" )\n",
" # fit\n",
" ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" fit_func[ion][systype_name](\n",
" frequencies,\n",
" *popt_dict[ion]['opt'][system['name']]\n",
" ) / oconst.rec_cm_per_THz,\n",
" color=line.get_color(),\n",
" linestyle='--',\n",
" linewidth=0.6,\n",
" )\n",
" ax.axvline(popt_dict[ion]['opt'][system['name']][-2] * oconst.rec_cm_per_THz,\n",
" color=line.get_color(), linewidth=0.6, linestyle=':')\n",
" ax.axvline(popt_dict[ion]['opt'][system['name']][-5] * oconst.rec_cm_per_THz,\n",
" color=line.get_color(), linewidth=0.5, linestyle='--')\n",
" #if len(popt_dict[ion]['opt'][system['name']]) == 9:\n",
" #ax.axvline(popt_dict[ion]['opt'][system['name']][-8] * oconst.rec_cm_per_THz,\n",
" #color=line.get_color(), linewidth=0.5, linestyle='-.')\n",
" \n",
" print(ff_short_names[system['force-field']['name']])\n",
" print(popt_dict[ion]['opt'][system['name']][1::3])\n",
" print(popt_dict[ion]['opt'][system['name']][1::3] * oconst.rec_cm_per_THz)\n",
" # p0\n",
" \"\"\"\n",
" ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" three_lorentz(frequencies, *p0[systype_name]) / oconst.rec_cm_per_THz,\n",
" color='k',\n",
" linestyle='-',\n",
" linewidth=0.6,\n",
" label='p0'\n",
" )\n",
" \"\"\"\n",
"\n",
" ax.set_title(ion_short_names[moltype['name']])\n",
" ax.set_ylim(0)\n",
" ax.set_xlabel(r'$\\tilde v$ in cm$^{-1}$')\n",
" #ax.set_yticks([])\n",
"\n",
" # TEMP\n",
" #plt.xticks(np.arange(min(x), max(x), 50))\n",
"\n",
" axes[0].set_xlim(xlim_cations[systype_name])\n",
" axes[1].set_xlim(0, 400)\n",
" axes[1].legend(frameon=False)\n",
" axes[0].set_ylabel(r'$D\\!O\\!S(\\tilde v)$ in cm')\n",
" for ax in axes:\n",
" ax.ticklabel_format(axis='y', style='sci', scilimits=(0, 0))\n",
" fig.savefig(f\"../figures/dos-{systype_name}.pdf\")\n",
" plt.show()\n",
" \n",
"plot_dos()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### show water dos"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"# show ion dos\n",
"def plot_water_dos():\n",
" params = {\n",
" 'legend.handlelength': 2.0,\n",
" 'legend.fontsize': 8,\n",
" 'legend.labelspacing': 0.05,\n",
" 'figure.dpi': 96,\n",
" }\n",
"\n",
" force_fields_to_show = {ffn: ff for ffn, ff in force_fields_2pt.items() if ffn not in ('netz-co0.9',)}\n",
" system_types_to_show = {stn: st for stn, st in system_types_2pt.items() if stn not in ('water-pure',)}\n",
"\n",
" linestyles = ['-', '--', '-.', ':']\n",
"\n",
" with mpl.rc_context(rc={**mpl_rc_global, **params}):\n",
" \n",
" fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(4.6, 3.2), constrained_layout=True, dpi=200, sharex='all', sharey='all')\n",
" \n",
" for t, (systype_name, systype) in enumerate(system_types_to_show.items()):\n",
" print(f\"system type {systype_name}\")\n",
"\n",
" ax = axes.flatten()[t]\n",
" for s, system in enumerate((sys for sys in system_generator({systype_name: systype}, force_fields_to_show) if 'dos' in sys['tags'])):\n",
" print(f\"system {system['name']}\")\n",
" moltypes_with_dos = moltypes_with_dos_dict[system['name']]\n",
" moltype = moltypes_with_dos[0]\n",
"\n",
" frequencies = np.array(moltype['doses'][('frequencies', 0)])\n",
" dos_trn_samples = np.array(moltype['doses'].loc[:, ('trn', slice(None))]).T\n",
" dos_rot_samples = np.array(moltype['doses'].loc[:, ('roto', slice(None))]).T\n",
" dos_trn_mean = dos_trn_samples.mean(axis=0)\n",
" dos_rot_mean = dos_rot_samples.mean(axis=0)\n",
" color = ff_colors[system['force-field']['name']]\n",
" line_trn, = ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" dos_trn_mean / oconst.rec_cm_per_THz,\n",
" linestyle='-',\n",
" linewidth=1.0,\n",
" color=color,\n",
" label=ff_short_names[system['force-field']['name']],\n",
" )\n",
" line_rot, = ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" dos_rot_mean / oconst.rec_cm_per_THz,\n",
" linestyle='--',\n",
" linewidth=1.0,\n",
" color=color,\n",
" )\n",
" #ax.set_title('foo')\n",
" ax.text(0.2, 0.8, sys_type_short_names[systype_name], transform=ax.transAxes)\n",
"\n",
" # all\n",
" for ax in axes.flatten():\n",
" ax.set_xlim(0, 1100)\n",
" ax.set_ylim(0)\n",
" # first col\n",
" for ax in axes[:, 0]:\n",
" ax.set_ylabel(r'$D\\!O\\!S(\\tilde v)$ in cm')\n",
" # bottom row\n",
" for ax in axes[-1, :]:\n",
" ax.set_xlabel(r'$\\tilde v$ in cm$^{-1}$')\n",
" axes[0, 0].legend(frameon=False)\n",
" #for ax in axes:\n",
" #ax.ticklabel_format(axis='y', style='sci', scilimits=(0, 0))\n",
" fig.savefig(f\"../figures/dos-water.pdf\")\n",
" plt.show()\n",
" \n",
"plot_water_dos()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### show changes in water spectra"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true,
"tags": []
},
"outputs": [],
"source": [
"# show ion dos\n",
"def plot_water_dos_changes():\n",
" params = {\n",
" 'legend.handlelength': 2.0,\n",
" 'legend.fontsize': 8,\n",
" 'legend.labelspacing': 0.05,\n",
" 'figure.dpi': 96,\n",
" }\n",
"\n",
" force_fields_to_show = {ffn: ff for ffn, ff in force_fields_2pt.items() if ffn in ('iff-altern5-eccr1-co1.2-nopc',)}\n",
" #force_fields_to_show = {ffn: ff for ffn, ff in force_fields_2pt.items() if ffn in ('iff-altern5-eccr1-co1.2-nopc', 'madrid-co1.0tc', 'eccr1-co1.2', 'netz-co0.9tc')}\n",
" #force_fields_to_show = {ffn: ff for ffn, ff in force_fields_2pt.items() if ffn in ('iff-altern5-eccr1-co1.2-nopc', 'eccr1-co1.2')}\n",
" system_types_to_show = {stn: st for stn, st in system_types_2pt.items() if stn not in ['water-kcl', 'water-pure']}\n",
" system_types_pure = {stn: st for stn, st in system_types_2pt.items() if stn in ['water-pure']}\n",
" systype_pure_name = 'water-pure'\n",
" systype_pure = system_types_2pt[systype_pure_name]\n",
"\n",
" linestyles = ['-', '--', '-.', ':']\n",
"\n",
" with mpl.rc_context(rc={**mpl_rc_global, **params}):\n",
" \n",
" # dos\n",
" fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(5.2, 3.4), constrained_layout=True, dpi=200, sharex='all')\n",
" # dos difference\n",
" fig_d, axes_d = plt.subplots(nrows=3, ncols=1, figsize=(5.2, 3.4), constrained_layout=True, dpi=200, sharex='all')\n",
" \n",
" for t, (systype_name, systype) in enumerate(system_types_to_show.items()):\n",
" print(f\"system type {systype_name}\")\n",
"\n",
" ax = axes[t]\n",
" ax_d = axes_d[t]\n",
" for s, system in enumerate((sys for sys in system_generator({systype_name: systype}, force_fields_to_show) if ('dos' in sys['tags']) and (sys['molar-mixing-ratio'] == 0.1))):\n",
" print(f\"system {system['name']}\", system['molar-mixing-ratio'])\n",
" \n",
" # load dos pure water\n",
" print(system['force-field']['name'])\n",
" system_pure = next(system_generator({systype_pure_name: systype_pure}, {system['force-field']['name']: system['force-field']}))\n",
" moltypes_pure_with_dos = moltypes_with_dos_dict[system_pure['name']]\n",
" moltype_pure = moltypes_pure_with_dos[0]\n",
" frequencies_pure = np.array(moltype_pure['doses'][('frequencies', 0)])\n",
" dos_trn_samples_pure = np.array(moltype_pure['doses'].loc[:, ('trn', slice(None))]).T\n",
" dos_rot_samples_pure = np.array(moltype_pure['doses'].loc[:, ('roto', slice(None))]).T\n",
" dos_trn_mean_pure = dos_trn_samples_pure.mean(axis=0)\n",
" dos_rot_mean_pure = dos_rot_samples_pure.mean(axis=0)\n",
" \n",
" # load dos electrolyte\n",
" moltypes_with_dos = moltypes_with_dos_dict[system['name']]\n",
" moltype = moltypes_with_dos[0]\n",
" try:\n",
" frequencies = np.array(moltype['doses'][('frequencies', 0)])\n",
" except:\n",
" continue\n",
" dos_trn_samples = np.array(moltype['doses'].loc[:, ('trn', slice(None))]).T\n",
" dos_rot_samples = np.array(moltype['doses'].loc[:, ('roto', slice(None))]).T\n",
" dos_trn_mean = dos_trn_samples.mean(axis=0)\n",
" dos_rot_mean = dos_rot_samples.mean(axis=0)\n",
" \n",
" # plot dos\n",
" color = ff_colors[system['force-field']['name']]\n",
" if s == 0:\n",
" ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" dos_trn_mean_pure / oconst.rec_cm_per_THz,\n",
" linestyle='-',\n",
" linewidth=1.0,\n",
" color='k',\n",
" label='pure water',\n",
" )\n",
" ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" dos_rot_mean_pure / oconst.rec_cm_per_THz,\n",
" linestyle='--',\n",
" linewidth=1.0,\n",
" color='k',\n",
" )\n",
" line_trn, = ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" dos_trn_mean / oconst.rec_cm_per_THz,\n",
" linestyle='-',\n",
" linewidth=1.0,\n",
" color=color,\n",
" label=ff_short_names[system['force-field']['name']],\n",
" )\n",
" line_rot, = ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" dos_rot_mean / oconst.rec_cm_per_THz,\n",
" linestyle='--',\n",
" linewidth=1.0,\n",
" color=color,\n",
" )\n",
" #ax.set_title('foo')\n",
" ax.text(0.2, 0.8, sys_type_short_names[systype_name], transform=ax.transAxes)\n",
" \n",
" # plot dos difference\n",
" color = ff_colors[system['force-field']['name']]\n",
" line_trn, = ax_d.plot(frequencies * oconst.rec_cm_per_THz,\n",
" (dos_trn_mean - dos_trn_mean_pure) / oconst.rec_cm_per_THz,\n",
" linestyle='-',\n",
" linewidth=1.0,\n",
" color=color,\n",
" label=ff_short_names[system['force-field']['name']],\n",
" )\n",
" line_rot, = ax_d.plot(frequencies * oconst.rec_cm_per_THz,\n",
" (dos_rot_mean - dos_rot_mean_pure) / oconst.rec_cm_per_THz,\n",
" linestyle='--',\n",
" linewidth=1.0,\n",
" color=color,\n",
" )\n",
" #ax.set_title('foo')\n",
" if s == 0:\n",
" ax.text(0.2, 0.8, sys_type_short_names[systype_name], transform=ax.transAxes)\n",
" ax_d.text(0.1, 0.1, sys_type_short_names[systype_name], transform=ax_d.transAxes)\n",
"\n",
" # dos\n",
" for ax in axes:\n",
" ax.set_xlim(0, 1100)\n",
" ax.set_ylim(0)\n",
" ax.set_ylabel(r'$D\\!O\\!S(\\tilde v)$ in cm')\n",
" axes[-1].set_xlabel(r'$\\tilde v$ in cm$^{-1}$')\n",
" axes[0].legend(frameon=False)\n",
" \n",
" # difference\n",
" for ax_d in axes_d:\n",
" ax_d.set_xlim(0, 1100)\n",
" #ax_d.set_ylim(-1e-3, 1e-3)\n",
" ax_d.set_ylabel(r'$\\Delta D\\!O\\!S(\\tilde v)$ in cm')\n",
" ax_d.ticklabel_format(style='sci', axis='y', scilimits=(0, 0))\n",
" ax_d.axhline(0, color='gray', linewidth=0.5, linestyle=':', zorder=-10)\n",
" axes_d[-1].set_xlabel(r'$\\tilde v$ in cm$^{-1}$')\n",
" axes_d[0].legend(frameon=False, ncol=5)\n",
" \n",
" fig.savefig(f\"../figures/dos-water.pdf\")\n",
" fig_d.savefig(f\"../figures/dos-water-differences.pdf\")\n",
" plt.show()\n",
" \n",
"plot_water_dos_changes()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true,
"tags": []
},
"outputs": [],
"source": [
"# show water roto\n",
"def plot_dos_changes():\n",
" params = {\n",
" 'legend.handlelength': 2.0,\n",
" 'legend.fontsize': 8,\n",
" 'legend.labelspacing': 0.05,\n",
" 'figure.dpi': 96,\n",
" }\n",
"\n",
" system_types_to_show = {stn: st for stn, st in system_types_2pt.items() if stn not in ['water-kcl', 'water-pure']}\n",
" system_types_pure = {stn: st for stn, st in system_types_2pt.items() if stn in ['water-pure']}\n",
" systype_pure_name = 'water-pure'\n",
" systype_pure = system_types_2pt[systype_pure_name]\n",
" \n",
" doses_to_show_sets = {\n",
" 'sum': {\n",
" 'trn': {'dos': 'trn', 'label': 'trn', 'linestyle': '-', 'moltype': 0, 'sum': True},\n",
" 'roto': {'dos': 'roto', 'label': 'rot', 'linestyle': '--', 'moltype': 0, 'sum': True},\n",
" 'trn-cat': {'dos': 'trn', 'label': 'trn', 'linestyle': '-', 'moltype': 1, 'sum': True},\n",
" 'trn-an': {'dos': 'trn', 'label': 'trn', 'linestyle': '-', 'moltype': 2, 'sum': True},\n",
" },\n",
" 'ions': {\n",
" 'trn-cat': {'dos': 'trn', 'label': 'cation', 'linestyle': '-', 'moltype': 1, 'sum': False},\n",
" 'trn-an': {'dos': 'trn', 'label': 'anion', 'linestyle': '--', 'moltype': 2, 'sum': False},\n",
" },\n",
" 'trn+rot': {\n",
" 'trn': {'dos': 'trn', 'label': 'trn', 'linestyle': '-', 'moltype': 0, 'sum': False},\n",
" 'roto': {'dos': 'roto', 'label': 'rot', 'linestyle': '--', 'moltype': 0, 'sum': False},\n",
" },\n",
" 'roto-sep': {\n",
" 'roto_a': {'dos': 'roto_a', 'label': 'a', 'linestyle': '-', 'moltype': 0, 'sum': False},\n",
" 'roto_b': {'dos': 'roto_b', 'label': 'b', 'linestyle': '--', 'moltype': 0, 'sum': False},\n",
" 'roto_c': {'dos': 'roto_c', 'label': 'c', 'linestyle': ':', 'moltype': 0, 'sum': False},\n",
" },\n",
" }\n",
" \n",
" for dos_set_name, doses_to_show in doses_to_show_sets.items():\n",
" print(dos_set_name)\n",
" \n",
" force_fields_to_show_sets = {\n",
" 'ecc-imc': {ffn: ff for ffn, ff in force_fields_2pt.items() if ffn in ('iff-altern5-eccr1-co1.2-nopc',)},\n",
" 'madrid': {ffn: ff for ffn, ff in force_fields_2pt.items() if ffn in ('madrid-co1.0tc',)},\n",
" }\n",
" for ff_set_name, force_fields_to_show in force_fields_to_show_sets.items():\n",
" print(ff_set_name)\n",
"\n",
" with mpl.rc_context(rc={**mpl_rc_global, **params}):\n",
"\n",
" # dos\n",
" fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(5.2, 3.4), constrained_layout=True, dpi=200, sharex='all')\n",
" # dos difference\n",
" #fig_d, axes_d = plt.subplots(nrows=3, ncols=1, figsize=(5.2, 3.4), constrained_layout=True, dpi=200, sharex='all')\n",
"\n",
" for t, (systype_name, systype) in enumerate(system_types_to_show.items()):\n",
" #print(f\"system type {systype_name}\")\n",
"\n",
" ax = axes[t]\n",
" #ax_d = axes_d[t]\n",
" for s, system in enumerate((sys for sys in system_generator({systype_name: systype}, force_fields_to_show) if ('dos' in sys['tags']) and (sys['molar-mixing-ratio'] == 0.1))):\n",
" #print(f\"system {system['name']}\", system['molar-mixing-ratio'])\n",
"\n",
" # load dos pure water\n",
" #print(system['force-field']['name'])\n",
" system_pure = next(system_generator({systype_pure_name: systype_pure}, {system['force-field']['name']: system['force-field']}))\n",
" moltypes_pure_with_dos = moltypes_with_dos_dict[system_pure['name']]\n",
" doses_mean_pure = {}\n",
" for dos, dos_data in doses_to_show.items():\n",
" try:\n",
" moltype_pure = moltypes_pure_with_dos[dos_data['moltype']]\n",
" except:\n",
" continue\n",
" if dos_data['sum']:\n",
" if 'sum' not in doses_mean_pure:\n",
" doses_mean_pure['sum'] = moltype_pure['nmols'] * np.array(moltype_pure['doses'].loc[:, (dos_data['dos'], slice(None))]).T.mean(axis=0)\n",
" else:\n",
" doses_mean_pure['sum'] += moltype_pure['nmols'] * np.array(moltype_pure['doses'].loc[:, (dos_data['dos'], slice(None))]).T.mean(axis=0)\n",
" else:\n",
" doses_mean_pure[dos] = np.array(moltype_pure['doses'].loc[:, (dos_data['dos'], slice(None))]).T.mean(axis=0)\n",
"\n",
" # load dos electrolyte\n",
" moltypes_with_dos = moltypes_with_dos_dict[system['name']]\n",
" frequencies = np.array(moltypes_with_dos[0]['doses'][('frequencies', 0)])\n",
" doses_mean = {}\n",
" for dos, dos_data in doses_to_show.items():\n",
" moltype = moltypes_with_dos[dos_data['moltype']]\n",
" if dos_data['sum']:\n",
" if 'sum' not in doses_mean:\n",
" doses_mean['sum'] = moltype['nmols'] * np.array(moltype['doses'].loc[:, (dos_data['dos'], slice(None))]).T.mean(axis=0)\n",
" else:\n",
" doses_mean['sum'] += moltype['nmols'] * np.array(moltype['doses'].loc[:, (dos_data['dos'], slice(None))]).T.mean(axis=0)\n",
" else:\n",
" doses_mean[dos] = np.array(moltype['doses'].loc[:, (dos_data['dos'], slice(None))]).T.mean(axis=0)\n",
"\n",
" # plot dos\n",
" color = ff_colors[system['force-field']['name']]\n",
" # plot pure\n",
" if s == 0:\n",
" for d, (dos, dos_data) in enumerate(doses_to_show.items()):\n",
" if dos_data['sum']:\n",
" dos = 'sum'\n",
" if d != 0:\n",
" continue\n",
" if dos not in doses_mean_pure.keys():\n",
" print('.. no pure water data ..')\n",
" continue\n",
" ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" doses_mean_pure[dos] / oconst.rec_cm_per_THz,\n",
" linestyle=dos_data['linestyle'],\n",
" linewidth=1.0,\n",
" color='k',\n",
" label='pure water' if d == 0 else None,\n",
" )\n",
" # plot electrolyte\n",
" for d, (dos, dos_data) in enumerate(doses_to_show.items()):\n",
" if dos_data['sum']:\n",
" dos = 'sum'\n",
" if d != 0:\n",
" continue\n",
" line_rot, = ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" doses_mean[dos] / oconst.rec_cm_per_THz,\n",
" linestyle=dos_data['linestyle'],\n",
" linewidth=1.0,\n",
" color=color,\n",
" label=ff_short_names[system['force-field']['name']] if d == 0 else None,\n",
" )\n",
" if s == 0:\n",
" ax.text(0.22, 0.8, sys_type_short_names[systype_name], transform=ax.transAxes)\n",
"\n",
" # plot dos difference\n",
" \"\"\"\n",
" for a, mol_ax in enumerate(mol_axes):\n",
" line_rot, = ax_d.plot(frequencies * oconst.rec_cm_per_THz,\n",
" (dos_rot_ax_mean[a] - dos_rot_ax_mean_pure[a]) / oconst.rec_cm_per_THz,\n",
" linestyle=linestyles[a],\n",
" linewidth=1.0,\n",
" color=color,\n",
" )\n",
" #ax.set_title('foo')\n",
" if s == 0:\n",
" ax_d.text(0.1, 0.1, sys_type_short_names[systype_name], transform=ax_d.transAxes)\n",
" \"\"\"\n",
"\n",
" # dos\n",
" for ax in axes:\n",
" ax.set_xlim(0, 1100)\n",
" ax.set_ylim(0)\n",
" ax.set_ylabel(r'$D\\!O\\!S(\\tilde v)$ in cm')\n",
" axes[-1].set_xlabel(r'$\\tilde v$ in cm$^{-1}$')\n",
" leg1 = axes[0].legend(frameon=False, loc='upper right')\n",
" if dos_set_name != 'sum':\n",
" axes[0].add_artist(leg1)\n",
" handles = []\n",
" for d, (dos, dos_data) in enumerate(doses_to_show.items()):\n",
" handles.append(axes[0].plot(np.nan, np.nan, linestyle=dos_data['linestyle'], color='grey', label=dos_data['label'])[0])\n",
" axes[0].legend(handles=handles, loc='upper left' if dos_set_name == 'roto-sep' else 'upper center')\n",
"\n",
" # difference\n",
" \"\"\"\n",
" for ax_d in axes_d:\n",
" ax_d.set_xlim(0, 1100)\n",
" #ax_d.set_ylim(-1e-3, 1e-3)\n",
" ax_d.set_ylabel(r'$\\Delta D\\!O\\!S(\\tilde v)$ in cm')\n",
" ax_d.ticklabel_format(style='sci', axis='y', scilimits=(0, 0))\n",
" ax_d.axhline(0, color='gray', linewidth=0.5, linestyle=':', zorder=-10)\n",
" axes_d[-1].set_xlabel(r'$\\tilde v$ in cm$^{-1}$')\n",
" axes_d[0].legend(frameon=False, ncol=5)\n",
" \"\"\"\n",
"\n",
" fig.savefig(f\"../figures/dos-{dos_set_name}-{ff_set_name}.pdf\")\n",
" #fig_d.savefig(f\"../figures/dos-rot-water-{ff_set_name}-differences.pdf\")\n",
" plt.show()\n",
" \n",
"plot_dos_changes()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!cp -a ../figures/dos-* ~/research/output/ion-spectra/figures"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### toc dos plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"# show ion dos\n",
"def plot_dos_toc():\n",
" params = {}\n",
"\n",
" force_fields_to_show = {ffn: ff for ffn, ff in force_fields_2pt.items() if ffn in ('netz-co0.9tc', 'iff-altern5-netz-co0.9-nopc')}\n",
"\n",
" with mpl.rc_context(rc={**mpl_rc_global, **params}):\n",
" systype_name = 'water-licl'\n",
" systype = system_types_2pt[systype_name]\n",
" fig, ax = plt.subplots(figsize=(1.0, 1.8), constrained_layout=True, dpi=300)\n",
" ion = 'cation'\n",
" for s, system in enumerate((sys for sys in system_generator({systype_name: systype}, force_fields_to_show) if 'dos' in sys['tags'])):\n",
" print(f\"system {system['name']}\")\n",
" moltypes_with_dos = moltypes_with_dos_dict[system['name']]\n",
" moltype = moltypes_with_dos[1]\n",
"\n",
" frequencies = np.array(moltype['doses'][('frequencies', 0)])\n",
" dos_samples = np.array(moltype['doses'].loc[:, ('trn', slice(None))]).T\n",
" dos_mean = dos_samples.mean(axis=0)\n",
" dos_min = dos_samples.min(axis=0)\n",
" dos_max = dos_samples.max(axis=0)\n",
" linestyle = '-'\n",
" color = ff_colors[system['force-field']['name']]\n",
" print(color)\n",
" #color = 'k'\n",
" # plot dos\n",
" line, = ax.plot(frequencies * oconst.rec_cm_per_THz,\n",
" dos_mean / oconst.rec_cm_per_THz + (1-s) * 1.5e-2, # shift upward\n",
" linestyle=linestyle,\n",
" linewidth=1.0,\n",
" color=color,\n",
" label=ff_short_names[system['force-field']['name']],\n",
" )\n",
" # show exp\n",
" ax.axvline(freq_exp[systype_name], color='k', linewidth=1.0, linestyle=':')\n",
"\n",
" ax.set_ylim(0)\n",
" ax.set_xlim(0, 850)\n",
" ax.set_xlabel(r'$\\tilde v$ in cm$^{-1}$')\n",
" ax.set_yticks([])\n",
" ax.spines['top'].set_visible(False)\n",
" ax.spines['left'].set_visible(False)\n",
" ax.spines['right'].set_visible(False)\n",
" ax.yaxis.set_ticks_position('left')\n",
" ax.xaxis.set_ticks_position('bottom')\n",
" #plt.xticks(np.arange(min(x), max(x), 50))\n",
" #ax.axis['xzero'].set_axisline_style(\"-|>\")\n",
" \n",
" # arrow heads\n",
" ax.plot(1, 0, \">k\", transform=ax.get_yaxis_transform(), clip_on=False, markersize=1.0)\n",
"\n",
" #axes[1].set_xlim(0, 400)\n",
" #axes[0].set_ylabel(r'$D\\!O\\!S(\\tilde v)$ in kJ')\n",
"\n",
" fig.savefig(f\"../figures/toc-dos.svg\", dpi=300)\n",
" plt.show()\n",
" \n",
"plot_dos_toc()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!cp -a ../figures/toc-dos.svg ~/research/output/ion-shortrange-paper/figures/"
]
},
{
"cell_type": "markdown",
"metadata": {
"tags": []
},
"source": [
"### highest cation frequency"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"# show cation highest fit frequency\n",
"def highest_cat_freq(portrait=True, show_pmf_frequency=False):\n",
" SMALL_SIZE = 8\n",
" MEDIUM_SIZE = 10\n",
" BIGGER_SIZE = 12\n",
" params = {\n",
" 'font.size': MEDIUM_SIZE, # controls default text sizes\n",
" 'axes.titlesize': MEDIUM_SIZE, # fontsize of the axes title\n",
" 'axes.labelsize': MEDIUM_SIZE, # fontsize of the x and y labels\n",
" 'xtick.labelsize': MEDIUM_SIZE, # fontsize of the tick labels\n",
" 'ytick.labelsize': MEDIUM_SIZE, # fontsize of the tick labels\n",
" 'legend.fontsize': MEDIUM_SIZE, # legend fontsize\n",
" 'figure.titlesize': BIGGER_SIZE, # fontsize of the figure title\n",
" }\n",
" params |= {\n",
" 'legend.handlelength': 2.0,\n",
" 'legend.fontsize': 8,\n",
" 'legend.labelspacing': 0.1,\n",
" 'legend.columnspacing': 1.2,\n",
" 'figure.dpi': 120,\n",
" 'text.usetex': True,\n",
" }\n",
" width = 0.125 # of each bar\n",
" width_percentage = 0.85\n",
" ys = []\n",
" yexp = []\n",
" force_fields_to_show = {ffn: ff for ffn, ff in force_fields_2pt.items() if ffn not in ('netz-co0.9',)}\n",
" system_types_to_show = {stn: st for stn, st in system_types_2pt.items() if stn not in ('water-pure', 'water-kcl')}\n",
"\n",
" with mpl.rc_context(rc={**mpl_rc_global, **params}):\n",
"\n",
" figsize = (3.8, 3.4) if portrait else (3.8, 2.0)\n",
" fig, ax = plt.subplots(figsize=figsize, constrained_layout=True, dpi=300)\n",
" fig.set_constrained_layout_pads(w_pad=0.02, h_pad=0.01)\n",
" x = np.arange(len(system_types_to_show))\n",
" y_dict = {}\n",
" y_pmf_dict = {}\n",
" yerr_dict = {}\n",
"\n",
" for t, (systype_name, systype) in enumerate(system_types_to_show.items()):\n",
" #print(f\"system type {systype}\")\n",
" yexp.append(freq_exp[systype_name])\n",
"\n",
" for s, system in enumerate((sys for sys in system_generator({systype_name: systype}, force_fields_to_show) if 'dos' in sys['tags'])):\n",
" #print(f\"system {system['name']}\")\n",
" #moltypes_with_dos = moltypes_with_dos_dict[system['name']]\n",
" #moltype = moltypes_with_dos[i+1]\n",
" # TODO\n",
" #print(max(popt_dict['cation']['opt'][system['name']][1::3]), popt_dict['cation']['opt'][system['name']][7])\n",
" #assert np.isclose(max(popt_dict['cation']['opt'][system['name']][1::3]), popt_dict['cation']['opt'][system['name']][7])\n",
" index_to_show = -5 if systype_name == 'water-cacl2_' else -2\n",
" y_dict[(systype_name, system['force-field']['name'])] = popt_dict['cation']['opt'][system['name']][index_to_show]\n",
" yerr_dict[(systype_name, system['force-field']['name'])] = popt_dict['cation']['err'][system['name']][index_to_show]\n",
" #print(popt_dict['cation'][system['name']][1::3])\n",
" #print(popt_dict['cation'][system['name']][2::3])\n",
" if show_pmf_frequency:\n",
" y_pmf_dict[(systype_name, system['force-field']['name'])] = (pmf_frequency_dict[system['name']]\n",
" if system['type']['name'] != 'water-cacl2_'\n",
" else np.nan)\n",
"\n",
" for f, (ffn, ff) in enumerate(force_fields_to_show.items()):\n",
" xf = x + np.linspace(0, width*len(force_fields_to_show), num=len(force_fields_to_show)+1)[f] - width*len(force_fields_to_show)/2\n",
" yf = [y_dict[(systype_name, ffn)] for systype_name in system_types_to_show.keys()]\n",
" yerrf = [yerr_dict[(systype_name, ffn)] for systype_name in system_types_to_show.keys()]\n",
" ax.bar(xf, np.array(yf) * oconst.rec_cm_per_THz, width=width*width_percentage, yerr=yerrf, color=ff_colors[ffn], label=ff_short_names[ffn])\n",
" if show_pmf_frequency:\n",
" yf_pmf = [y_pmf_dict[(systype_name, ffn)] for systype_name in system_types_to_show.keys()]\n",
" ax.plot(xf, np.array(yf_pmf) * oconst.rec_cm_per_THz, color='k', linestyle='none', marker='x', markersize=3)\n",
"\n",
" # Calcium experimental from looking at spectrum\n",
" #xf = x + np.linspace(0, width*len(force_fields_to_show), num=len(force_fields_to_show)+1)[-1] - width*len(force_fields_to_show)/2\n",
" #y = [freq_exp2[stn] if stn in freq_exp2 else np.nan for stn in system_types_to_show.keys()]\n",
" #ax.bar(xf, y, width=width*width_percentage, color='lightgrey')\n",
"\n",
" # plot experimental\n",
" xf = x + np.linspace(0, width*len(force_fields_to_show), num=len(force_fields_to_show)+1)[-1] - width*len(force_fields_to_show)/2\n",
" ax.bar(xf, yexp, width=width*width_percentage, color='k', label='exp.')\n",
"\n",
" ax.set_xticks(range(len(system_types_to_show)))\n",
" ax.set_xticklabels([sys_type_short_names[stn] for stn in system_types_to_show.keys()])\n",
" legend_loc = (0.38, 0.78) if portrait else (0.0, 0.8)\n",
" ax.legend(ncol=2, loc=legend_loc)\n",
" ax.set_ylim(0, 655)\n",
" ax.set_ylabel(r'$\\tilde v$ in cm$^{-1}$')\n",
" fig.savefig('../figures/cation-highest-freq.pdf')\n",
" plt.show()\n",
" \n",
"highest_cat_freq(show_pmf_frequency=True)\n",
"#highest_cat_freq(show_pmf_frequency=False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!cp -a ../figures/cation-highest-freq.pdf ~/research/output/ion-shortrange-paper/figures/"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2pt entropies"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### functions"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def _calc_normalized_diffusivity(dos, moltype, temperature, pascal_2010_var=False):\n",
" partial_volume = moltype['partial_volume']\n",
" nmols = moltype['nmols']\n",
" mass = sum((atom['mass'] for atom in moltype['atoms']))\n",
" if pascal_2010_var:\n",
" \"\"\"As defined in Pascal 2010\"\"\"\n",
" return (2 * dos[0] / 9 * (np.pi * oconst.k_gro * temperature / mass)**(1/3)\n",
" * (nmols / partial_volume)**(1/3) * (6 / np.pi)**(2/3))\n",
" else:\n",
" \"\"\"As defined in Lin 2003, Lin 2010, lai_2014\"\"\"\n",
" return (2 * dos[0] / 9 * (np.pi * oconst.k_gro * temperature / mass)**(1/2)\n",
" * (nmols / partial_volume)**(1/3) * (6 / np.pi)**(2/3))\n",
"\n",
"def _calc_f(normalized_diffusivity):\n",
" delta = normalized_diffusivity\n",
" def function(f):\n",
" return (2 * delta**(-9/2) * f**(15/2) - 6 * delta**-3 * f**5 \n",
" - delta**(-3/2) * f**(7/2) + 6 * delta**(-3/2) * f**(5/2) + 2 * f - 2)\n",
" return optimize.brenth(function, 0, 1)\n",
"\n",
"def _calc_dos_gas(dos, f, frequencies):\n",
" return dos[0] / (1 + (np.pi * dos[0] * frequencies / (6 * f))**2)\n",
"\n",
"def _calc_w_s_solid(frequencies, temperature):\n",
" beta = 1 / (oconst.k_gro * temperature)\n",
" # circumvent warning of zero-division\n",
" frequencies = frequencies[1:]\n",
" w = (beta * oconst.h_gro * frequencies / (np.exp(beta * oconst.h_gro * frequencies) - 1) \n",
" - np.log(1 - np.exp(-beta * oconst.h_gro * frequencies)))\n",
" w = np.insert(w, 0, 0)\n",
" return w\n",
"\n",
"def _calc_w_s_trn_gas(moltype, temperature):\n",
" partial_volume = moltype['partial_volume']\n",
" nmols = moltype['nmols']\n",
" f_trn = moltype['f_trn']\n",
" mass_mol = sum((atom['mass'] for atom in moltype['atoms']))\n",
" normalized_diffusivity = moltype['normalized_diffusivity_trn']\n",
" y = f_trn**(5/2) / normalized_diffusivity**(3/2)\n",
" z = lambda y: (1 + y + y**2 - y**3) / (1-y)**3\n",
" return 1 / 3 * (5 / 2 + np.log((2 * np.pi * mass_mol * oconst.k_gro * temperature / oconst.h_gro**2)**(3/2) \n",
" * partial_volume / f_trn / nmols * z(y)) \n",
" + y * (3 * y - 4) / (1 - y)**2)\n",
" \n",
"def _calc_w_s_rot_gas(moltype, temperature):\n",
" nmols = moltype['nmols']\n",
" f_rot = moltype['f_rot']\n",
" normalized_diffusivity = moltype['normalized_diffusivity_rot']\n",
" moments_of_inertia = moltype['moi']\n",
" sigma = moltype['sigma']\n",
" if len(moltype['atoms']) > 1:\n",
" rotational_temperatures = oconst.h_gro**2 / (8 * np.pi**2 * moments_of_inertia * oconst.k_gro)\n",
" return 1 / 3 * np.log(np.sqrt(np.pi) * np.exp(3/2) / sigma \n",
" * np.sqrt(temperature**3 / np.prod(rotational_temperatures)))\n",
" else:\n",
" return 0\n",
" \n",
"def calculate_entropy_2PTQ(moltypes_with_dos, volume, temperature, sample, norm_dos=None, pascal_2010_var=False):\n",
" # total entropy of system\n",
" entropy = 0\n",
" moltypes_2pt = []\n",
"\n",
" for moltype in moltypes_with_dos:\n",
" moltype = deepcopy(moltype)\n",
" moltype['mole_fraction'] = moltype['nmols'] / sum([moltype['nmols'] for moltype in moltypes_with_dos])\n",
" moltype['partial_volume'] = moltype['mole_fraction'] * volume\n",
" \n",
" if moltype['nmols'] == 0:\n",
" continue\n",
" \n",
" # get data of sample\n",
" doses = moltype['doses']\n",
" frequencies = np.array(doses['frequencies'][0])\n",
" moltype['frequencies'] = frequencies\n",
" moltype['moi'] = np.array(moltype['moments_of_inertia'][sample])\n",
" prefactor = 1\n",
" if norm_dos == 'temp':\n",
" prefactor = 1 / (1/2 * oconst.k_gro * temperature)\n",
" moltype['dos_trn'] = prefactor * np.array(doses[('trn', sample)])\n",
" moltype['dos_rot'] = prefactor * np.array(doses[('roto', sample)])\n",
" moltype['dos_vib'] = prefactor * np.array(doses[('vib', sample)])\n",
" \n",
" # warn if not integral = ndof\n",
" integral_trn = np.trapz(x=moltype['frequencies'], y=moltype['dos_trn'])\n",
" if abs(integral_trn - 3) > 0.1:\n",
" warnings.warn(f'integral of dos_trn is {integral_trn} but should be close to 3!')\n",
"\n",
" # translation\n",
" moltype[\"normalized_diffusivity_trn\"] = _calc_normalized_diffusivity(moltype['dos_trn'], moltype, temperature, pascal_2010_var=pascal_2010_var)\n",
" moltype['f_trn'] = _calc_f(moltype[\"normalized_diffusivity_trn\"])\n",
" moltype['dos_trn_gas'] = _calc_dos_gas(moltype[\"dos_trn\"], moltype[\"f_trn\"], frequencies)\n",
" moltype['dos_trn_solid'] = moltype[\"dos_trn\"] - moltype[\"dos_trn_gas\"]\n",
"\n",
" # rotation\n",
" if len(moltype['atoms']) > 1:\n",
" moltype['normalized_diffusivity_rot'] = _calc_normalized_diffusivity(moltype[\"dos_rot\"], moltype, temperature)\n",
" moltype['f_rot'] = _calc_f(moltype[\"normalized_diffusivity_rot\"])\n",
" moltype['dos_rot_gas'] = _calc_dos_gas(moltype[\"dos_rot\"], moltype[\"f_rot\"], frequencies)\n",
" moltype['dos_rot_solid'] = moltype[\"dos_rot\"] - moltype[\"dos_rot_gas\"]\n",
" else:\n",
" moltype['normalized_diffusivity_rot'] = 0\n",
" moltype['f_rot'] = 0\n",
" moltype['dos_rot_gas'] = np.zeros(len(moltype[\"dos_rot\"]))\n",
" moltype['dos_rot_solid'] = np.zeros(len(moltype[\"dos_rot\"]))\n",
"\n",
" # weighting functions (w) for properties (_e = energy, _s = entropy),\n",
" # , motion (_trn, _rot) and phase (_solid, _gas)\n",
" w_s_trn_solid = _calc_w_s_solid(frequencies, temperature)\n",
" w_s_rot_solid = _calc_w_s_solid(frequencies, temperature)\n",
" w_s_trn_gas = _calc_w_s_trn_gas(moltype, temperature)\n",
" w_s_rot_gas = _calc_w_s_rot_gas(moltype, temperature)\n",
"\n",
" integrator = integrate.trapz\n",
"\n",
" moltype['nDoF_trn'] = integrator(1 * moltype['dos_trn'], frequencies)\n",
" moltype['nDoF_rot'] = integrator(1 * moltype['dos_rot'], frequencies)\n",
" moltype['nDoF_vib'] = integrator(1 * moltype['dos_vib'], frequencies)\n",
" moltype['entropy_trn_ho'] = oconst.k_gro * integrator(w_s_trn_solid \n",
" * moltype['dos_trn_solid'], frequencies)\n",
" moltype['entropy_trn_hs'] = oconst.k_gro * integrator(w_s_trn_gas \n",
" * moltype['dos_trn_gas'], frequencies)\n",
"\n",
" moltype['entropy_trn'] = moltype['entropy_trn_ho'] + moltype['entropy_trn_hs']\n",
"\n",
" moltype['entropy_rot_hs'] = oconst.k_gro * integrator(w_s_rot_gas\n",
" * moltype['dos_rot_gas'], frequencies)\n",
" moltype['entropy_rot_ho'] = oconst.k_gro * integrator(w_s_rot_solid\n",
" * moltype['dos_rot_solid'], frequencies)\n",
"\n",
" moltype['entropy_rot'] = moltype['entropy_rot_ho'] + moltype['entropy_rot_hs']\n",
"\n",
" moltype['entropy_vib'] = oconst.k_gro * integrator(w_s_trn_solid * moltype['dos_vib'], frequencies)\n",
"\n",
" moltype['entropy'] = moltype['entropy_trn'] + moltype['entropy_rot'] + moltype['entropy_vib']\n",
" entropy += moltype['entropy']\n",
" entropy -= oconst.k_gro * moltype['mole_fraction'] * np.log(moltype['mole_fraction'])\n",
" \n",
" # for later plotting\n",
" for dos_name in ['trn_gas', 'trn_solid', 'rot_gas', 'rot_solid']:\n",
" doses[(dos_name, sample)] = 1/prefactor * moltype['dos_' + dos_name]\n",
" moltypes_2pt.append(moltype)\n",
" \n",
" return moltypes_2pt, entropy"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def calc_volume_npt(edr_file):\n",
" run_bash(f\"gmx energy -f {edr_file} -o volume.xvg <<< 'volume'\")\n",
" data, _ = gt.xvg.load(\"volume.xvg\")\n",
" run_bash(\"rm volume.xvg\")\n",
" return np.mean(data['Volume'])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def calc_s_trn_ideal_gas(moltype, temperature, volume):\n",
" nmols = moltype['nmols']\n",
" mass_mol = sum((atom['mass'] for atom in moltype['atoms']))\n",
" return oconst.k_gro * (5 / 2 + np.log((2 * np.pi * mass_mol * oconst.k_gro * temperature / oconst.h_gro**2)**(3/2) \n",
" * volume / nmols))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### create dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# put data in DataFrame\n",
"index = pd.MultiIndex.from_tuples([(sys['type']['name'], sys['force-field']['name'], sample)\n",
" for sys in sys_gen_2pt()\n",
" for sample in range(param_dos['n_samples'])])\n",
"columns = pd.MultiIndex.from_product((range(3), ('S', 'S_trn', 'S_rot')))\n",
"df_2pt = pd.DataFrame(index=index, columns=columns, dtype=float)\n",
"df_2pt.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### calculate entropies"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def calc_2pt():\n",
" for system in sys_gen_2pt():\n",
" print(f\"system {system['name']}\")\n",
" for sample in range(param_dos['n_samples']):\n",
" moltypes_with_dos = moltypes_with_dos_dict[system['name']]\n",
" with WorkingDir(system['name']):\n",
" volume = calc_volume_npt('npt-prod-vel/ener.edr')\n",
" moltypes_2pt, entropy = calculate_entropy_2PTQ(moltypes_with_dos, volume, system['temperature'], sample, norm_dos='temp', pascal_2010_var=True)\n",
" #print(f\" total entropy: {entropy*1000:.1f} J/mol/K\")\n",
" for m, moltype in enumerate(moltypes_2pt):\n",
" entropy_id = calc_s_trn_ideal_gas(moltype, system['temperature'], volume)\n",
" \"\"\"\n",
" print(f\" moltype: {moltype['name']}\")\n",
" print(f\" ideal gas entropy: {entropy_id*1000:.1f} J/mol/K\")\n",
" print(f\" entropy: {moltype['entropy']*1000:.1f} J/mol/K\")\n",
" print(\" {} {:.5f} {:.5f} {:.5f} {:.3f} {:.3f} {:.3f} \".format(sample,\n",
" *np.array([moltype['entropy_trn'], moltype['entropy_rot'], moltype['entropy_vib']])*1000,\n",
" moltype['nDoF_trn'], moltype['nDoF_rot'], moltype['nDoF_vib']))\n",
" \"\"\"\n",
" df_2pt.at[(system['type']['name'], system['force-field']['name'], sample), (m, 'S')] = moltype['entropy']\n",
" df_2pt.at[(system['type']['name'], system['force-field']['name'], sample), (m, 'S_trn')] = moltype['entropy_trn']\n",
" df_2pt.at[(system['type']['name'], system['force-field']['name'], sample), (m, 'S_rot')] = moltype['entropy_rot']\n",
"\n",
" moltypes_with_dos_dict[system['name']] = moltypes_2pt\n",
" \n",
"calc_2pt()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"df_2pt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"df_2pt.mean(axis=0, level=(0, 1)) * 1000"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"df_2pt.std(axis=0, level=(0, 1)) * 1000"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"diffs = (\n",
" #('netz-co0.9tc', 'netz-co0.9'),\n",
" ('netz-co0.9tc', 'iff-altern5-netz-co0.9-nopc'),\n",
" ('eccr1-co1.2', 'iff-altern5-eccr1-co1.2-nopc'),\n",
" #('netz-co0.9', 'iff-altern5-netz-co0.9-nopc'),\n",
")\n",
"df_2pt_diff = pd.DataFrame(index=pd.MultiIndex.from_product((system_types_2pt.keys(), diffs)), columns=df_2pt.columns)\n",
"df_2pt_diff_std = pd.DataFrame(index=pd.MultiIndex.from_product((system_types_2pt.keys(), diffs)), columns=df_2pt.columns)\n",
"df_2pt_diff.sort_index(axis=1, inplace=True)\n",
"df_2pt_diff_std.sort_index(axis=1, inplace=True)\n",
"df_2pt_diff.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for systype in system_types_2pt.keys():\n",
" #print(systype)\n",
" for diff in diffs:\n",
" #print(diff)\n",
" df_2pt_diff.loc[(systype, diff), (slice(None), slice(None))] = (\n",
" -df_2pt.groupby(axis=0, level=(0, 1)).mean().loc[(systype, diff[0]), (slice(None), slice(None))]\n",
" +df_2pt.groupby(axis=0, level=(0, 1)).mean().loc[(systype, diff[1]), (slice(None), slice(None))]\n",
" )\n",
" df_2pt_diff_std.loc[(systype, diff), (slice(None), slice(None))] = (\n",
" +df_2pt.groupby(axis=0, level=(0, 1)).std().loc[(systype, diff[0]), (slice(None), slice(None))]\n",
" +df_2pt.groupby(axis=0, level=(0, 1)).std().loc[(systype, diff[1]), (slice(None), slice(None))]\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_2pt_diff * 1000"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_2pt_diff_std * 1000"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### compare entropies"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"(\n",
" 100*df_2pt_diff.loc[(slice(None), slice(None)), (0, slice('S', 'S_trn'))][0]\n",
" +df_2pt_diff.loc[(slice(None), slice(None)), (1, slice('S', 'S_trn'))][1]\n",
" +df_2pt_diff.loc[(slice(None), slice(None)), (2, slice('S', 'S_trn'))][2]\n",
")* 1000"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"(\n",
" 100*df_2pt_diff_std.loc[(slice(None), slice(None)), (0, slice('S', 'S_trn'))][0]\n",
" +df_2pt_diff_std.loc[(slice(None), slice(None)), (1, slice('S', 'S_trn'))][1]\n",
" +df_2pt_diff_std.loc[(slice(None), slice(None)), (2, slice('S', 'S_trn'))][2]\n",
")* 1000"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"stn = 'water-kcl'\n",
"diff = ('netz-co0.9tc', 'iff-altern5-netz-co0.9-nopc')\n",
"(\n",
" +df_2pt_diff.at[(stn, diff), (0, 'S')] * 100\n",
" +df_2pt_diff.at[(stn, diff), (1, 'S')]\n",
" +df_2pt_diff.at[(stn, diff), (2, 'S')]\n",
") * 1000"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### plot entropies"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# from horinek 2009 who got it from marcus 1997\n",
"exp_solv_entropies = {\n",
" 'water-licl': (-0.217, 0.004),\n",
" 'water-nacl': (-0.186, 0.004),\n",
" 'water-kcl': (-0.149, 0.004),\n",
" # directly from marcus Ca + 2 x Cl\n",
" 'water-cacl2_': (-0.252 + 2 * -0.075, 0.004),\n",
"}\n",
"# from horinek 2009\n",
"lit_solv_entropies = {\n",
" ('water-licl', 'netz-co0.9tc'): (-0.268, 0.012),\n",
" ('water-licl', 'netz-co0.9'): (-0.268, 0.012), # tc assumed to have small influence\n",
" ('water-nacl', 'netz-co0.9tc'): (-0.217, 0.012),\n",
" ('water-nacl', 'netz-co0.9'): (-0.217, 0.012), # tc assumed to have small influence\n",
" ('water-kcl', 'netz-co0.9tc'): (-0.182, 0.012),\n",
" ('water-kcl', 'netz-co0.9'): (-0.182, 0.012), # tc assumed to have small influence\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def plot_entropies(show_2pt=True, show_ti=True, show_ecc=True, include_netz_error=True, base_on_netz_lit_value=False):\n",
" \n",
" mpl_rc_local = {\n",
" 'hatch.linewidth': 1.2,\n",
" 'legend.labelspacing': 0.1,\n",
" }\n",
" \n",
" terms_to_show = [\n",
" {'type': 'experimental'},\n",
" {'type': 'literature', 'ff': 'netz-co0.9tc'},\n",
" ]\n",
" if show_2pt:\n",
" terms_to_show.append({'type': 'difference-2pt', 'ff-ref': 'netz-co0.9tc', 'ff': 'iff-altern5-netz-co0.9-nopc'})\n",
" if show_ti:\n",
" terms_to_show.append({'type': 'difference-ti', 'path': (\n",
" {'ff-ref': 'netz-co0.9tc', 'ff': 'iff-altern5-netz-co0.9-nopc', 'ti-name': \"netz-to-iff-netz\"},\n",
" )})\n",
" if show_ecc:\n",
" terms_to_show.append({'type': 'difference-ti', 'path': (\n",
" {'ff-ref': 'netz-co0.9', 'ff': 'eccr1-co0.9', 'ti-name': \"netz-to-eccr1\"},\n",
" )})\n",
" terms_to_show.append({'type': 'difference-ti', 'path': (\n",
" {'ff-ref': 'netz-co0.9', 'ff': 'eccr1-co0.9', 'ti-name': \"netz-to-eccr1\"},\n",
" {'ff-ref': 'eccr1-co1.2', 'ff': 'iff-altern5-eccr1-co1.2-nopc', 'ti-name': \"eccr1-to-iff-eccr1\"},\n",
" )})\n",
" #'netz-to-eccr1': {\n",
" #'eccr1-to-iff-eccr1': {\n",
" \n",
" with plt.rc_context({**mpl_rc_global, **mpl_rc_local}):\n",
"\n",
" system_types_to_show = {stn: st for stn, st in system_types_2pt.items() if stn not in ('water-pure', 'water-cacl2_')}\n",
" #system_types_to_show = {stn: st for stn, st in system_types_2pt.items() if stn not in ('water-pure',)}\n",
" fig, ax = plt.subplots(figsize=(3, 3), constrained_layout=True, dpi=200)\n",
" x_base = np.arange(len(system_types_to_show))\n",
" for s, (stn, st) in enumerate(system_types_to_show.items()):\n",
" print(stn)\n",
" for t, term_to_show in enumerate(terms_to_show):\n",
" # plot experimental\n",
" width = 0.9 / len(terms_to_show) - 0.02\n",
" x = x_base[s] + np.linspace(-0.45+width/2, 0.45-width/2, num=len(terms_to_show))[t]\n",
" if term_to_show['type'] == 'experimental':\n",
" label = 'exp.' if s == 0 else None\n",
" ax.bar(x=x, height=exp_solv_entropies[stn][0] * 1000, yerr=exp_solv_entropies[stn][1] * 1000, width=width, color='#444444', label=label)\n",
" # plot literature\n",
" elif term_to_show['type'] == 'literature':\n",
" label = ff_short_names[term_to_show['ff']] if s == 0 else None\n",
" ax.bar(x=x,\n",
" height=lit_solv_entropies[(stn, term_to_show['ff'])][0] * 1000,\n",
" yerr=lit_solv_entropies[(stn, term_to_show['ff'])][1] * 1000,\n",
" width=width, color=ff_colors[term_to_show['ff']], label=label)\n",
" # plot 2pt difference\n",
" elif term_to_show['type'] == 'difference-2pt':\n",
" label = ff_short_names[term_to_show['ff']] + ' 2PT' if s == 0 else None\n",
" diff = (term_to_show['ff-ref'], term_to_show['ff'])\n",
" y = lit_solv_entropies[(stn, term_to_show['ff-ref'])][0] + (\n",
" +df_2pt_diff.at[(stn, diff), (0, 'S')] * 100\n",
" +df_2pt_diff.at[(stn, diff), (1, 'S')]\n",
" +df_2pt_diff.at[(stn, diff), (2, 'S')]\n",
" )\n",
" y_err = int(include_netz_error) * lit_solv_entropies[(stn, term_to_show['ff-ref'])][1] + (\n",
" +df_2pt_diff_std.at[(stn, diff), (0, 'S')] * 100\n",
" +df_2pt_diff_std.at[(stn, diff), (1, 'S')]\n",
" +df_2pt_diff_std.at[(stn, diff), (2, 'S')]\n",
" )\n",
" if show_2pt:\n",
" ax.bar(x, y * 1000, yerr=y_err * 1000, width=width, color=ff_colors[term_to_show['ff']], label=label,\n",
" hatch='///', alpha=0.999)\n",
"\n",
" # plot ti difference\n",
" elif term_to_show['type'] == 'difference-ti':\n",
" # netz iff\n",
" label = ff_short_names[term_to_show['path'][-1]['ff']] + ' TI' if s == 0 else None\n",
" assert st['n_cation_anion'] == (1, 1)\n",
" if base_on_netz_lit_value:\n",
" y = lit_solv_entropies[(stn, term_to_show['path'][0]['ff-ref'])][0]\n",
" y_err = int(include_netz_error) * lit_solv_entropies[(stn, term_to_show['path'][0]['ff-ref'])][1]\n",
" else:\n",
" step = term_to_show['path'][0] # Right?!\n",
" y = df_ti2.at[(stn[:-2], step['ti-name'], '2-vdW-charge'), 'ΔU-mean']\n",
" y_err = 0\n",
" for step in term_to_show['path']:\n",
" y += (\n",
" # cation \n",
" +df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'ΔU-mean']\n",
" -df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'ΔG-mean']\n",
" +df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'pΔV-mean']\n",
" ) / 300 + (\n",
" # anion \n",
" +df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'ΔU-mean']\n",
" -df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'ΔG-mean']\n",
" +df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'pΔV-mean']\n",
" ) / 300\n",
" y_err += (\n",
" # cation \n",
" +df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'ΔU-std']\n",
" +df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'ΔG-std']\n",
" +df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'pΔV-std']\n",
" ) / 300 + (\n",
" # anion \n",
" +df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'ΔU-std']\n",
" +df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'ΔG-std']\n",
" +df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'pΔV-std']\n",
" ) / 300\n",
" ax.bar(x, y * 1000, yerr=y_err * 1000, width=width, color=ff_colors[term_to_show['path'][-1]['ff']], label=label, hatch='')\n",
" else:\n",
" print(\".. unknown term ..\", term_to_show)\n",
"\n",
" ax.set_xlim(x_base[0] - 0.6, x_base[-1] + 0.6)\n",
" ax.set_xticks(x_base)\n",
" ax.set_xticklabels((sys_type_short_names[stn] for stn in system_types_to_show.keys()))\n",
" #ax.set_ylabel(r'$\\Delta S_\\mathrm{cat.} + \\Delta S_\\mathrm{Cl¯}$ in kJ/mol')\n",
" ax.set_ylabel(r'$\\Delta S_\\mathrm{solv}$ in J/mol/K')\n",
" ax.xaxis.set_ticks_position('top')\n",
" ax.xaxis.set_ticks_position('none') \n",
" ax.legend(loc='upper center', frameon=True)\n",
" #fig.savefig('../figures/solvation-entropies.pdf')\n",
" plt.show()\n",
"\n",
"plot_entropies(True, True, show_ecc=False, include_netz_error=True, base_on_netz_lit_value=True)\n",
"#plot_entropies(True, True, True, include_netz_error=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!cp -a ../figures/solvation-entropies.pdf ~/research/output/ion-shortrange-paper/figures/"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### toc plot entropies"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"def plot_entropies_toc():\n",
" \n",
" mpl_rc_local = {\n",
" 'hatch.linewidth': 1.2,\n",
" 'legend.labelspacing': 0.1,\n",
" }\n",
" terms_to_show = []\n",
" terms_to_show.append({'type': 'difference-ti', 'path': (\n",
" {'ff-ref': 'netz-co0.9tc', 'ff': 'iff-altern5-netz-co0.9-nopc', 'ti-name': \"netz-to-iff-netz\"},\n",
" )})\n",
" terms_to_show.append({'type': 'literature', 'ff': 'netz-co0.9tc'})\n",
" terms_to_show.append({'type': 'experimental'})\n",
" \n",
" with plt.rc_context({**mpl_rc_global, **mpl_rc_local}):\n",
"\n",
" system_types_to_show = {stn: st for stn, st in system_types_2pt.items() if stn in ('water-licl',)}\n",
" fig, ax = plt.subplots(figsize=(1.0, 1.8), constrained_layout=True, dpi=200)\n",
" x_base = np.arange(len(system_types_to_show))\n",
" for s, (stn, st) in enumerate(system_types_to_show.items()):\n",
" for t, term_to_show in enumerate(terms_to_show):\n",
" # plot experimental\n",
" width = 0.75 / len(terms_to_show) - 0.02\n",
" x = x_base[s] + np.linspace(-0.45+width/2, 0.45-width/2, num=len(terms_to_show))[t]\n",
" if term_to_show['type'] == 'experimental':\n",
" label = 'exp.' if s == 0 else None\n",
" #ax.barh(y=x, width=exp_solv_entropies[stn][0] * 1000, xerr=exp_solv_entropies[stn][1] * 1000, height=width, color='#444444', label=label)\n",
" ax.axvline(x=exp_solv_entropies[stn][0] * 1000, color='k', linestyle=':', linewidth=1.0)\n",
" # plot literature\n",
" elif term_to_show['type'] == 'literature':\n",
" label = ff_short_names[term_to_show['ff']] if s == 0 else None\n",
" ax.barh(y=x,\n",
" width=lit_solv_entropies[(stn, term_to_show['ff'])][0] * 1000,\n",
" xerr=lit_solv_entropies[(stn, term_to_show['ff'])][1] * 1000,\n",
" height=width, color=ff_colors[term_to_show['ff']], label=label)\n",
" # plot 2pt difference\n",
" elif term_to_show['type'] == 'difference-2pt':\n",
" label = ff_short_names[term_to_show['ff']] + ' 2PT' if s == 0 else None\n",
" diff = (term_to_show['ff-ref'], term_to_show['ff'])\n",
" y = lit_solv_entropies[(stn, term_to_show['ff-ref'])][0] + (\n",
" +df_2pt_diff.at[(stn, diff), (0, 'S')] * 100\n",
" +df_2pt_diff.at[(stn, diff), (1, 'S')]\n",
" +df_2pt_diff.at[(stn, diff), (2, 'S')]\n",
" )\n",
" y_err = int(include_netz_error) * lit_solv_entropies[(stn, term_to_show['ff-ref'])][1] + (\n",
" +df_2pt_diff_std.at[(stn, diff), (0, 'S')] * 100\n",
" +df_2pt_diff_std.at[(stn, diff), (1, 'S')]\n",
" +df_2pt_diff_std.at[(stn, diff), (2, 'S')]\n",
" )\n",
" if show_2pt:\n",
" ax.barh(y=x, width=y * 1000, xerr=y_err * 1000, height=width, color=ff_colors[term_to_show['ff']], label=label,\n",
" hatch='///', alpha=0.999)\n",
"\n",
" # plot ti difference\n",
" elif term_to_show['type'] == 'difference-ti':\n",
" # netz iff\n",
" label = ff_short_names[term_to_show['path'][-1]['ff']] + ' TI' if s == 0 else None\n",
" assert st['n_cation_anion'] == (1, 1)\n",
" y = lit_solv_entropies[(stn, term_to_show['path'][0]['ff-ref'])][0]\n",
" y_err = lit_solv_entropies[(stn, term_to_show['path'][0]['ff-ref'])][1]\n",
" for step in term_to_show['path']:\n",
" y += (\n",
" # cation \n",
" +df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'ΔU-mean']\n",
" -df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'ΔG-mean']\n",
" +df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'pΔV-mean']\n",
" ) / 300 + (\n",
" # anion \n",
" +df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'ΔU-mean']\n",
" -df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'ΔG-mean']\n",
" +df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'pΔV-mean']\n",
" ) / 300\n",
" y_err += (\n",
" # cation \n",
" +df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'ΔU-std']\n",
" +df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'ΔG-std']\n",
" +df_ti2.at[(stn[:-2], step['ti-name'], '1-linear-direct'), 'pΔV-std']\n",
" ) / 300 + (\n",
" # anion \n",
" +df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'ΔU-std']\n",
" +df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'ΔG-std']\n",
" +df_ti2.at[('water-cl', step['ti-name'], '1-linear-direct'), 'pΔV-std']\n",
" ) / 300\n",
" ax.barh(y=x, width=y * 1000, xerr=y_err * 1000, height=width, color=ff_colors[term_to_show['path'][-1]['ff']], label=label, hatch='')\n",
" else:\n",
" print(\".. unknown term ..\", term_to_show)\n",
" \n",
" # arrow heads\n",
" ax.plot(0, 0, \"