{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Jscatter : Build Models\n",
    "\n",
    "This Notebook aims to demonstrate how ot build your own models for fitting.\n",
    "\n",
    "### Content\n",
    "- How to build simple models\n",
    "- How to build a complex model\n",
    "\n",
    "![Jscatter](https://jscatter.readthedocs.io/en/latest/_images/Jscatter.jpeg \"Jscatter\")\n",
    "\n",
    "#### Prerequisite"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys,site\n",
    "!{sys.executable} -m pip install jscatter --user          \n",
    "sys.path.append(site.USER_BASE)\n",
    "%matplotlib notebook\n",
    "\n",
    "import jscatter as js\n",
    "import numpy as np\n",
    "js.usempl(True)   # force matplotlib, not needed in Linux"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## How to build simple models\n",
    "... which are actually not so simple....\n",
    "\n",
    "Models get as input a number of parameters with one as the X values and \n",
    "should return only Y values or a dataArray with `.Y`. \n",
    "\n",
    "Except the X values all other parameters should be single valued (no list).\n",
    "Use numpy ufunctions as shown below."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Build models in one line using lambda\n",
    "Directly calc the values and return only Y values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "diffusion = lambda A,D,t,wavevector,elastic=0: A*np.exp(-wavevector**2*D*t)+elastic"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Use a model from the libraries\n",
    "Here Teubner-Strey model is used adding background and power law.\n",
    "\n",
    "This model returns as above only Y values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tbpower = lambda q,B,xi,dd,A,beta,bgr: js.ff.teubnerStrey(q=q, xi=xi, d=dd).Y*B+A*q**beta+bgr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The same as above in a function definition\n",
    "Function definitions are the prefered way as they are more clean, e.g. they have a defined function name."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def diffusion2(A, D, t, elastic, wavevector=0):\n",
    "    Y = A * np.exp(-wavevector ** 2 * D * t) + elastic\n",
    "    return Y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Returning dataArray allows additional attributes to be included in the result\n",
    "This returns a dataArray with X, Y values and attributes.\n",
    "The attributes as e.g. the chosen parameters for the calculation are documented in this way and will not be forget."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def diffusion3(A, D, t, wavevector, elastic=0):\n",
    "    # calc values\n",
    "    Y = A * np.exp(-wavevector ** 2 * D * t) + elastic\n",
    "    # create dataArray from calculated values\n",
    "    result = js.dA(np.c_[t, Y].T)\n",
    "    # add attributes\n",
    "    result.diffusioncoefficient = D\n",
    "    result.wavevector = wavevector\n",
    "    result.columnname = 'time;Iqt'\n",
    "    return result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def tbpower2(q, B, xi, dd, A, beta, bgr):\n",
    "    \"\"\"Model Teubner Strey  + power law and background. \n",
    "    This string will be later a docstring and describe the model\"\"\"\n",
    "    # save different contributions for later analysis\n",
    "    tb = js.ff.teubnerStrey(q=q, xi=xi, d=dd)  # this returns a dataArray\n",
    "    pl = A * q ** beta                         # power law\n",
    "    tb = tb.addZeroColumns(2)\n",
    "    tb[-2] = pl                                # save power law in second last column\n",
    "    tb[-1] = tb.Y                              # save Teubner-Strey in last column\n",
    "    # put full model to Y values (usually tb[1])\n",
    "    tb.Y = B * tb.Y + pl + bgr  \n",
    "    # save the additional parameters ; xi and d already included in teubnerStrey\n",
    "    tb.A = A\n",
    "    tb.bgr = bgr\n",
    "    tb.beta = beta\n",
    "    # name the columns \n",
    "    tb.columnname = 'q;Iq,IqTb,Iqpower'\n",
    "    # set no errors for Y \n",
    "    tb.setColumnIndices(iey=None)\n",
    "    return tb\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## How to build a complex model\n",
    "\n",
    "Build a complex model of different components.\n",
    "\n",
    "The docstring (in tripple \"\") can be used to generate the documentation. The used style corresponds to the numpy type docstring with specific sections as Parameters, Returns, Notes, Examples and References."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def particlesWithInteraction(q, Ra, Rb, molarity, bgr, contrast, collimation=None, beta=True):\n",
    "    \"\"\"\n",
    "    Particles with interaction and ellipsoid form factor as a model for e.g. dense protein solutions.\n",
    "\n",
    "    Document your model if needed for later use that you know what you did and why.\n",
    "    Or make it short without all the nasty documentation for testing.\n",
    "    The example neglects the protein exact shape and non constant scattering length density.\n",
    "    Proteins are more potato shaped  and nearly never like a ellipsoid or sphere.\n",
    "    So this model is only valid at low Q as an approximation.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    q : float\n",
    "        Wavevector\n",
    "    Ra,Rb : float\n",
    "        Radius\n",
    "    molarity : float\n",
    "        Concentration in mol/l\n",
    "    contrast : float\n",
    "        Contrast between ellipsoid and solvent.\n",
    "    bgr : float\n",
    "        Background e.g. incoherent scattering\n",
    "    collimation : float\n",
    "        Collimation length for SANS. For SAXS use None.\n",
    "    beta : bool\n",
    "        True include asymmetry factor beta of\n",
    "        M. Kotlarchyk and S.-H. Chen, J. Chem. Phys. 79, 2461 (1983).\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "        dataArray\n",
    "\n",
    "    Notes\n",
    "    -----\n",
    "    Explicitly:\n",
    "    **The return value can be a dataArray OR only Y values**. Both is working for fitting.\n",
    "    Reference can be included from below as [1]_ .\n",
    "\n",
    "    Examples\n",
    "    --------\n",
    "    ::\n",
    "    \n",
    "     import jscatter as js\n",
    "     # do something\n",
    "     \n",
    "    References\n",
    "    ----------\n",
    "    .. [1] Another one bites the dust\n",
    "           Queen, Album: The Game (1980)\n",
    "           \n",
    "\n",
    "    \"\"\"\n",
    "    # We need to multiply form factor and structure factor and add an additional background.\n",
    "    # formfactor of ellipsoid returns dataArray with beta at last column.\n",
    "    ff = js.ff.ellipsoid(q, Ra, Rb, SLD=contrast)\n",
    "    V = ff.EllipsoidVolume\n",
    "    # the structure factor returns also dataArray\n",
    "    # we need to supply a radius calculated from Ra Rb, this is an assumption of effective radius for the interaction.\n",
    "    R = (Ra * Rb * Rb) ** (1 / 3.)\n",
    "    # the volume fraction is concentration * volume\n",
    "    # the units have to be converted as V is usually nm**3 and concentration is mol/l\n",
    "    sf = js.sf.PercusYevick(q, R, molarity=molarity)\n",
    "    if beta:\n",
    "        # beta is asymmetry factor according to M. Kotlarchyk and S.-H. Chen, J. Chem. Phys. 79, 2461 (1983).\n",
    "        # correction to apply for the structure factor\n",
    "        # noinspection PyProtectedMember\n",
    "        sf.Y = 1 + ff._beta * (sf.Y - 1)\n",
    "    #\n",
    "    # molarity (mol/l) with conversion to number/nm**3 result is in cm**-1\n",
    "    ff.Y = molarity * 6.023e23 / (1000 * 1e7 ** 2) * ff.Y * sf.Y + bgr\n",
    "    # add parameters for later use; ellipsoid parameters are already included in ff\n",
    "    # if data are saved these are included in the file as documentation\n",
    "    # or can be used for further calculations e.g. if volume fraction is needed (V*molarity)\n",
    "    ff.R = R\n",
    "    ff.bgr = bgr\n",
    "    ff.Volume = V\n",
    "    ff.molarity = molarity\n",
    "    # for small angle neutron scattering we may need instrument resolution smearing\n",
    "    if collimation is not None:\n",
    "        # For SAX we set collimation=None and this is ignored\n",
    "        # For SANS we set some reasonable values as 2000 (mm) or 20000 (mm)\n",
    "        # as attribute in the data we want to fit.\n",
    "        result = js.sas.resFunct(ff, collimation, 10, collimation, 10, 0.7, 0.15)\n",
    "    else:\n",
    "        result = ff\n",
    "    # we return the complex model for fitting\n",
    "    return result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Use the model to simulate and plot this \n",
    "Hint for fitting SANS data or other fits combining different measurements described by a common parameter.\n",
    "\n",
    "For a combined fit of several collimation distances each dataset should contain an attribute data.collimation.\n",
    "This is automatically used in the fit, if there is not explicit fit parameter with this name. SAXS data may not have this parameter or set it to zero.\n",
    "In this way different methods can be discriminated in the model function. Also char parameters are allowed as fixed parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = js.mplot()\n",
    "q = js.loglist(0.1, 5, 300)\n",
    "for m in [0.01, 0.1, 1, 2, 3, 4]:\n",
    "    data = particlesWithInteraction(q, Ra=3, Rb=4, molarity=m * 1e-3, bgr=0, contrast=1)\n",
    "    p.Plot(data, sy=[-1, 0.3, -1], le='c= $molarity M')\n",
    "\n",
    "p.Legend()\n",
    "p.Yaxis(min=100, max=1e9, scale='l', label='I(Q) / $cm^{-1}$', tick=[10, 9])\n",
    "p.Xaxis(scale='n', label='Q / $nm^{-1}$')\n",
    "p.Title('Ellipsoidal particles with interaction\\nRa=3, Rb=4 and PercusYevick structure factor')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
