{"action":"created","comment":{"url":"https://api.github.com/repos/scipy/scipy/pulls/comments/80849370","pull_request_review_id":1873664,"id":80849370,"diff_hunk":"@@ -0,0 +1,631 @@\n+# Generalized simulated annealing implementation.\n+# Copyright (c) 2016 Sylvain Gubian <sylvain.gubian@pmi.com>,\n+# Yang Xiang <yang.xiang@pmi.com>\n+# Author: Sylvain Gubian, PMP S.A.\n+\"\"\"\n+gensa: A generalized simulated annealing global optimization algorithm\n+\"\"\"\n+from __future__ import division, print_function, absolute_import\n+\n+import numpy as np\n+import time\n+from scipy.optimize import OptimizeResult\n+from scipy.optimize import _lbfgsb\n+from scipy.special import gammaln\n+from scipy._lib._util import check_random_state\n+\n+__all__ = ['gensa']\n+\n+KSPRING = 1.e8\n+BIG_VALUE = 1.e13\n+MAX_REINIT_COUNT = 100\n+\n+\n+class GenSARunner(object):\n+    \"\"\"This class implements the core of the gensa algorithm.\n+\n+    fun : callable\n+        The objective function\n+    x0 : ndarray\n+        The starting coordinates.\n+    bounds : sequence\n+        Bounds for variables.  ``(min, max)`` pairs for each element in ``x``,\n+        defining the lower and upper bounds for the optimizing argument of\n+        `func`. It is required to have ``len(bounds) == len(x)``.\n+        ``len(bounds)`` is used to determine the number of parameters in ``x``.\n+    args : tuple, optional\n+        Any additional fixed parameters needed to\n+        completely specify the objective function.\n+\n+    \"\"\"\n+    def __init__(self, fun, x0, bounds, args=(), seed=None):\n+        self.fun = fun\n+        self.args = args\n+        if x0 is not None and not len(x0) == len(bounds):\n+            raise ValueError('Bounds size does not match x0')\n+        lu = zip(*bounds)\n+        self._lower = np.array(lu[0])\n+        self._upper = np.array(lu[1])\n+        # Checking that bounds are consistent\n+        if not np.all(self._lower < self._upper):\n+            raise ValueError('Bounds are note consistent min < max')\n+        # Initialization of RandomState for reproducible runs if seed provided\n+        self._random_state = check_random_state(seed)\n+        if x0 is None:\n+            x0 = self._lower + self._random_state.random_sample(\n+                    len(bounds)) * (self._upper - self._lower)\n+        self.seed = seed\n+        # Number of maximum sof iteration for local search\n+        self.itsoftmax = x0.size * 6\n+        # Size of the markov chain. Twice the dimension problem is a\n+        # recommended value.\n+        self.markov_length = x0.size * 2\n+        # In case the real value of the global minimum is known\n+        # it can be used as stopping criterion\n+        self.know_real = False\n+        self.real_threshold = -np.inf\n+        # Maximum duration time of execution as a stopping criterion\n+        # Default is unlimited duration\n+        self.maxtime = np.inf\n+        # Maximum number of function call that can be used a stopping criterion\n+        self.maxfuncall = 1e7\n+        # Maximum number of step (main iteration)  that ca be used as\n+        # stopping criterion\n+        self.maxsteps = 500\n+        # Minimum value of annealing temperature reached to perform\n+        # re-annealing\n+        self.temp_restart = 0.1\n+        # Visiting distribution parameter\n+        self.qv = 2.62\n+        # Acceptance parameter value\n+        self.qa = -5.0\n+        # Initial temperature value for annealing\n+        self.temp_start = 5230\n+        # Not yet implemented contraint function that would be used in the\n+        # future\n+        self.has_constraint = False\n+        self.judge_constraint = None\n+        self.factr = 1000\n+        self.pgtol = 1.e-6\n+        self.reps = 1.e-6\n+\n+        self._x = np.array(x0)\n+        self._xrange = self._upper - self._lower\n+        self._xbackup = np.array(self._x)\n+        self._xmini = np.array(self._x)\n+        self._xbuffer = np.zeros(self._x.size)\n+        self._temp = self.temp_start\n+        self._usey = 1\n+        self._xgas = 0.0\n+        self._ygas = 0.0\n+        self._ranbyx = 0.0\n+        self._ranbyy = 0.0\n+        self._sgas = -1.0\n+        self._step_record = 0\n+        self._nbfuncall = 0\n+        self._emini_unchanged = True\n+        self._index_no_emini_update = 0\n+        self._temp_qa = 0\n+\n+    def initialize(self):\n+        \"\"\"\n+        Random coordinate generation in case given initial coordinates\n+        are giving invalid objective value\n+        \"\"\"\n+        self._nbfuncall = 0\n+        if self.markov_length % self._x.size != 0:\n+            raise ValueError('Incorrect markov length.')\n+        in_constraint = True\n+        init_error = True\n+        reinit_counter = 0\n+        while(init_error):\n+            self._energy(self._x)\n+            if self._etot >= BIG_VALUE:\n+                if reinit_counter >= MAX_REINIT_COUNT:\n+                    init_error = False\n+                    self._message = [(\n+                        'Stopping algorithm because function '\n+                        'create NaN or (+/-) inifinity values even with '\n+                        'trying new random parameters')]\n+                    raise ValueError(self._message[0])\n+                self._x = self._lower + self._random_state.random_sample(\n+                        self._x.size) * (self._upper - self._lower)\n+                reinit_counter += 1\n+            else:\n+                init_error = False\n+\n+    def _visiting(self, step):\n+        \"\"\"\n+        Assignement of components values based on visiting distribution.\n+        The way of exploring space depends on the Markov chain stepping\n+        \"\"\"\n+        # It it is the first part of the markov chain\n+        # Changing all components at the same time\n+        if step < self._x.size:\n+            visits = [self._visita() for _ in range(self._x.size)]\n+            visits[visits > 1.e8] = 1.e8 * self._random_state.random_sample()\n+            visits[visits < -1e8] = -1.e8 * self._random_state.random_sample()\n+            self._x = visits + self._xbackup\n+            a = self._x - self._lower\n+            b = np.fmod(a, self._xrange) + self._xrange\n+            self._x = np.fmod(b, self._xrange) + self._lower\n+            self._x[np.fabs(self._x - self._lower) < 1.e-10] += 1.e-10\n+        else:\n+            # Second part of the markov chain\n+            # Now change only one component at a time\n+            visit = self._visita()\n+            if visit > 1.e8:\n+                visit = 1.e8 * self._random_state.random_sample()\n+            elif visit < -1e8:\n+                visit = -1.e8 * self._random_state.random_sample()\n+            index = step - self._x.size\n+            self._x[index] = visit + self._xbackup[index]\n+            a = self._x[index] - self._lower[index]\n+            b = np.fmod(\n+                a, self._xrange[index]) + self._xrange[index]\n+            self._x[index] = np.fmod(\n+                b, self._xrange[index]) + self._lower[index]\n+            if np.fabs(self._x[index] - self._lower[\n+                    index]) < 1.e-10:\n+                self._x[index] += 1.e-10\n+\n+    def _run_markov_chain(self, step):\n+        for j in range(self.markov_length):\n+            if j == 0:\n+                self._emini_unchanged = True\n+            if step == 0 and j == 0:\n+                self._emini_unchanged = False\n+            # Keeping old location before visiting new place\n+            self._xbackup = np.array(self._x)\n+            self._visiting(j)\n+            self._energy(self._x)\n+            if self._etot < self._etot0:\n+                # We get a better energy value\n+                self._etot0 = np.array(self._etot)\n+                if self._etot < self._emini:\n+                    self._emini = np.array(self._etot)\n+                    self._xmini = np.array(self._x)\n+                    self._emini_unchanged = False\n+                    self._index_no_emini_update = 0\n+            else:\n+                # We do not have improvement but do we accept location\n+                r = self._random_state.random_sample()\n+                pqa1 = (self.qa - 1.0) * (\n+                    self._etot - self._etot0) / self._temp_qa + 1.0\n+                if pqa1 < 0.0:\n+                    pqa = 0.0\n+                else:\n+                    pqa = np.exp(np.log(pqa1) / (1.0 - self.qa))\n+                if r > pqa:\n+                    # We reject the new visiting location\n+                    self._x = self._xbackup\n+                else:\n+                    # The new visiting location is accepted\n+                    self._etot0 = self._etot\n+            if self._check_stopping():\n+                self._stop_search()\n+                return 0\n+            if self._index_no_emini_update >= self._index_tol_emini_update - 1:\n+                if j == 0:\n+                    self._emini_markov = np.array(self._etot0)\n+                    self._xmini_markov = np.array(self._x)\n+                else:\n+                    if self._etot0 < self._emini_markov:\n+                        self._emini_markov = np.array(self._etot0)\n+                        self._xmini_markov = np.array(self._x)\n+\n+    def start_search(self):\n+        \"\"\" Start annealing process with eventually re-annealing\n+        \"\"\"\n+        in_constraint = True\n+        self._emini_unchanged = True\n+        self._emini_markov = 0.0\n+        self._xmini_markov = np.zeros(self._x.size)\n+        self._index_no_emini_update = 0\n+        self._index_tol_emini_update = 1000\n+        self._starttime = time.time()\n+        self._emini = np.array(self._etot)\n+        self._xmini = np.array(self._x)\n+        self._etot0 = np.array(self._etot)\n+        if self._etot < self._emini:\n+            self._emini = np.array(self._etot)\n+            self._xmini = np.array(self._x)\n+        self._etot0 = self._etot\n+        if self._check_stopping():\n+            self._stop_search()\n+        self._step_record = 0\n+        self._temp = self.temp_start\n+        tolowtemp = True\n+        while(tolowtemp):\n+            for i in range(self.maxsteps):\n+                # Evaluating iteration artificial temperature\n+                s = float(i) + 2.0\n+                t1 = np.exp((self.qv - 1) * np.log(2.0)) - 1.0\n+                t2 = np.exp((self.qv - 1) * np.log(s)) - 1.0\n+                self._temp = self.temp_start * t1 / t2\n+                self._step_record += 1\n+                self._temp_qa = self._temp / float(i + 1)\n+                self._index_no_emini_update += 1\n+                # break out of both for-loop and while loop because, annealing\n+                # and eventually re-anneling reached maximum number of\n+                # iteration set.\n+                if self._step_record == self.maxsteps:\n+                    tolowtemp = False\n+                    break\n+                # Need a re-annealing process? - Restarting main loop\n+                if self._temp < self.temp_restart:\n+                    break\n+                # Starting Markov chain\n+                self._run_markov_chain(i)\n+\n+                # Decision making for performing a local search\n+                # based on the markov chain results\n+                if not self._emini_unchanged:\n+                    temp = np.array(self._xmini)\n+                    etemp = self._ls_energy(temp)\n+                    temp = self._xbuffer\n+                    if etemp < self._emini:\n+                        self._xmini = np.array(temp)\n+                        self._emini = np.array(etemp)\n+                        self._index_no_emini_update = 0\n+                if self._index_no_emini_update >= (\n+                        self._index_tol_emini_update - 1):\n+                    self._emini_markov = np.array(self._ls_energy(\n+                        self._xmini_markov))\n+                    self._index_no_emini_update = 0\n+                    self._index_tol_emini_update = self._x.size\n+                    if self._emini_markov < self._emini:\n+                        self._xmini = np.array(self._xmini_markov)\n+                        self._emini = np.array(self._emini_markov)\n+                if self._check_stopping():\n+                    self._stop_search()\n+                    return 0\n+        self._stop_search()\n+        self._message = [\"Number of iteration reached\"]\n+        return 0\n+\n+    def _check_stopping(self):\n+        \"\"\"Check if the search has to be stopped\n+        \"\"\"\n+        if self.know_real:\n+            if self._emini <= self.realthreshold:\n+                self._message = [\"Known value for minimum reached\"]\n+                return True\n+        self._endtime = time.time()\n+        delta = self._endtime - self._starttime\n+        if delta >= self.maxtime:\n+            self._message = [\"Time limit reached\"]\n+            return True\n+        if self._nbfuncall >= self.maxfuncall:\n+            self._message = [\"Number of function call reached\"]\n+            return True\n+\n+    def _stop_search(self):\n+        \"\"\"Record time stamp when stop searching\n+        \"\"\"\n+        self._endtime = time.time()\n+\n+    def _coordin(self):\n+        \"\"\"Random generation of new coordinates\n+        \"\"\"\n+        self._x = self._random_state.random_sample(\n+            self._x.size) * self._xrange + self._lower\n+\n+    def _energy(self, x):\n+        \"\"\"Calling objective function and adding elasticity if needed\n+        \"\"\"\n+        delta_energy = 0\n+        if self.has_constraint:\n+            in_constraint = self.judge_constraint()\n+            if not in_constraint:\n+                self._etot = BIG_VALUE\n+                return 0\n+        if np.all(np.logical_and(\n+                x >= self._lower, x <= self._upper)):\n+            delta_energy = 0\n+        else:\n+            lcomp = x < self._lower\n+            ucomp = x > self._upper\n+            delta_energy_l = np.fabs(\n+                x[lcomp] - self._lower[lcomp]) * KSPRING\n+            delta_energy_u = np.fabs(\n+                x[ucomp] - self._upper[ucomp]) * KSPRING\n+            delta_energy = np.sum(delta_energy_l) + np.sum(\n+                delta_energy_u)\n+        self._etot = self.fun(x, *self.args)\n+        self._nbfuncall += 1\n+        self._etot = self._etot + delta_energy\n+        if np.isinf(self._etot) or np.isnan(self._etot):\n+            self._etot = BIG_VALUE\n+\n+    def _ls_energy(self, x):\n+        \"\"\"Performing a local search on the current location\n+        \"\"\"\n+        self._xbuffer = np.array(x)\n+        self._smooth_search()\n+        self._x = np.array(self._xbuffer)\n+        return self._fvalue\n+\n+    def _visita(self):\n+        \"\"\"Visiting distribution function\n+        \"\"\"\n+        pi = np.arcsin(1.0) * 2.0\n+        fator1 = np.exp(np.log(self._temp) / (self.qv - 1.0))\n+        fator2 = np.exp((4.0 - self.qv) * np.log(self.qv - 1.0))\n+        fator3 = np.exp((2.0 - self.qv) * np.log(2.0) / (self.qv - 1.0))\n+        fator4 = np.sqrt(pi) * fator1 * fator2 / (fator3 * (3.0 - self.qv))\n+        fator5 = 1.0 / (self.qv - 1.0) - 0.5\n+        d1 = 2.0 - fator5\n+        fator6 = pi * (1.0 - fator5) / \\\n+            np.sin(pi * (1.0 - fator5)) / np.exp(gammaln(d1))\n+        sigmax = np.exp(-(self.qv - 1.0) *\n+                        np.log(fator6 / fator4) / (3.0 - self.qv))\n+        x = sigmax * self._yygas()\n+        y = self._yygas()\n+        den = np.exp(\n+            (self.qv - 1.0) * np.log((np.fabs(y))) / (3.0 - self.qv))\n+        return x / den\n+\n+    def _fobjective(self, x):\n+        \"\"\"Wrapper to the objective function\n+        \"\"\"\n+        self._x = np.array(x)\n+        self._energy(self._x)\n+        return self._etot\n+\n+    def _smooth_search(self):\n+        \"\"\"Local search implementation using setulb core function\n+        \"\"\"\n+        m = 5\n+        iteration = 0\n+        f = 0\n+        n = self._x.size\n+        ndb = np.zeros(self._x.size)\n+        ndb.fill(2)\n+        iprint = -1\n+        x = np.array(self._xbuffer, np.float64)\n+        f = np.array(0.0, np.float64)\n+        l = np.array(self._lower, np.float64)\n+        u = np.array(self._upper, np.float64)\n+        g = np.zeros(n, np.float64)\n+        wa = np.zeros(2 * m * n + 5 * n + 11 * m * m + 8 * m, np.float64)\n+        iwa = np.zeros(3 * n, np.int32)\n+        task = np.zeros(1, 'S60')\n+        csave = np.zeros(1, 'S60')\n+        lsave = np.zeros(4, np.int32)\n+        isave = np.zeros(44, np.int32)\n+        dsave = np.zeros(29, np.float64)\n+        task[:] = 'START'\n+        if self.itsoftmax < 100:\n+            self.itsoftmax = 100\n+        elif self.itsoftmax > 1000:\n+            self.itsoftmax = 1000\n+        while True:\n+            if iteration >= self.itsoftmax:\n+                self._xbuffer = np.array(x)\n+                self._fvalue = f\n+                return\n+            _lbfgsb.setulb(m, x, l, u, ndb,\n+                           f, g, self.factr, self.pgtol, wa, iwa, task,\n+                           iprint, csave, lsave, isave, dsave, 100)\n+            iteration += 1\n+            task_str = task.tostring()\n+            if task_str.startswith(b'FG'):\n+                self._xbuffer = np.array(x)\n+                f = self._fobjective(self._xbuffer)\n+                if self.know_real:\n+                    if f <= self.real_threshold:\n+                        self._xbuffer = np.array(x)\n+                        self._fvalue = f\n+                        return\n+                g = np.array(self._compute_gradient(x), np.float64)\n+            elif task_str.startswith(b'NEW_X'):\n+                pass\n+            else:\n+                self._fvalue = f\n+                self._xbuffer = np.array(x)\n+                return\n+\n+    def _compute_gradient(self, x):\n+        \"\"\"Computation of numerical derivatives for local search\n+        \"\"\"\n+        g = np.zeros(self._x.size, np.float64)\n+        for i in range(self._x.size):\n+            x1 = np.array(x)\n+            x2 = np.array(x)\n+            respl = self.reps\n+            respr = self.reps\n+            x1[i] = x[i] + respr\n+            if x1[i] > self._upper[i]:\n+                x1[i] = self._upper[i]\n+                respr = x1[i] - x[i]\n+            x2[i] = x[i] - respl\n+            if x2[i] < self._lower[i]:\n+                x2[i] = self._lower[i]\n+                respl = x[i] - x2[i]\n+            f1 = self._fobjective(x1)\n+            f2 = self._fobjective(x2)\n+            g[i] = ((f1 - f2)) / (respl + respr)\n+        idx = np.logical_or(np.isnan(g), np.isinf(g))\n+        g[idx] = 101.0\n+        return g\n+\n+    def _yygas(self):\n+        if self._usey == 1:\n+            enter = True\n+            while(enter or (self._sgas <= 0 or self._sgas >= 1)):\n+                enter = False\n+                self._xgas = self._random_state.random_sample() * 2.0 - 1.0\n+                self._ygas = self._random_state.random_sample() * 2.0 - 1.0\n+                self._sgas = self._xgas * self._xgas + self._ygas * self._ygas\n+            root = np.sqrt(-2.0 / self._sgas * np.log(self._sgas))\n+            self._ranbyx = self._xgas * root\n+            self._ranbyy = self._ygas * root\n+            retval = self._ranbyy\n+            self._usey = 0\n+        else:\n+            retval = self._ranbyx\n+            self._usey = 1\n+        return retval\n+\n+    @property\n+    def result(self):\n+        \"\"\" The OptimizeResult \"\"\"\n+        res = OptimizeResult()\n+        res.x = self._xmini\n+        res.fun = self._fvalue\n+        res.message = self._message\n+        res.nit = self._step_record\n+        return res\n+\n+def gensa(func, x0, bounds, niter=500, T=5230., visitparam=2.62,\n+        acceptparam=-5.0, maxcall=1e7, args=(), seed=None):\n+    \"\"\"\n+    Find the global minimum of a function using the Generalized Simulated\n+    Annealing algorithm\n+\n+    Parameters\n+    ----------\n+    func : callable\n+        The objective function to be minimized.  Must be in the form\n+        ``f(x, *args)``, where ``x`` is the argument in the form of a 1-D array\n+        and ``args`` is a  tuple of any additional fixed parameters needed to\n+        completely specify the function.\n+    bounds : sequence\n+        Bounds for variables.  ``(min, max)`` pairs for each element in ``x``,\n+        defining the lower and upper bounds for the optimizing argument of\n+        `func`. It is required to have ``len(bounds) == len(x)``.\n+        ``len(bounds)`` is used to determine the number of parameters in ``x``.\n+    niter : int, optional\n+        The number of gensa iterations\n+    T : float, optional\n+        Initial value for temperature. The higher the initial temperature, the\n+        the higher the probability of hill-climbing for the initial iterations.\n+    visitparam : float, optional\n+        Parameter for visiting distribution. Higher value means the\n+        visiting distribution has a heavier tail which makes the\n+        algorithm jump to a farer region. The value range is ]0; 3]\n+    acceptparam : float, optional\n+        Parameter for acceptance distribution. It is used to control the\n+        probability of acceptance. The lower the acceptance parameter, the\n+        smaller the probability of acceptance. It has to be any negative value.\n+    maxcall : int, optional","path":"scipy/optimize/_gensa.py","position":512,"original_position":512,"commit_id":"87c496ce49c8e497f7503dd8463a47ac215e52c3","original_commit_id":"87c496ce49c8e497f7503dd8463a47ac215e52c3","user":{"login":"andyfaff","id":702934,"avatar_url":"https://avatars.githubusercontent.com/u/702934?v=3","gravatar_id":"","url":"https://api.github.com/users/andyfaff","html_url":"https://github.com/andyfaff","followers_url":"https://api.github.com/users/andyfaff/followers","following_url":"https://api.github.com/users/andyfaff/following{/other_user}","gists_url":"https://api.github.com/users/andyfaff/gists{/gist_id}","starred_url":"https://api.github.com/users/andyfaff/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andyfaff/subscriptions","organizations_url":"https://api.github.com/users/andyfaff/orgs","repos_url":"https://api.github.com/users/andyfaff/repos","events_url":"https://api.github.com/users/andyfaff/events{/privacy}","received_events_url":"https://api.github.com/users/andyfaff/received_events","type":"User","site_admin":false},"body":"I'd use `maxfun` here, it's used elsewhere in `scipy.optimize`. It's made clear here that it's a soft limit.","created_at":"2016-09-28T06:21:28Z","updated_at":"2016-09-28T06:21:28Z","html_url":"https://github.com/scipy/scipy/pull/6569#discussion_r80849370","pull_request_url":"https://api.github.com/repos/scipy/scipy/pulls/6569","_links":{"self":{"href":"https://api.github.com/repos/scipy/scipy/pulls/comments/80849370"},"html":{"href":"https://github.com/scipy/scipy/pull/6569#discussion_r80849370"},"pull_request":{"href":"https://api.github.com/repos/scipy/scipy/pulls/6569"}}},"pull_request":{"url":"https://api.github.com/repos/scipy/scipy/pulls/6569","id":84832168,"html_url":"https://github.com/scipy/scipy/pull/6569","diff_url":"https://github.com/scipy/scipy/pull/6569.diff","patch_url":"https://github.com/scipy/scipy/pull/6569.patch","issue_url":"https://api.github.com/repos/scipy/scipy/issues/6569","number":6569,"state":"open","locked":false,"title":"ENH: Adding simulated annealing method (and related benchmark update)","user":{"login":"sgubianpm","id":17434537,"avatar_url":"https://avatars.githubusercontent.com/u/17434537?v=3","gravatar_id":"","url":"https://api.github.com/users/sgubianpm","html_url":"https://github.com/sgubianpm","followers_url":"https://api.github.com/users/sgubianpm/followers","following_url":"https://api.github.com/users/sgubianpm/following{/other_user}","gists_url":"https://api.github.com/users/sgubianpm/gists{/gist_id}","starred_url":"https://api.github.com/users/sgubianpm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sgubianpm/subscriptions","organizations_url":"https://api.github.com/users/sgubianpm/orgs","repos_url":"https://api.github.com/users/sgubianpm/repos","events_url":"https://api.github.com/users/sgubianpm/events{/privacy}","received_events_url":"https://api.github.com/users/sgubianpm/received_events","type":"User","site_admin":false},"body":"Dear All,\r\nA PR proposing the new simulated annealing including code additions in the benchmark section.\r\nThanks a lot !","created_at":"2016-09-12T08:27:20Z","updated_at":"2016-09-28T06:21:28Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/scipy/scipy/pulls/6569/commits","review_comments_url":"https://api.github.com/repos/scipy/scipy/pulls/6569/comments","review_comment_url":"https://api.github.com/repos/scipy/scipy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/scipy/scipy/issues/6569/comments","statuses_url":"https://api.github.com/repos/scipy/scipy/statuses/87c496ce49c8e497f7503dd8463a47ac215e52c3","head":{"label":"sgubianpm:adding-gensa-benchmarked","ref":"adding-gensa-benchmarked","sha":"87c496ce49c8e497f7503dd8463a47ac215e52c3","user":{"login":"sgubianpm","id":17434537,"avatar_url":"https://avatars.githubusercontent.com/u/17434537?v=3","gravatar_id":"","url":"https://api.github.com/users/sgubianpm","html_url":"https://github.com/sgubianpm","followers_url":"https://api.github.com/users/sgubianpm/followers","following_url":"https://api.github.com/users/sgubianpm/following{/other_user}","gists_url":"https://api.github.com/users/sgubianpm/gists{/gist_id}","starred_url":"https://api.github.com/users/sgubianpm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sgubianpm/subscriptions","organizations_url":"https://api.github.com/users/sgubianpm/orgs","repos_url":"https://api.github.com/users/sgubianpm/repos","events_url":"https://api.github.com/users/sgubianpm/events{/privacy}","received_events_url":"https://api.github.com/users/sgubianpm/received_events","type":"User","site_admin":false},"repo":{"id":52374389,"name":"scipy","full_name":"sgubianpm/scipy","owner":{"login":"sgubianpm","id":17434537,"avatar_url":"https://avatars.githubusercontent.com/u/17434537?v=3","gravatar_id":"","url":"https://api.github.com/users/sgubianpm","html_url":"https://github.com/sgubianpm","followers_url":"https://api.github.com/users/sgubianpm/followers","following_url":"https://api.github.com/users/sgubianpm/following{/other_user}","gists_url":"https://api.github.com/users/sgubianpm/gists{/gist_id}","starred_url":"https://api.github.com/users/sgubianpm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sgubianpm/subscriptions","organizations_url":"https://api.github.com/users/sgubianpm/orgs","repos_url":"https://api.github.com/users/sgubianpm/repos","events_url":"https://api.github.com/users/sgubianpm/events{/privacy}","received_events_url":"https://api.github.com/users/sgubianpm/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/sgubianpm/scipy","description":"Scipy library main repository","fork":true,"url":"https://api.github.com/repos/sgubianpm/scipy","forks_url":"https://api.github.com/repos/sgubianpm/scipy/forks","keys_url":"https://api.github.com/repos/sgubianpm/scipy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sgubianpm/scipy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sgubianpm/scipy/teams","hooks_url":"https://api.github.com/repos/sgubianpm/scipy/hooks","issue_events_url":"https://api.github.com/repos/sgubianpm/scipy/issues/events{/number}","events_url":"https://api.github.com/repos/sgubianpm/scipy/events","assignees_url":"https://api.github.com/repos/sgubianpm/scipy/assignees{/user}","branches_url":"https://api.github.com/repos/sgubianpm/scipy/branches{/branch}","tags_url":"https://api.github.com/repos/sgubianpm/scipy/tags","blobs_url":"https://api.github.com/repos/sgubianpm/scipy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sgubianpm/scipy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sgubianpm/scipy/git/refs{/sha}","trees_url":"https://api.github.com/repos/sgubianpm/scipy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sgubianpm/scipy/statuses/{sha}","languages_url":"https://api.github.com/repos/sgubianpm/scipy/languages","stargazers_url":"https://api.github.com/repos/sgubianpm/scipy/stargazers","contributors_url":"https://api.github.com/repos/sgubianpm/scipy/contributors","subscribers_url":"https://api.github.com/repos/sgubianpm/scipy/subscribers","subscription_url":"https://api.github.com/repos/sgubianpm/scipy/subscription","commits_url":"https://api.github.com/repos/sgubianpm/scipy/commits{/sha}","git_commits_url":"https://api.github.com/repos/sgubianpm/scipy/git/commits{/sha}","comments_url":"https://api.github.com/repos/sgubianpm/scipy/comments{/number}","issue_comment_url":"https://api.github.com/repos/sgubianpm/scipy/issues/comments{/number}","contents_url":"https://api.github.com/repos/sgubianpm/scipy/contents/{+path}","compare_url":"https://api.github.com/repos/sgubianpm/scipy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sgubianpm/scipy/merges","archive_url":"https://api.github.com/repos/sgubianpm/scipy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sgubianpm/scipy/downloads","issues_url":"https://api.github.com/repos/sgubianpm/scipy/issues{/number}","pulls_url":"https://api.github.com/repos/sgubianpm/scipy/pulls{/number}","milestones_url":"https://api.github.com/repos/sgubianpm/scipy/milestones{/number}","notifications_url":"https://api.github.com/repos/sgubianpm/scipy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sgubianpm/scipy/labels{/name}","releases_url":"https://api.github.com/repos/sgubianpm/scipy/releases{/id}","deployments_url":"https://api.github.com/repos/sgubianpm/scipy/deployments","created_at":"2016-02-23T16:39:07Z","updated_at":"2016-02-23T16:39:17Z","pushed_at":"2016-09-23T11:38:53Z","git_url":"git://github.com/sgubianpm/scipy.git","ssh_url":"git@github.com:sgubianpm/scipy.git","clone_url":"https://github.com/sgubianpm/scipy.git","svn_url":"https://github.com/sgubianpm/scipy","homepage":"http://scipy.org/scipylib/","size":59735,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"scipy:master","ref":"master","sha":"fd7af6d2ec716d67026b9621119ef90e65504771","user":{"login":"scipy","id":288277,"avatar_url":"https://avatars.githubusercontent.com/u/288277?v=3","gravatar_id":"","url":"https://api.github.com/users/scipy","html_url":"https://github.com/scipy","followers_url":"https://api.github.com/users/scipy/followers","following_url":"https://api.github.com/users/scipy/following{/other_user}","gists_url":"https://api.github.com/users/scipy/gists{/gist_id}","starred_url":"https://api.github.com/users/scipy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scipy/subscriptions","organizations_url":"https://api.github.com/users/scipy/orgs","repos_url":"https://api.github.com/users/scipy/repos","events_url":"https://api.github.com/users/scipy/events{/privacy}","received_events_url":"https://api.github.com/users/scipy/received_events","type":"Organization","site_admin":false},"repo":{"id":1460385,"name":"scipy","full_name":"scipy/scipy","owner":{"login":"scipy","id":288277,"avatar_url":"https://avatars.githubusercontent.com/u/288277?v=3","gravatar_id":"","url":"https://api.github.com/users/scipy","html_url":"https://github.com/scipy","followers_url":"https://api.github.com/users/scipy/followers","following_url":"https://api.github.com/users/scipy/following{/other_user}","gists_url":"https://api.github.com/users/scipy/gists{/gist_id}","starred_url":"https://api.github.com/users/scipy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scipy/subscriptions","organizations_url":"https://api.github.com/users/scipy/orgs","repos_url":"https://api.github.com/users/scipy/repos","events_url":"https://api.github.com/users/scipy/events{/privacy}","received_events_url":"https://api.github.com/users/scipy/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/scipy/scipy","description":"Scipy library main repository","fork":false,"url":"https://api.github.com/repos/scipy/scipy","forks_url":"https://api.github.com/repos/scipy/scipy/forks","keys_url":"https://api.github.com/repos/scipy/scipy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/scipy/scipy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/scipy/scipy/teams","hooks_url":"https://api.github.com/repos/scipy/scipy/hooks","issue_events_url":"https://api.github.com/repos/scipy/scipy/issues/events{/number}","events_url":"https://api.github.com/repos/scipy/scipy/events","assignees_url":"https://api.github.com/repos/scipy/scipy/assignees{/user}","branches_url":"https://api.github.com/repos/scipy/scipy/branches{/branch}","tags_url":"https://api.github.com/repos/scipy/scipy/tags","blobs_url":"https://api.github.com/repos/scipy/scipy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/scipy/scipy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/scipy/scipy/git/refs{/sha}","trees_url":"https://api.github.com/repos/scipy/scipy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/scipy/scipy/statuses/{sha}","languages_url":"https://api.github.com/repos/scipy/scipy/languages","stargazers_url":"https://api.github.com/repos/scipy/scipy/stargazers","contributors_url":"https://api.github.com/repos/scipy/scipy/contributors","subscribers_url":"https://api.github.com/repos/scipy/scipy/subscribers","subscription_url":"https://api.github.com/repos/scipy/scipy/subscription","commits_url":"https://api.github.com/repos/scipy/scipy/commits{/sha}","git_commits_url":"https://api.github.com/repos/scipy/scipy/git/commits{/sha}","comments_url":"https://api.github.com/repos/scipy/scipy/comments{/number}","issue_comment_url":"https://api.github.com/repos/scipy/scipy/issues/comments{/number}","contents_url":"https://api.github.com/repos/scipy/scipy/contents/{+path}","compare_url":"https://api.github.com/repos/scipy/scipy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/scipy/scipy/merges","archive_url":"https://api.github.com/repos/scipy/scipy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/scipy/scipy/downloads","issues_url":"https://api.github.com/repos/scipy/scipy/issues{/number}","pulls_url":"https://api.github.com/repos/scipy/scipy/pulls{/number}","milestones_url":"https://api.github.com/repos/scipy/scipy/milestones{/number}","notifications_url":"https://api.github.com/repos/scipy/scipy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/scipy/scipy/labels{/name}","releases_url":"https://api.github.com/repos/scipy/scipy/releases{/id}","deployments_url":"https://api.github.com/repos/scipy/scipy/deployments","created_at":"2011-03-09T18:52:03Z","updated_at":"2016-09-28T05:43:10Z","pushed_at":"2016-09-28T03:23:59Z","git_url":"git://github.com/scipy/scipy.git","ssh_url":"git@github.com:scipy/scipy.git","clone_url":"https://github.com/scipy/scipy.git","svn_url":"https://github.com/scipy/scipy","homepage":"http://scipy.org/scipylib/","size":77329,"stargazers_count":2600,"watchers_count":2600,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1514,"mirror_url":null,"open_issues_count":962,"forks":1514,"open_issues":962,"watchers":2600,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/scipy/scipy/pulls/6569"},"html":{"href":"https://github.com/scipy/scipy/pull/6569"},"issue":{"href":"https://api.github.com/repos/scipy/scipy/issues/6569"},"comments":{"href":"https://api.github.com/repos/scipy/scipy/issues/6569/comments"},"review_comments":{"href":"https://api.github.com/repos/scipy/scipy/pulls/6569/comments"},"review_comment":{"href":"https://api.github.com/repos/scipy/scipy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/scipy/scipy/pulls/6569/commits"},"statuses":{"href":"https://api.github.com/repos/scipy/scipy/statuses/87c496ce49c8e497f7503dd8463a47ac215e52c3"}}}}
{"actor":{"display_login":"andyfaff"}}