{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1 Histogram Toolkit Tutorial\n",
    "\n",
    "This notebook contains tutorial material for the histogram toolkit provided as part of DL-MONTE. The aim is to provide an introduction to the idea of reweighting observations and histograms based on results from a simulation.\n",
    "\n",
    "For exposition, we choose the two-dimensional Ising model. A simple implementation of the Ising model is provided with the toolkit so that results for analysis can be generated by the reader.\n",
    "\n",
    "* All the results used for analysis are generated in the first section of the notebook. So users need to look through this first.\n",
    "\n",
    "Users interested in DL-MONTE per se should look at the accompanying notebook for the introduction to the toolkit in the DL-MONTE context.\n",
    "\n",
    "\n",
    "In writing this notebook, a number of sources have been drawn upon, including\n",
    "\n",
    "1. _A Guide to Monte Carlo Simulations in Statistical Physics_ D.P. Landau and K. Binder (Cambridge).\n",
    "\n",
    "2. Online lecture notes by Kari Rummukainen of Helsinki University available at http://www.helsinki.fi/~rummukai/lectures/montecarlo_oulu/\n",
    "\n",
    "\n",
    "This work was funded by the UK EPSRC under the ARCHER eCSE Programme.\n",
    "\n",
    "\n",
    "#### Note\n",
    "\n",
    "Please note this notebook uses `print()` and so requires Python 3 (usually displayed at the top right of the notebook screen).\n",
    "\n",
    "You can also check the current version via:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import sys\n",
    "print (sys.version_info)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "<br>\n",
    "<br>\n",
    "\n",
    "## 1.1 Python and the Histogram Toolkit\n",
    "\n",
    "Practical examples of the histogram toolkit are provided throughout the notebook, so the reader will need to know some python.\n",
    "\n",
    "Basic requirements beyond the toolkit are limited to the `numpy`, `scipy`, and `matplotlib` packages (this notebook only needs `numpy` and `matplotlib`).\n",
    "\n",
    "The following cell imports all the requirements:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import numpy\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "import htk.util\n",
    "from htk.sources.ising import IsingModel\n",
    "from htk.sources.isingdata import IsingModelData"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Not found?\n",
    "\n",
    "If you see an error saying `htk` is not found, then you may need to add the toolkit directory to `PYTHONPATH`. This can be achieved by running\n",
    "```\n",
    "export PYTHONPATH=$DL_MONTE_HOME/htk:$PYTHONPATH\n",
    "```\n",
    "in the shell, where `DL_MONTE_HOME` is the location of the DL MONTE installation, and then restarting the notebook."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "<br>\n",
    "\n",
    "# 2. Ising Model Example\n",
    "\n",
    "Here's an example based on the Ising Model. Suppose we have an Ising model in two dimensions with Hamiltonian:\n",
    "\n",
    "${\\cal H} = -J \\sum_{<i,j>} \\sigma_i \\sigma_j - \\mu H \\sum_i \\sigma_i$\n",
    "\n",
    "where $J$ is a coupling constant, $\\mu$ is a susceptibility, $H$ is the external field and the\n",
    "spins are $\\sigma_i$. The interactions involve four nearest neighbours denoted $\\langle i,j\\rangle$. We assume there are a total of $N$ spins and the temperature is $k_b T$. From this point on we will assume $\\mu = 1$ and write the Hamoltonian\n",
    "\n",
    "$ {\\cal H} = -JS - HM. $\n",
    "\n",
    "The free energy of the system is written $F = -k_b T \\ln Z$, where $Z$ is the partition function. The internal energy is $U = -T^2\\partial(F/T)/\\partial T$ where $F$ may depend on other parameters (assumed to be held constant in the derivative). The heat capacity $C_V$ is the rate of change $(\\partial U / \\partial T)_V$. Fluctuations in the energy can be used to obtain the heat capacity $C_V$ via\n",
    "\n",
    "$$\n",
    "C_V = \\frac{\\langle{\\cal H}^2\\rangle - \\langle{\\cal H}\\rangle^2}{k_b T^2} = \\frac{1}{k_b T^2}\\big\\langle({\\cal H} - \\langle{\\cal H}\\rangle )^2\\big\\rangle_{NVT}.$$\n",
    "\n",
    "To compare systems of different sizes, it is often useful to consider the \"specific heat\" $C_V/N$.\n",
    "\n",
    "Likewise, fluctuations in the magnetisation $M = \\sum_i \\sigma_i$ can be used to obtain the isothermal susceptibilty $\\chi = (\\partial M / \\partial H)_V$ via\n",
    "\n",
    "$$\\chi = \\frac{\\langle M^2\\rangle - \\langle M \\rangle^2}{k_b T}.$$\n",
    "\n",
    "\n",
    "The (mean field) critical temperature for the Ising model is known to be at $$kT/J = 2 / ln(1 + \\sqrt{2})$$ or $kT_c/J \\approx 2.2692$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.1 Running the Ising Model\n",
    "\n",
    "The histogram toolkit contains a 2-d `IsingModel` class which can be used to generate some test data. To use the class, import via"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from htk.sources.ising import IsingModel"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The interface to the `IsingModel` class includes methods for initialisation and executation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "help(IsingModel)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "The following sections discuss how to generate the test results used later in the analysis. Some of the longer Monte Carlo runs may take an unreasonably long time on any given hardware, so it may be desirable to use fewer Monte Carlo interations, particularly for the larger systems.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1.1 Running from different initial conditions\n",
    "\n",
    "We can examine the course of the observable quantiies as a function of the number of Monte Carlo sweeps for different initial conditions. Ultimately, this should not influence the equilibrium result.\n",
    "\n",
    "Take an example of a 16x16 system at a relatively low temperature kT = 1.0, which we can initialise as either 'hot' or 'cold'. We run the system for 2000 Monte Carlo sweeps and store the results in a file in each case. These should take a few seconds to execute in each case:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "nlen = 16\n",
    "J = 1.0\n",
    "H = 0.0\n",
    "kT = 1.0\n",
    "seed = 37\n",
    "nsweeps = 100\n",
    "modelh = IsingModel(nlen, J, H, kT, seed, 'hot')\n",
    "modelh.run(nsweeps, file = 'ising016_ht_1.0_s37.dat')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Instantiate a new model with the same parameters, and use a 'cold' start:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "modelc = IsingModel(nlen, J, H, kT, seed, 'cold')\n",
    "modelc.run(nsweeps, file = 'ising016_cd_1.0_s37.dat')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we need to reload these data from file into an object which can be manipulated by the histogram toolkit. This is done via the `IsingModelData` class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from htk.sources.isingdata import IsingModelData \n",
    "datah = IsingModelData(filename = 'ising016_ht_1.0_s37.dat')\n",
    "datac = IsingModelData(filename = 'ising016_cd_1.0_s37.dat')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using these objects, we can extract the total energy and the magnetisation as a function of MC sweep (which have been recorded in the file), and plot them using standard `pyplot` methods. Here we plot the total energy and M, the absolute value of the magnetisation as a function of MC sweep.\n",
    "\n",
    "Data are accessed via the `data` attribute of the obsevable, identified by its key (`e` for energy, and `m` for magnetisation; these are not case sensitive).\n",
    "\n",
    "It can be seen that for this system size and temperature, it takes about 10-20 MC sweeps for the hot and cold starts to converge to a consistent state."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import numpy\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.figure(figsize=(10.,3.0))\n",
    "plt.subplot(1,2,1)\n",
    "plt.xlabel(\"MC sweep\")\n",
    "plt.ylabel(\"Total energy\")\n",
    "plt.axis([0,nsweeps,-2.5,1.2])\n",
    "plt.plot(datac.observable('e').data[:], 'bs')\n",
    "plt.plot(datah.observable('e').data[:], 'ro')\n",
    "\n",
    "plt.subplot(1,2,2)\n",
    "plt.xlabel(\"MC sweep\")\n",
    "plt.ylabel(\"|Magnetisation|\")\n",
    "plt.axis([0,nsweeps,0.0,1.2])\n",
    "plt.plot(numpy.abs(datac.observable('M').data), 'bs')\n",
    "plt.plot(numpy.abs(datah.observable('M').data), 'ro')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can try again at a higher temperature. We make use of a convenience method in the `IsingModelData` class (actaully the superclass method `summary_time_series()`) to display time series of the observables in each case. At the higher temperature, comparison will show that the results of the 'hot' and 'cold' look very similar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "kT = 3.0\n",
    "nsweeps = 500\n",
    "model3h = IsingModel(nlen, J, H, kT, seed, 'hot')\n",
    "model3c = IsingModel(nlen, J, H, kT, seed, 'cold')\n",
    "model3h.run(nsweeps, file = \"ising016_ht_3.0_s37.dat\")\n",
    "model3c.run(nsweeps, file = \"ising016_cd_3.0_s37.dat\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data3h = IsingModelData(filename = \"ising016_ht_3.0_s37.dat\")\n",
    "data3c = IsingModelData(filename = \"ising016_cd_3.0_s37.dat\")\n",
    "data3h.summary_time_series(plt, 'e')\n",
    "data3c.summary_time_series(plt, 'e')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "##### 2.1.2 Generating the Test Results\n",
    "\n",
    "A number of runs of the Ising model are required to generate the data files used in the analysis. These are generated by the commands below.\n",
    "\n",
    "Two sets are provided. The \"short\" set can be generated relateively quickly and provide a reasonable overview of the reuslts. However, the short length of the runs means that statistics are not really satisifactory.\n",
    "\n",
    "A \"medium\" set is also described which provide better statistics at the cost\n",
    "of increased run time.\n",
    "\n",
    "All the simulations use here have $N=4, 8$ or $16$, and have $J=1$ and $H=0$.\n",
    "\n",
    "###### Short set"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "J = 1.0; H = 0.0\n",
    "model = IsingModel(4, J, H, 2.1, 37, 'hot')\n",
    "model.run(10000, file = \"short_u004_2.1_s37_h.dat\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "model = IsingModel(8, J, H, 2.1, 37, 'hot')\n",
    "model.run(10000, file = \"short_u008_2.1_s37_h.dat\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "model = IsingModel(16, J, H, 2.1, 37, 'hot')\n",
    "model.run(10000, file = \"short_u016_2.1_s37_h.dat\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Medium set"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# May take about a minute\n",
    "model = IsingModel(4, J, H, 2.2692, 39, \"hot\")\n",
    "model.run(200000, file = \"medium_u004_2.2692_s39_h.dat\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# May take a few minutes\n",
    "model = IsingModel(8, 1.0, 0.0, 2.2692, 39, \"hot\")\n",
    "model.run(200000, file = \"medium_u008_2.2692_s39_h.dat\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# May take 20-30 minutes\n",
    "model = IsingModel(16, 1.0, 0.0, 2.2692, 39, \"hot\")\n",
    "model.run(200000, file = \"medium_u016_2.2692_s39_h.dat\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br>\n",
    "\n",
    "# 3. Reweighting\n",
    "\n",
    "Here we use some of the data generated in the previous section to perform some basic reweighting operations. A number of features of the Histogram Toolkit are also discussed.\n",
    "\n",
    "## 3.1 Obsevables\n",
    "\n",
    "Suppose we have some data from a previous Ising Model simulation. We can load it using the data source class, and review its contents:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data1 = IsingModelData(filename = \"short_u008_2.1_s37_h.dat\")\n",
    "table = data1.to_table()\n",
    "print(table)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Internally, the parameters are stored as `Parameter` objects, having a `Label` and a value. Parameters with a given label can extracted from the data set container:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "p = data1.parameter(\"kT\")\n",
    "print(\"The temperature is: \", p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the `Parameter` is a number, but also has an associated label:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "print(p.label)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Likewise, observables data are stored in an `Observable` object, which may be accessed via, e.g.,"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "t = data1.observable(\"t\")\n",
    "print(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This shows that the time (a misnomer for MC simulation) observations are stored as an array (`numpy.ndarray`, in fact), here with values between 2,020 and 400,000 MC sweeps.\n",
    "\n",
    "We can use the labels --- which are not case sensitive --- to look at the time series of the various observables. For example, use \"M\" or \"m\" to look at the magnetisation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data1.summary_time_series(plt, 'm')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.1.1 Autocorrelation and Errors\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The normalised autocorrelation for quantity $A(t)$ is defined as:\n",
    "\n",
    "$$ \\phi(t) = \\frac{\\langle A(0)A(t)\\rangle - \\langle A\\rangle^2}{\\langle A^2\\rangle - \\langle A\\rangle^2} $$\n",
    "\n",
    "The normaliser $\\langle A^2\\rangle - \\langle A\\rangle^2 $\n",
    "is defined so that $ \\phi(t = 0) = 1$. This can be useful to check that obsevations are not unduly correlated. For example, looking at the energy in an output with observations recorded at every MC sweep, we can see:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data2 = IsingModelData(filename = \"short_u004_2.1_s37_h.dat\")\n",
    "data2.summary_autocorrelation(plt, 'e')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can get a numerical estimate of the correlation time $\\tau$ by integrating the autocorrelation function $\\phi(t)$. This utility function also returns the \"time step\" $\\delta t$ (in Monte Carlo sweeps)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "(dt, tau) = data2.autocorrelation_time(\"E\")\n",
    "print(dt, tau, (1.0 + 2.0*tau/dt))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Estimates of the standard deviation for an observable quantity are then scaled by the factor\n",
    "\n",
    "$(1 + 2\\tau / \\delta t)$\n",
    "\n",
    "which can be large. See Landau and Binder 4.2.4."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Different observable quatities may have different characteristic correlation times:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data2.summary_autocorrelation(plt, 'M')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ultimately, the autocorrelation time can depend on many factors, including the observable, the temperature, update method in the MC algorithm, and so on.\n",
    "\n",
    "#### Histogram\n",
    "\n",
    "A utility method is provided to plot a histogram of observable measurements, e.g., for the magnetisation\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data1.summary_histogram(plt, \"m\", 21)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "## 3.2 Observable reweighting\n",
    "\n",
    "Suppose we have a satisfactory Monte Carlo simulation for the Ising Model in the NVT ensemble which has been run with a given set of parameters near the critical point."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data04 = IsingModelData()\n",
    "data04.load(filename = \"medium_u004_2.2692_s39_h.dat\")\n",
    "table = data04.to_table()\n",
    "print(table)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will need the system size and the temperature from the parameters, and we will also use the total energy (not energy per site) to find reweighted values of the specific heat:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "volume = data04.parameter(\"V\")\n",
    "kT = data04.parameter(\"kT\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The data arrays are extracted as follows. We will require the total energy (not that per site), so multiply by the volume. We also generate a set of values for observations $E_i^2$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "e0 = data04.observable('E').data\n",
    "e1 = volume*e0[:]\n",
    "e2 = e1[:]*e1[:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We reweight a series of observables in NVT via:\n",
    "\n",
    "$$ \\langle O\\rangle_{\\beta'} = \\frac{\\sum_i O_i \\exp[-(\\beta' - \\beta)E_i]}{\\sum_i \\exp[-(\\beta' - \\beta)E_i]}.$$\n",
    "\n",
    "We define a series of new temerpatures; for each temperature we will produce a reweighted $\\langle  E\\rangle$ and reweighted $\\langle E^2 \\rangle$, which allows $C_V$ (actually $C_V/N$) to be computed.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "nrw = 75\n",
    "kt_new = numpy.linspace(start = 1.2, stop = 2.7, num = nrw)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A `Reweighter` object is supplied by the data set container. This performs the appropriate reweighting for new values of $kT$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "reweighter = data04.reweighter(\"kT\")\n",
    "print(reweighter)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "cv04 = numpy.zeros(nrw)\n",
    "for nt in range(nrw):\n",
    "    e1r = reweighter.reweight_obs(e1, kt_new[nt])\n",
    "    e2r = reweighter.reweight_obs(e2, kt_new[nt])\n",
    "    cv04[nt] = (1.0/(volume*kt_new[nt]**2))*(e2r - e1r*e1r)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The reweighted values of $C_V$ are shown below. We can also plot the single value of $C_V/N$ computed directly from the simulation data at the original temperature (the reweighted value must match here)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "e1bar = e1.mean()\n",
    "e2bar = e2.mean()\n",
    "cv04s = (1.0/(volume*kT**2))*(e2bar - e1bar*e1bar)\n",
    "\n",
    "plt.plot(kt_new, cv04)\n",
    "plt.plot(kT, cv04s, 'ro')\n",
    "plt.xlabel(\"Temperature\")\n",
    "plt.ylabel(\"$C_V/N$\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To get an estimate of the statistical error in the energy, we recall our factor related to the autocorrelation time $(1 + 2\\tau/\\delta t)$, which is (actually rather small for this example):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "(dt, tau) = data04.autocorrelation_time(\"E\")\n",
    "de = numpy.sqrt((1.0/e1.size)*(e2bar - e1bar*e1bar)*(1.0 + 2.0*tau/dt))\n",
    "print(dt, tau, de)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Larger systems\n",
    "\n",
    "If the total energy becomes large, the terms in the exponential can grow to the point that numerical problems arise. This is handled in the reweighter code by finding $E_{max} = max_i \\{E_i\\}$ and computing\n",
    "\n",
    "$$\n",
    "\\langle O\\rangle_{\\beta'} = \\frac{\\sum_i O_i \\exp[-(\\beta' - \\beta)(E_i - E_{max})]}{\\sum_i \\exp[-(\\beta' - \\beta)(E_i - E_{max})]}.\n",
    "$$\n",
    "\n",
    "However, there is also a limit on the range to which reweighting can be carried out accurately (discussed below). We'll see what the situation looks like at first.\n",
    "\n",
    "Here are the data for an 8x8 system:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data08 = IsingModelData(\"medium_u008_2.2692_s39_h.dat\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "volume = data08.parameter(\"V\")\n",
    "kT = data08.parameter(\"kT\")\n",
    "\n",
    "e0 = data08.observable(\"E\").data\n",
    "e1 = volume*e0[:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Reweight to the same series of new temperatures. Note that the reweighter function will take `numpy.ndarray` as an argument and return an array of new values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "reweighter = data08.reweighter(\"kT\")\n",
    "\n",
    "e1r = reweighter.reweight_obs(e1[:], kt_new[:])\n",
    "e2r = reweighter.reweight_obs(e1[:]*e1[:], kt_new[:])\n",
    "\n",
    "cv08 = numpy.zeros(nrw)\n",
    "cv08[:] = (1.0/(volume*kt_new[:]**2))*(e2r[:] - e1r[:]*e1r[:])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Data for the 16x16 system is also reweighted to provide estimates of $C_V$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "data16 = IsingModelData(\"medium_u016_2.2692_s39_h.dat\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "volume = data16.parameter(\"V\")\n",
    "kT = data16.parameter(\"kT\")\n",
    "\n",
    "e0 = data16.observable(\"E\").data\n",
    "e1 = volume*e0[:]\n",
    "\n",
    "rewght = data16.reweighter(\"beta\")\n",
    "e1r = rewght.reweight_obs(e1, 1.0/kt_new[:])\n",
    "e2r = rewght.reweight_obs(e1[:]*e1[:], 1.0/kt_new[:])\n",
    "\n",
    "cv16 = numpy.zeros(nrw)\n",
    "cv16[:] = (1.0/(volume*kt_new[:]**2))*(e2r[:] - e1r[:]*e1r[:])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "This plot compares the reweighted values of $C_V$ for the different system sizes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plt.plot(kt_new, cv04)\n",
    "plt.plot(kt_new, cv08)\n",
    "plt.plot(kt_new, cv16)\n",
    "plt.xlabel(\"Temperature\", fontsize = 12)\n",
    "plt.ylabel(\"$C_V/N$\", fontsize = 18)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Clearly, there is something awry with the above picture. In particular, the curve for the largest system size (red) look badly behaved. This is because the range over which reweighting is valid depends on a number of factors."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2.1 Valid range of reweighting\n",
    "\n",
    "If we look at the histogram of the total energy for a given simulation, this will give us an idea of the extent of reliable statitics in energy space. Clearly, away from the expectation value, there are few or no observations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data16.summary_histogram(plt, \"E\", 21)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we form the discrete histogram of the total energy from the $N$ observations of the total energy $E_i$ as\n",
    "\n",
    "$ h_{kT}(E_j) = (1/N) \\sum_i \\delta(E_i, E_j) $\n",
    "\n",
    "where $E_j$ represent the histogram bins and $\\delta(E_i, E_j)$ counts the observations in bin $j$, it is possible to reweight the historgram to a new temperature $T'$ via\n",
    "\n",
    "$$ h_{kT'}(E_j) = \\frac{\\sum_i \\delta(E_i, E_j) exp [-(1/kT' - 1/kT)E_i]}{\\sum_i exp[-(1/kT' - 1/kT)E_i]}$$\n",
    "\n",
    "If the resulting histogram is too far from the expectation value for the original, statisitical errors will grow to dominate.\n",
    "\n",
    "We can look more closely at the histogram. At this point we will use the `numpy.histogram` method to perform the binning explicitly. Later, we will see the Histogram Toolkit `Histrogram` class."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "e0 = data16.observable(\"E\").data\n",
    "volume = data16.parameter(\"V\")\n",
    "e1 = volume*e0[:]\n",
    "kt = data16.parameter(\"kT\")\n",
    "erange = (-512.0, -128.0)\n",
    "hist1, bin_edges1 = numpy.histogram(e1, bins = 32, range = erange, density = True)\n",
    "ax = plt.subplot()\n",
    "ax.set_ylim(0.0, 0.01)\n",
    "bars = plt.bar(bin_edges1[:-1], hist1, width = 4.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can reweight the histogram to a new temperature by computing the weights $exp[-(1/kT' - 1/kT)E_i]$ in numerator of the above equation. The normalisation is taken care of by the `numpy.histogram` routine as it is just hte sum of the weights. Note we subtract the largest exponent as a protection against overflow. We change the temperature by 0.1 (a guess)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "w2 = numpy.zeros(e0.size)\n",
    "smax = e1.max()\n",
    "# Change kt by 0.1\n",
    "ktnew = kt + 0.1\n",
    "db = (1.0/ktnew) - (1.0/kt)\n",
    "w2[:] = numpy.exp(-db*(e1[:] - smax))\n",
    "ax = plt.subplot()\n",
    "ax.set_ylim(0.0, 0.01)\n",
    "hist2, bin_edges2 = numpy.histogram(e1, bins = 32, range = erange, density = True, weights = w2)\n",
    "bars2 = plt.bar(bin_edges2[:-1], hist2, width = 8., color = 'r')\n",
    "bars1 = plt.bar(bin_edges1[:-1], hist1, width = 4., color = 'b')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This shows that we may be getting toward the limit of reweighting for this example. Following Rummukainen, the limit of reweighting may be estimated by asking that the new expectation value lies within one (original) standard deviation of the original expectation, i.e.:\n",
    "\n",
    "$$\n",
    "\\left| \\langle E\\rangle_{kT} - \\langle E\\rangle_{kT'} \\right| < \\big\\langle[E - \\langle E\\rangle_{kT}]^2 \\big\\rangle_{kT}^{1/2}.\n",
    "$$\n",
    "\n",
    "At this point we note the standard relation $-(\\partial U / \\partial\\beta)_V = \\langle{\\cal H}\\rangle - \\langle{\\cal H}\\rangle^2$ may be used to write\n",
    "\n",
    "$$\n",
    "kT^2C_V = \\langle H\\rangle^2 - \\langle H\\rangle^2 = \\big\\langle (E - \\langle E\\rangle)^2 \\big\\rangle_{NVT}\n",
    "$$\n",
    "\n",
    "(see Landau and Binder 2.1.1.4). In this case our condition becomes\n",
    "\n",
    "$$\n",
    "\\left| \\langle E\\rangle_{kT} - \\langle E\\rangle_{kT'} \\right| < (kT^2 C_V)^{1/2},\n",
    "$$\n",
    "\n",
    "where $C_V$ is evaluated at the original temperature. If we make the assumption that the changes are small, we may approximate\n",
    "\n",
    "$$\n",
    "C_V = \\frac{\\partial E }{\\partial T}\\Bigg|_V \\approx \\frac{\\langle E\\rangle_{kT} - \\langle E\\rangle_{kT'}}{\\delta kT}\n",
    "$$\n",
    "and so the maximum change in temperature which meets the condition is\n",
    "\n",
    "$$\n",
    "\\delta kT_{max} \\approx (kT^2/C_V)^{1/2}.\n",
    "$$\n",
    "\n",
    "We can check this against what is seen above by computing directly $C_V$ from the expectation values of the energy:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "e2 = e1[:]*e1[:]\n",
    "e1x = e1.mean()\n",
    "e2x = e2.mean()\n",
    "cv = htk.util.nvt_cv(e1x, e2x, kt)\n",
    "\n",
    "dktmax = kt/numpy.sqrt(cv)\n",
    "print(kt, dktmax)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "which looks about right. Similar arguments can be made for the histograms of other observables, in which case $C_V$ is replaced by the appropriate susceptibility. Note that the maximum temperature range for reweighting decreases with increasing system size (as $V^{-1/2}$, or $N^{-1/2}$).\n",
    "\n",
    "##### Re-analysis\n",
    "\n",
    "If we take a slightly relaxed limit of two standard deviations in the above argument, we can look again at the data for systems sizes 4x4, 8x8, 16x16 and 32x32, and adopt limits of $\\delta kT_{max} = 0.8$, $0.4$, $0.2$ and $0.1$ respectively.\n",
    "\n",
    "If we take the appropriate subset of the results computed above, the analysis then looks like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plt.plot(kt_new, cv04)\n",
    "plt.plot(kt_new[34:], cv08[34:])\n",
    "plt.plot(kt_new[44:59], cv16[44:59])\n",
    "plt.xlabel(\"Temperature\", fontsize = 12)\n",
    "plt.ylabel(\"$C_V/N$\", fontsize = 18)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2.3 Reweighting wrt External Magnetic Field\n",
    "\n",
    "We can also reweight by changing other parameters in the Hamiltonian . If our original simulation was performed at zero external magnetic field $H = 0$, we can reweight to finite $H$.\n",
    "\n",
    "Let's work out the susceptibility for one of the previous data sets. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data04 = IsingModelData(filename = \"short_u004_2.1_s37_h.dat\")\n",
    "kt = data04.parameter(\"kT\")\n",
    "volume04 = data04.parameter(\"V\")\n",
    "m1 = volume04*data04.observable(\"m\").data\n",
    "m2 = m1[:]*m1[:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "m1bar = m1.mean()\n",
    "m2bar = m2.mean()\n",
    "chi = (1.0/kt)*(m2bar - m1bar*m1bar)\n",
    "print (\"Susceptibility \", chi/(volume04*volume04))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To reweight an observable with repect to the external magnetic field we need\n",
    "$$\n",
    "\\langle O\\rangle_{H'} = \\frac{\\sum_i O_i exp [-\\beta(H' - H)M_i]}{ \\sum_i exp[-\\beta(H'-H)M_i]}\n",
    "$$\n",
    "where $H'$ is the new external field."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Create some new external field values\n",
    "nrw = 40\n",
    "hnew04 = numpy.linspace(start = 0.0, stop = 0.4, num = nrw)\n",
    "\n",
    "# Use the pre-supplied reweighter for \"H\"\n",
    "reweighter = data04.reweighter(\"H\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "chinew = numpy.zeros(nrw, dtype = numpy.float)\n",
    "\n",
    "\n",
    "for i in range(nrw):\n",
    "    m1r = reweighter.reweight_obs(m1, hnew04[i])\n",
    "    m2r = reweighter.reweight_obs(m2, hnew04[i])\n",
    "    chinew[i] = (1.0/(volume04*kt))*(m2r - m1r*m1r)\n",
    "\n",
    "plt.plot(hnew04*volume, chinew/volume)\n",
    "plt.xlabel(\"$HL^2$\", fontsize = 16)\n",
    "plt.ylabel(\"$\\chi / L^2$\", fontsize = 16)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data08a = IsingModelData(filename = \"short_u008_2.1_s37_h.dat\")\n",
    "kt = data08a.parameter(\"kT\")\n",
    "volume08 = data08a.parameter(\"V\")\n",
    "m1 = volume08*data08a.observable(\"M\").data\n",
    "m2 = m1[:]*m1[:]\n",
    "\n",
    "chinew2 = numpy.zeros(nrw, dtype = numpy.float)\n",
    "hnew08 = numpy.linspace(start = 0.0, stop = 0.1, num = nrw)\n",
    "reweighter = data08a.reweighter(\"H\")\n",
    "\n",
    "for i in range(nrw):\n",
    "    m1r = reweighter.reweight_obs(m1, hnew08[i])\n",
    "    m2r = reweighter.reweight_obs(m2, hnew08[i])\n",
    "    chinew2[i] = (1.0/(volume08*kt))*(m2r - m1r*m1r)\n",
    "\n",
    "ax = plt.subplot()\n",
    "ax.set_xlim(0.0, 6.0)\n",
    "plt.plot(hnew04*volume04, chinew/volume04)\n",
    "plt.plot(hnew08*volume08, chinew2/volume08)\n",
    "plt.xlabel(\"$HL^2$\", fontsize = 16)\n",
    "plt.ylabel(\"$\\chi / L^2$\", fontsize = 16)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These values can be compared with the results of, e.g., Ferrenberg and Swendsen *Physical Review Letters* **61**, 2635-2638 (1988)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "### 3.2.4 Reweighting wrt two or more parameters at the same time\n",
    "\n",
    "It may be convenient to reweight more than one parameter, e.g., to reweight with respect to both temperature and external field in one\n",
    "go.\n",
    "\n",
    "This may be done simply by combining the appropriate Boltzmann weight factors in the reweighting expression, e.g.,\n",
    "$$\n",
    "\\langle O\\rangle_{kT', H'} = \\frac{\\sum_i O_i exp[-(\\beta' - \\beta)E_i] exp[-\\beta(H' - H)M_i]}{ \\sum_i exp[-(\\beta' - \\beta)E_i] exp[-\\beta(H'-H)M_i]}\n",
    "$$\n",
    "\n",
    "where we have a new (inverse) temperature $\\beta'$ and a new external magnetic field $H'$.\n",
    "\n",
    "We will illustrate this by first creating, explicitly, a `Reweighter` object for the temperature (we could have used the one above)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data04 = IsingModelData(filename = \"short_u004_2.1_s37_h.dat\")\n",
    "\n",
    "# Note these are the Parameter objects, and the Observable object\n",
    "\n",
    "kt = data04.parameter(\"kT\")\n",
    "volume = data04.parameter(\"V\")\n",
    "obs_e1 = data04.observable(\"E\")\n",
    "\n",
    "# Recall that the energies are per site, so a factor of the volume\n",
    "# is required to compute the total energy\n",
    "\n",
    "reweighter_kt = htk.histogram.KTReweighter(\"kt\", kt, volume, obs_e1)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# You can check the arguments of the KTReweighter initialisation:\n",
    "help(htk.histogram.KTReweighter)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We note that we can reweight the magnetistation wrt temperature in the normal way (here in fact the modulus of the magnetisation), so"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "\n",
    "kt = data04.parameter(\"kT\")\n",
    "volume = data04.parameter(\"V\")\n",
    "m1 = data04.observable(\"M\").data\n",
    "m2 = m1[:]*m1[:]\n",
    "\n",
    "nrw = 40\n",
    "kt_new = numpy.linspace(start = 1.2, stop = 3.7, num = nrw)\n",
    "\n",
    "mr = numpy.zeros(nrw, dtype = numpy.float)\n",
    "\n",
    "for i in range(nrw):\n",
    "    mr[i] = reweighter_kt.reweight_obs(m2, kt_new[i])\n",
    "\n",
    "plt.plot(kt_new, mr)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "We may recover from the reweighter object for $kT'$ a set of weights appropriate to a single new temperature $kT'$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "kt_new = 1.5\n",
    "wkt = reweighter_kt.compute_weights(kt_new)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We than then create a `ChainReweighter` object which will reweight with resepect to the magnetic field, but includes the Boltzmann weights for the new temperature, thus:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "h_old = data04.parameter(\"H\")\n",
    "m = data04.observable(\"M\")\n",
    "\n",
    "const = htk.parameter.Parameter(volume/kt,\n",
    "                                htk.util.Label(\"v/kT\", \"Constant\", \"sites/k_bT\"))\n",
    "\n",
    "r = htk.histogram.ChainReweighter(\"h_and_kt\", h_old, const, m, wkt)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use this to reweight to a series of new external fields, and the new temperature:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "kt = data04.parameter(\"kT\")\n",
    "volume = data04.parameter(\"V\")\n",
    "m1 = volume*data04.observable(\"M\").data\n",
    "m2 = m1[:]*m1[:]\n",
    "\n",
    "nrw = 40\n",
    "hnew = numpy.linspace(start = 0.0, stop = 0.4, num = nrw)\n",
    "chinew1 = numpy.zeros(nrw, dtype = numpy.float)\n",
    "\n",
    "for i in range(nrw):\n",
    "    m1r = r.reweight_obs(m1, hnew[i])\n",
    "    m2r = r.reweight_obs(m2, hnew[i])\n",
    "    chinew1[i] = (1.0/(volume*kt_new))*(m2r - m1r*m1r)\n",
    "    \n",
    "plt.plot(hnew*volume, chinew/volume)\n",
    "plt.plot(hnew*volume, chinew1/volume)\n",
    "plt.xlabel(\"$HL^2$\", fontsize = 16)\n",
    "plt.ylabel(\"$\\chi / L^2$\", fontsize = 16)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "### 3.2.5 Predicting the position of the critical point  via reweighting\n",
    "\n",
    "In the example above, the simulation took place very close to the (known) position of the (mean field) critical point. But what is the actual position of the critical point for that system size? We just need to locate the position of the maximum of the curve as plotted.\n",
    "\n",
    "It is a straightforward task to locate the maximum of the reweighted curve for the existing data at $KT = 2.1$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data004a = IsingModelData(filename = \"short_u004_2.1_s37_h.dat\")\n",
    "volume = data004a.parameter(\"V\")\n",
    "kt = data004a.parameter(\"kt\")\n",
    "\n",
    "nrw = 80\n",
    "ktnew = numpy.linspace(start = 1.9, stop = 2.7, num = nrw)\n",
    "\n",
    "cv_a = data004a.reweight_cv(ktnew)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Cheat: original cv is...\n",
    "\n",
    "cva0 = data004a.reweight_cv(kt)\n",
    "\n",
    "plt.plot(ktnew, cv_a, 'bs')\n",
    "plt.plot(kt, cva0, 'ro')\n",
    "plt.xlabel(\"Temperature\", fontsize=16)\n",
    "plt.ylabel(\"$C_V/N$\", fontsize=16)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One can identify the predicted maximum of $C_V$, e.g.:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "kt_peak = ktnew[cv_a.argmax()]\n",
    "print (\"Maximum approx. \", kt_peak)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try again nearer the critical temperature, e.g., $kT = 2.3$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "J = 1.0\n",
    "H = 0.0\n",
    "seed = 99\n",
    "model = IsingModel(4, J, H, 2.3, seed, 'hot')\n",
    "model.run(10000, \"short_u004_2.3_s99_h.dat\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And look at the updated results (cf. previous)..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data004b = IsingModelData(filename = \"short_u004_2.3_s99_h.dat\")\n",
    "kt = data004b.parameter(\"kt\")\n",
    "cv_b = data004b.reweight_cv(ktnew)\n",
    "cvb0 = data004b.reweight_cv(kt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plt.plot(ktnew, cv_a, label = \"kT = 2.10\")\n",
    "plt.plot(ktnew, cv_b, label = \"kT = 2.38\")\n",
    "plt.plot(2.10, cva0, 'bo')\n",
    "plt.plot(kt, cvb0, 'go')\n",
    "plt.legend(loc = 'lower right')\n",
    "plt.xlabel(\"Temperature\")\n",
    "plt.ylabel(\"$C_V/N$\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Additional simulation\n",
    "\n",
    "Let's try one more at a higher temperature $kT = 2.7$. The accompanying plot shows we obtain three slightly differening estimates of the $C_V$ curve from the three simuations at different temperatures."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "J = 1.0\n",
    "H = 0.0\n",
    "seed = 99\n",
    "model = IsingModel(4, J, H, 2.7, seed, 'hot')\n",
    "model.run(10000, \"short_u004_2.7_s99_h.dat\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data004c = IsingModelData(filename = \"short_u004_2.7_s99_h.dat\")\n",
    "cv_c = data004c.reweight_cv(ktnew)\n",
    "cvc0 = data004c.reweight_cv(2.7)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plt.plot(ktnew, cv_a, label = \"kT = 2.1\")\n",
    "plt.plot(ktnew, cv_b, label = \"kT = 2.3\")\n",
    "plt.plot(ktnew, cv_c, label = \"kT = 2.7\")\n",
    "plt.plot(2.10, cva0, 'bo')\n",
    "plt.plot(2.30, cvb0, 'go')\n",
    "plt.plot(2.70, cvc0, 'ro')\n",
    "plt.legend(loc = \"lower right\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br>\n",
    "\n",
    "## 3.3 Reweighting Two or More Datasets\n",
    "\n",
    "The previous section has produced a number of simulations of the same system with different temperatures. We can use more than one set of observations to reweight a given observation; this should produce better statistics than a single simulation alone. The description here follows Rummukainen [2].\n",
    "\n",
    "In practice, we need to connect the simulations at different temperatures by coming up for an expression for the (temperature-independent) density of states. This introduces a free energy for each separate simulation which we denote $f_r$ where we have simulations $r = 1, \\ldots N^\\mathrm{run}$. A given simulation $r$ can has a number of measurements $N^r$ associated with it (and the number can be different for different runs). Energy measurements are denoted $E_i^r$ for run $r$ with $i = 1,\\ldots, N^r$. Each of the runs has a weight associated with it which is related to the statistical inefficiency for the energy measurements:\n",
    "$ w = (1.0 + 2\\tau/\\delta t)^{-1}$\n",
    "\n",
    "The free energies are related by the equation\n",
    "\n",
    "$$\n",
    "\\exp[-f_r] = \\sum_q^{N^\\mathrm{run}} \\sum_i^{N^q} \\frac{w_q \\exp[-\\beta_r E_i^q]}{\\sum_s^{N^\\mathrm{run}} N^s w_s \\exp[-\\beta_s E_i^q + f_s]}\n",
    "$$\n",
    "\n",
    "Values for the free energies must be computed via an iterative method and are determined up to an arbitary constant (which will remain unspecified).\n",
    "\n",
    "To reweight to a new temperature denoted $kT' = 1/\\beta'$, we first need the free energy at the new temperature $f'$, computed using the same relation\n",
    "\n",
    "$$\n",
    "\\exp[-f'] = \\sum_q^{N^\\mathrm{run}} \\sum_i^{N^q} \\frac{w_q \\exp[-\\beta' E_i^q]}{\\sum_s^{N^\\mathrm{run}} N^s w_s \\exp[-\\beta_s E_i^q + f_s]}.\n",
    "$$\n",
    "\n",
    "Observable quantities $O_i^r$ may be reweighted to the new temperature via\n",
    "\n",
    "$$\n",
    "\\langle O \\rangle_{\\beta'} = \\sum_q^{N^\\mathrm{run}} \\sum_i^{N^q} \\frac{ O_i^q w_q \\exp[-\\beta' E_i^q - f']}{\\sum_s^{N^\\mathrm{run}} N^s w_s \\exp[-\\beta_s E_i^q + f_s]}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# This will take a minute or two..\n",
    "volume = 16.0\n",
    "\n",
    "ea = volume*data004a.observable(\"E\").data\n",
    "eb = volume*data004b.observable(\"E\").data\n",
    "ec = volume*data004c.observable(\"E\").data\n",
    "e = [ea, eb, ec]\n",
    "obs1 = [ea, eb, ec]\n",
    "obs2 = [ea[:]*ea[:], eb[:]*eb[:], ec[:]*ec[:]]\n",
    "kT = [data004a.parameter(\"kT\"), data004b.parameter(\"kT\"),\n",
    "      data004c.parameter(\"kt\")]\n",
    "\n",
    "(dt_a, tau_a) = data004a.autocorrelation_time(\"E\")\n",
    "(dt_b, tau_b) = data004b.autocorrelation_time(\"E\")\n",
    "(dt_c, tau_c) = data004c.autocorrelation_time(\"E\")\n",
    "ta = (1.0 + 2.0*tau_a/dt_a)\n",
    "tb = (1.0 + 2.0*tau_b/dt_b)\n",
    "tc = (1.0 + 2.0*tau_c/dt_c)\n",
    "w = [1.0/ta, 1.0/tb, 1.0/tc]\n",
    "\n",
    "print (dt_a, dt_b, dt_c, tau_a, tau_b, tau_c, ta, tb, tc)\n",
    "\n",
    "cvm = numpy.zeros(nrw)\n",
    "fe = htk.util.multiple_free_energies(e, kT, w)\n",
    "for i in range(nrw):\n",
    "    e1r = htk.util.multiple_reweight_observable_nvt(e, obs1, kT, fe, w, ktnew[i])\n",
    "    e2r = htk.util.multiple_reweight_observable_nvt(e, obs2, kT, fe, w, ktnew[i])\n",
    "    cvm[i] = (1.0/(volume*ktnew[i]**2))*(e2r - e1r*e1r)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plt.plot(ktnew, cv_a, label = \"kT = 2.1\")\n",
    "plt.plot(ktnew, cv_b, label = \"kT = 2.3\")\n",
    "plt.plot(ktnew, cv_c, label = \"kT = 2.7\")\n",
    "plt.plot(ktnew, cvm,  label = \"Combined\")\n",
    "plt.legend(loc = \"upper left\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On close inspection it can be seen that the combined reweighted curve is closer to the original lower temperature data at low temperatures, and nearer the original higher temperature data at high temperatures.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [Root]",
   "language": "python",
   "name": "Python [Root]"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
