{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "# Custom Models in pycalphad: Viscosity"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "## Viscosity Model Background\n",
    "\n",
    "We are going to take a CALPHAD-based property model from the literature and use it to predict the viscosity of Al-Cu-Zr liquids.\n",
    "\n",
    "For a binary alloy liquid under small undercooling, Gąsior suggested an entropy model of the form\n",
    "$$\\eta = (\\sum_i x_i \\eta_i ) (1 - 2\\frac{S_{ex}}{R})$$\n",
    "\n",
    "where $\\eta_i$ is the viscosity of the element $i$, $x_i$ is the mole fraction, $S_{ex}$ is the excess entropy, and $R$ is the gas constant.\n",
    "\n",
    "For more details on this model, see \n",
    "\n",
    "1. M.E. Trybula, T. Gancarz, W. Gąsior, *Density, surface tension and viscosity of liquid binary Al-Zn and ternary Al-Li-Zn alloys*, Fluid Phase Equilibria 421 (2016) 39-48, [doi:10.1016/j.fluid.2016.03.013](http://dx.doi.org/10.1016/j.fluid.2016.03.013).\n",
    "\n",
    "2. Władysław Gąsior, *Viscosity modeling of binary alloys: Comparative studies*, Calphad 44 (2014) 119-128, [doi:10.1016/j.calphad.2013.10.007](http://dx.doi.org/10.1016/j.calphad.2013.10.007).\n",
    "\n",
    "3. Chenyang Zhou, Cuiping Guo, Changrong Li, Zhenmin Du, *Thermodynamic assessment of the phase equilibria and prediction of glass-forming ability of the Al–Cu–Zr system*, Journal of Non-Crystalline Solids 461 (2017) 47-60, [doi:10.1016/j.jnoncrysol.2016.09.031](https://doi.org/10.1016/j.jnoncrysol.2016.09.031)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "deletable": true,
    "editable": true,
    "execution": {
     "iopub.execute_input": "2022-02-19T19:24:46.402127Z",
     "iopub.status.busy": "2022-02-19T19:24:46.402127Z",
     "iopub.status.idle": "2022-02-19T19:24:49.201546Z",
     "shell.execute_reply": "2022-02-19T19:24:49.201546Z"
    }
   },
   "outputs": [],
   "source": [
    "from pycalphad import Database"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "## TDB Parameters\n",
    "We can calculate the excess entropy of the liquid using the Al-Cu-Zr thermodynamic database from Zhou et al.\n",
    "\n",
    "We add three new parameters to describe the viscosity (in Pa-s) of the pure elements Al, Cu, and Zr:\n",
    "```\n",
    "   $ Viscosity test parameters\n",
    "   PARAMETER ETA(LIQUID,AL;0) 2.98150E+02  +0.000281*EXP(12300/(8.3145*T));   6.00000E+03   \n",
    "  N REF:0 !\n",
    "   PARAMETER ETA(LIQUID,CU;0) 2.98150E+02  +0.000657*EXP(21500/(8.3145*T));   6.00000E+03   \n",
    "  N REF:0 !\n",
    "  PARAMETER ETA(LIQUID,ZR;0) 2.98150E+02  +4.74E-3 - 4.97E-6*(T-2128) ;   6.00000E+03   \n",
    "    N REF:0 !\n",
    "```\n",
    "\n",
    "Great! However, if we try to load the database now, we will get an error. This is because `ETA` parameters are not supported by default in pycalphad, so we need to tell pycalphad's TDB parser that \"ETA\" should be on the list of supported parameter types."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "execution": {
     "iopub.execute_input": "2022-02-19T19:24:49.210107Z",
     "iopub.status.busy": "2022-02-19T19:24:49.201546Z",
     "iopub.status.idle": "2022-02-19T19:24:49.261598Z",
     "shell.execute_reply": "2022-02-19T19:24:49.261598Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Failed while parsing:     PARAMETER ETA(LIQUID,AL;0) 2.98150E+02 +0.000281*EXP(12300/(8.3145*T)); 6.00000E+03 N REF:0 \n",
      "Tokens: None\n",
      "Expected {{'ELEMENT' W:(-/A-Za-z){1,2} W:(()-/-:A-Z_a-z) Re:('[-+]?([0-9]+\\.(?!([0-9]|[eE])))|([0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)') Re:('[-+]?([0-9]+\\.(?!([0-9]|[eE])))|([0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)') Re:('[-+]?([0-9]+\\.(?!([0-9]|[eE])))|([0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)') LineEnd} | {'SPECIES' W:(*+--9A-Z_a-z) [Suppress:('%')] Group:({{W:(A-Za-z){1,2} [Re:('[-+]?([0-9]+\\.(?!([0-9]|[eE])))|([0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)')]}}...) [Suppress:('/') W:(+-0-9)] LineEnd} | {'TYPE_DEFINITION' Suppress:(<SP><TAB><CR><LF>) !W:( !) SkipTo:(LineEnd)} | {'FUNCTION' W:(()-/-:A-Z_a-z) {{Re:('[-+]?([0-9]+\\.(?!([0-9]|[eE])))|([0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)') | [',']...} {{SkipTo:(';') Suppress:(';') [Suppress:(',')]... [Re:('[-+]?([0-9]+\\.(?!([0-9]|[eE])))|([0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)')] Suppress:([(Yy)])}}... Suppress:([(Nn)])} [Suppress:(W:(-0-:A-Z_a-z))] LineEnd} | {'ASSESSED_SYSTEMS' SkipTo:(LineEnd)} | {'DEFINE_SYSTEM_DEFAULT' SkipTo:(LineEnd)} | {'DEFAULT_COMMAND' SkipTo:(LineEnd)} | {'DATABASE_INFO' SkipTo:(LineEnd)} | {'VERSION_DATE' SkipTo:(LineEnd)} | {'REFERENCE_FILE' SkipTo:(LineEnd)} | {'ADD_REFERENCES' SkipTo:(LineEnd)} | {'LIST_OF_REFERENCES' SkipTo:(LineEnd)} | {'TEMPERATURE_LIMITS' SkipTo:(LineEnd)} | {'PHASE' W:(()-/-:A-Z_a-z) Suppress:(<SP><TAB><CR><LF>) !W:( !) Suppress:(<SP><TAB><CR><LF>) Suppress:(W:(0-9)) Group:({Re:('[-+]?([0-9]+\\.(?!([0-9]|[eE])))|([0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)')}...) Suppress:(SkipTo:(LineEnd))} | {'CONSTITUENT' W:(()-/-:A-Z_a-z) Suppress:(<SP><TAB><CR><LF>) Suppress:(':') Group:(Group:({{[Suppress:(',')] W:(*+--9A-Z_a-z) [Suppress:('%')]}}...) [: Group:({{[Suppress:(',')] W:(*+--9A-Z_a-z) [Suppress:('%')]}}...)]...) Suppress:(':') LineEnd} | {'PARAMETER' {'BMAGN' | 'DF' | 'DQ' | 'ELRS' | 'G' | 'GD' | 'L' | 'MF' | 'MQ' | 'NT' | 'SIGM' | 'TC' | 'THCD' | 'THETA' | 'V0' | 'VA' | 'VC' | 'VISC' | 'VK' | 'VS' | 'XI'} Suppress:('(') W:(()-/-:A-Z_a-z) [Suppress:('&') W:(-/A-Za-z){1,2}] Suppress:(',') Group:(Group:({{[Suppress:(',')] W:(*+--9A-Z_a-z) [Suppress:('%')]}}...) [: Group:({{[Suppress:(',')] W:(*+--9A-Z_a-z) [Suppress:('%')]}}...)]...) [Suppress:(';') W:(0-9)] Suppress:(')') {{Re:('[-+]?([0-9]+\\.(?!([0-9]|[eE])))|([0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)') | [',']...} {{SkipTo:(';') Suppress:(';') [Suppress:(',')]... [Re:('[-+]?([0-9]+\\.(?!([0-9]|[eE])))|([0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)')] Suppress:([(Yy)])}}... Suppress:([(Nn)])} [Suppress:(W:(-0-:A-Z_a-z))] LineEnd} | {'ZEROVOLUME_SPECIES' SkipTo:(LineEnd)} | {'DIFFUSION' SkipTo:(LineEnd)}}, found '('  (at char 17), (line:1, col:18)\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    dbf = Database('alcuzr-viscosity.tdb')\n",
    "except Exception as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Adding the `ETA` parameter to the TDB parser "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "execution": {
     "iopub.execute_input": "2022-02-19T19:24:49.261598Z",
     "iopub.status.busy": "2022-02-19T19:24:49.261598Z",
     "iopub.status.idle": "2022-02-19T19:24:49.281502Z",
     "shell.execute_reply": "2022-02-19T19:24:49.281502Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "import pycalphad.io.tdb_keywords\n",
    "pycalphad.io.tdb_keywords.TDB_PARAM_TYPES.append('ETA')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Now the database will load:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "execution": {
     "iopub.execute_input": "2022-02-19T19:24:49.281502Z",
     "iopub.status.busy": "2022-02-19T19:24:49.281502Z",
     "iopub.status.idle": "2022-02-19T19:24:49.341526Z",
     "shell.execute_reply": "2022-02-19T19:24:49.341526Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "dbf = Database('alcuzr-viscosity.tdb')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "## Writing the Custom Viscosity Model\n",
    "\n",
    "Now that we have our `ETA` parameters in the database, we need to write a `Model` class to tell pycalphad how to compute viscosity. All custom models are subclasses of the pycalphad `Model` class.\n",
    "\n",
    "When the `ViscosityModel` is constructed, the `build_phase` method is run and we need to construct the viscosity model after doing all the other initialization using a new method `build_viscosity`. The implementation of `build_viscosity` needs to do four things:\n",
    "1. Query the Database for all the `ETA` parameters\n",
    "2. Compute their weighted sum\n",
    "3. Compute the excess entropy of the liquid\n",
    "4. Plug all the values into the Gąsior equation and return the result\n",
    "\n",
    "Since the `build_phase` method sets the attribute `viscosity` to the `ViscosityModel`, we can access the property using `viscosity` as the output in pycalphad caluclations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "deletable": true,
    "editable": true,
    "execution": {
     "iopub.execute_input": "2022-02-19T19:24:49.351408Z",
     "iopub.status.busy": "2022-02-19T19:24:49.351408Z",
     "iopub.status.idle": "2022-02-19T19:24:49.361677Z",
     "shell.execute_reply": "2022-02-19T19:24:49.361677Z"
    }
   },
   "outputs": [],
   "source": [
    "from tinydb import where\n",
    "from pycalphad import Model, variables as v\n",
    "\n",
    "class ViscosityModel(Model):\n",
    "    def build_phase(self, dbe):\n",
    "        super(ViscosityModel, self).build_phase(dbe)\n",
    "        self.viscosity = self.build_viscosity(dbe)\n",
    "\n",
    "    def build_viscosity(self, dbe):\n",
    "        if self.phase_name != 'LIQUID':\n",
    "            raise ValueError('Viscosity is only defined for LIQUID phase')\n",
    "        phase = dbe.phases[self.phase_name]\n",
    "        param_search = dbe.search\n",
    "        # STEP 1\n",
    "        eta_param_query = (\n",
    "            (where('phase_name') == phase.name) & \\\n",
    "            (where('parameter_type') == 'ETA') & \\\n",
    "            (where('constituent_array').test(self._array_validity))\n",
    "        )\n",
    "        # STEP 2\n",
    "        eta = self.redlich_kister_sum(phase, param_search, eta_param_query)\n",
    "        # STEP 3\n",
    "        excess_energy = self.GM - self.models['ref'] - self.models['idmix']\n",
    "        #liquid_mod = Model(dbe, self.components, self.phase_name)\n",
    "        ## we only want the excess contributions to the entropy\n",
    "        #del liquid_mod.models['ref']\n",
    "        #del liquid_mod.models['idmix']\n",
    "        excess_entropy = -excess_energy.diff(v.T)\n",
    "        ks = 2\n",
    "        # STEP 4\n",
    "        result = eta * (1 - ks * excess_entropy / v.R)\n",
    "        self.eta = eta\n",
    "        return result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "## Performing Calculations\n",
    "\n",
    "Now we can create an instance of `ViscosityModel` for the liquid phase using the `Database` object we created earlier. We can verify this model has a `viscosity` attribute containing a symbolic expression for the viscosity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "execution": {
     "iopub.execute_input": "2022-02-19T19:24:49.372613Z",
     "iopub.status.busy": "2022-02-19T19:24:49.372613Z",
     "iopub.status.idle": "2022-02-19T19:24:49.381328Z",
     "shell.execute_reply": "2022-02-19T19:24:49.381328Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(0.000657*LIQUID0CU*2.71828182845905**(2585.84400745685*T**(-1.0)) + LIQUID0ZR*(0.00474 - 4.97e-06*(-2128.0 + T)))*(1 + 0.240543628600637*(LIQUID0CU*LIQUID0ZR*(392.8485 - 51.3121*log(T)) + (LIQUID0CU - LIQUID0ZR)*LIQUID0CU*LIQUID0ZR*(75.3798 - 9.6125*log(T)) + (LIQUID0CU - LIQUID0ZR)**2*LIQUID0CU*LIQUID0ZR*(-270.5305 + 36.8512*log(T)) + (LIQUID0CU - LIQUID0ZR)**3*LIQUID0CU*LIQUID0ZR*(105.895 - 13.6488*log(T)))/(LIQUID0CU + LIQUID0ZR))\n"
     ]
    }
   ],
   "source": [
    "mod = ViscosityModel(dbf, ['CU', 'ZR'], 'LIQUID')\n",
    "print(mod.viscosity)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Finally we calculate and plot the viscosity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "execution": {
     "iopub.execute_input": "2022-02-19T19:24:49.431215Z",
     "iopub.status.busy": "2022-02-19T19:24:49.411882Z",
     "iopub.status.idle": "2022-02-19T19:24:49.657823Z",
     "shell.execute_reply": "2022-02-19T19:24:49.657823Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 29932 characters]",
      "text/plain": [
       "<Figure size 600x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "from pycalphad import calculate\n",
    "\n",
    "mod = ViscosityModel(dbf, ['CU', 'ZR'], 'LIQUID')\n",
    "\n",
    "temp = 2100\n",
    "# NOTICE: we need to tell pycalphad about our model for this phase\n",
    "models = {'LIQUID': mod}\n",
    "res = calculate(dbf, ['CU', 'ZR'], 'LIQUID', P=101325, T=temp, model=models, output='viscosity') \n",
    "\n",
    "fig = plt.figure(figsize=(6,6))\n",
    "ax = fig.gca()\n",
    "ax.scatter(res.X.sel(component='ZR'), 1000 * res.viscosity.values)\n",
    "ax.set_xlabel('X(ZR)')\n",
    "ax.set_ylabel('Viscosity (mPa-s)')\n",
    "ax.set_xlim((0,1))\n",
    "ax.set_title('Viscosity at {}K'.format(temp));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "We repeat the calculation for Al-Cu."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "execution": {
     "iopub.execute_input": "2022-02-19T19:24:49.701334Z",
     "iopub.status.busy": "2022-02-19T19:24:49.701334Z",
     "iopub.status.idle": "2022-02-19T19:24:49.911692Z",
     "shell.execute_reply": "2022-02-19T19:24:49.911692Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 28092 characters]",
      "text/plain": [
       "<Figure size 600x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "from pycalphad import calculate\n",
    "\n",
    "temp = 1300\n",
    "models = {'LIQUID': ViscosityModel}  # we can also use Model class\n",
    "res = calculate(dbf, ['CU', 'AL'], 'LIQUID', P=101325, T=temp, model=models, output='viscosity')\n",
    "\n",
    "fig = plt.figure(figsize=(6,6))\n",
    "ax = fig.gca()\n",
    "ax.scatter(res.X.sel(component='CU'), 1000 * res.viscosity.values)\n",
    "ax.set_xlabel('X(CU)')\n",
    "ax.set_ylabel('Viscosity (mPa-s)')\n",
    "ax.set_xlim((0,1))\n",
    "ax.set_title('Viscosity at {}K'.format(temp));"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
