#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Pywr documentation build configuration file, created by
# sphinx-quickstart on Mon Jun  8 20:10:37 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

import sys
import os
import shlex
import alabaster

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.mathjax',
    'matplotlib.sphinxext.plot_directive',
    'alabaster',
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = 'Pywr'
copyright = '2015, Joshua Arnott'
author = 'Joshua Arnott'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.1'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'alabaster'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
html_theme_options = {
    'github_user': 'pywr',
    'github_repo': 'pywr',
}

# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = [alabaster.get_path()]

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
html_sidebars = {
    '**': [
        'about.html',
        'navigation.html',
        'relations.html',
        'searchbox.html',
        'donate.html',
    ]
}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
#   'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
#   'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr'
#html_search_language = 'en'

# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}

# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'

# Output file base name for HTML help builder.
htmlhelp_basename = 'Pywrdoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#'preamble': '',

# Latex figure (float) alignment
#'figure_align': 'htbp',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
  (master_doc, 'Pywr.tex', 'Pywr Documentation',
   'Joshua Arnott', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, 'pywr', 'Pywr Documentation',
     [author], 1)
]

# If true, show URL addresses after external links.
#man_show_urls = False

# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
  (master_doc, 'Pywr', 'Pywr Documentation',
   author, 'Pywr', 'One line description of project.',
   'Miscellaneous'),
]

# Documents to append as an appendix to all manuals.
#texinfo_appendices = []

# If false, no module index is generated.
#texinfo_domain_indices = True

# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False


from __future__ import print_function

import string

from matplotlib import inspect

class FormatDict(dict):
    """Adapted from http://stackoverflow.com/questions/11283961/partial-string-formatting"""

    def __missing__(self, key):
        return "{" + key + "}"

class DocReplacer(object):
    """Decorator object for replacing patterns in docstrings using string.format."""

    def __init__(self, auto_dedent=True, allow_partial_formatting=False, **doc_dict):
        '''
        Parameters
        -------------
        auto_indent : bool
            Flag for automatically indenting the replaced lines to the level of the docstring.
        allow_partial_formatting : bool
            Emnables partial formatting (i.e., not all keys are available in the dictionary)
        doc_dict : kwargs
            Pattern in docstring that a key in this dict will be replaced by the corresponding values.
        Example
        -------------
        TODO: Update this documentation
        @DocReplacer({'p1': 'p1 : int\n\tFirst parameter'})
        def foo(p1):
            """
            Some functions.

            Params:
            {p1}
            """
        will result in foo's docstring being:
            """
            Some functions.

            Params:
            p1 : int
                First parameter
            """
        '''
        self.doc_dict = doc_dict
        self.auto_dedent = auto_dedent
        self.allow_partial_formatting = allow_partial_formatting

    def __call__(self, func):
        if func.__doc__:
            doc = func.__doc__
            if self.auto_dedent:
                doc = inspect.cleandoc(doc)
            func.__doc__ = self._format(doc)
        return func

    def replace(self):
        """Reformat values inside the self.doc_dict using self.doc_dict

        TODO: Make support for partial_formatting
        """
        doc_dict = self.doc_dict.copy()
        for k, v in doc_dict.items():
            if '{' and '}' in v:
                self.doc_dict[k] = v.format(**doc_dict)

    def update(self, *args, **kwargs):
        "Assume self.params is a dict and update it with supplied args"
        self.doc_dict.update(*args, **kwargs)

    def _format(self, doc):
        """ Formats the docstring using self.doc_dict """
        if self.allow_partial_formatting:
            mapping = FormatDict(self.doc_dict)
        else:
            mapping = self.doc_dict
        formatter = string.Formatter()
        return formatter.vformat(doc, (), mapping)


"""Helicopter hovering task."""

from .Domain import Domain
import numpy as np
import rlpy.Tools.transformations as trans
from rlpy.Tools.GeneralTools import cartesian
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, Circle, Ellipse
from mpl_toolkits.mplot3d import proj3d

__copyright__ = "Copyright 2013, RLPy http://acl.mit.edu/RLPy"
__credits__ = ["Alborz Geramifard", "Robert H. Klein", "Christoph Dann",
               "William Dabney", "Jonathan P. How"]
__license__ = "BSD 3-Clause"
__author__ = "Christoph Dann <cdann@cdann.de>"

class Arrow3D(FancyArrowPatch):

    """
    Helper class for plotting arrows in 3d
    """

    def __init__(self, xs, ys, zs, *args, **kwargs):
        FancyArrowPatch.__init__(self, (0, 0), (0, 0), *args, **kwargs)
        self._verts3d = xs, ys, zs

    def draw(self, renderer):
        xs3d, ys3d, zs3d = self._verts3d
        xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
        self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
        FancyArrowPatch.draw(self, renderer)

class HelicopterHoverExtended(Domain):

    """
    Implementation of a simulator that models one of the Stanford
    autonomous helicopters (an XCell Tempest helicopter) in the flight
    regime close to hover.

    Adapted from the
    `RL-Community Java Implementation <http://library.rl-community.org/wiki/Helicopter_(Java)>`_

    **STATE:**
    The state of the helicopter is described by a 20-dimensional vector
    with the following entries:

    * 0: xerr [helicopter x-coord position - desired x-coord position] -- helicopter's x-axis points forward
    * 1: yerr [helicopter y-coord position - desired y-coord position] -- helicopter's y-axis points to the right
    * 2: zerr [helicopter z-coord position - desired z-coord position] -- helicopter's z-axis points down
    * 3: u [forward velocity]
    * 4: v [sideways velocity (to the right)]
    * 5: w [downward velocity]
    * 6: p [angular rate around helicopter's x axis]
    * 7: q [angular rate around helicopter's y axis]
    * 8: r [angular rate around helicopter's z axis]
    * 9-12: orientation of heli in world as quaterion
    * 13-18: current noise due to gusts (usually not observable!)
    * 19: t number of timesteps in current episode

    **REFERENCE:**

    .. seealso::
        Abbeel, P., Ganapathi, V. & Ng, A. Learning vehicular dynamics,
        with application to modeling helicopters.
        Advances in Neural Information Systems (2006).

    """

    MAX_POS = 20.  #: [m]  maximum deviation in position in each dimension
    MAX_VEL = 10.  #: [m/s] maximum velocity in each dimension
    MAX_ANG_RATE = 4 * np.pi  # : maximum angular velocity
    MAX_ANG = 1.
    WIND_MAX = 5.  # : maximum gust indensity
    MIN_QW_BEFORE_HITTING_TERMINAL_STATE = np.cos(30. / 2. * np.pi / 180.)

    wind = np.array([.0, .0, 0.])  #: wind in neutral orientation
    discount_factor = 0.95  #: discount factor
    gust_memory = 0.8
    domain_fig = None

    episodeCap = 6000
    # model specific parameters from the learned model
    noise_std = np.array([0.1941, 0.2975, 0.6058, 0.1508, 0.2492, 0.0734])
    drag_vel_body = np.array([.18, .43, .49])
    drag_ang_rate = np.array([12.78, 10.12, 8.16])
    u_coeffs = np.array([33.04, -33.32, 70.54, -42.15])
    tail_rotor_side_thrust = -0.54

    dt = 0.01  #: length of one timestep
    continuous_dims = np.arange(20)
    statespace_limits_full = np.array([[-MAX_POS, MAX_POS]] * 3
                                      + [[-MAX_VEL, MAX_VEL]] * 3
                                      + [[-MAX_ANG_RATE, MAX_ANG_RATE]] * 3
                                      + [[-MAX_ANG, MAX_ANG]] * 4
                                      + [[-2., 2.]] * 6
                                      + [[0, episodeCap]])
    statespace_limits = statespace_limits_full

    # create all combinations of possible actions
    _action_bounds = np.array([[-2., 2.]] * 4)
    # maximum action: 2
    _actions_dim = np.array(
        [[-.2, -0.05, 0.05, 0.2]] * 3 + [[0., 0.15, 0.3, 0.5]])
    actions = cartesian(list(_actions_dim))  #: all possible actions
    actions_num = np.prod(actions.shape[0])

    def __init__(self, noise_level=1., discount_factor=0.95):
        self.noise_level = noise_level
        self.discount_factor = discount_factor
        super(HelicopterHoverExtended, self).__init__()

    def s0(self):
        self.state = np.zeros((20))
        self.state[9] = 1.
        return self.state.copy(), self.isTerminal(), self.possibleActions()

    def isTerminal(self):
        s = self.state
        if np.any(self.statespace_limits_full[:9, 0] > s[:9]) or np.any(self.statespace_limits_full[:9, 1] < s[:9]):
            return True

        if len(s) <= 12:
            w = np.sqrt(1. - np.sum(s[9:12] ** 2))
        else:
            w = s[9]

        return np.abs(w) < self.MIN_QW_BEFORE_HITTING_TERMINAL_STATE

    def _get_reward(self):
        s = self.state
        if self.isTerminal():
            r = -np.sum(self.statespace_limits[:9, 1] ** 2)
            #r -= np.sum(self.statespace_limits[10:12, 1] ** 2)
            r -= (1. - self.MIN_QW_BEFORE_HITTING_TERMINAL_STATE ** 2)
            return r * (self.episodeCap - s[-1])
        else:
            return -np.sum(s[:9] ** 2) - np.sum(s[10:12] ** 2)

    def possibleActions(self, s=None):
        return np.arange(self.actions_num)

    def step(self, a):
        a = self.actions[a]
        # make sure the actions are not beyond their limits
        a = np.maximum(self._action_bounds[:, 0], np.minimum(a,
                       self._action_bounds[:, 1]))

        pos, vel, ang_rate, ori_bases, q = self._state_in_world(self.state)
        t = self.state[-1]
        gust_noise = self.state[13:19]
        gust_noise = (self.gust_memory * gust_noise
                      + (1. - self.gust_memory) * self.random_state.randn(6) * self.noise_level * self.noise_std)
        # update noise which simulates gusts
        for i in range(10):
            # Euler integration
            # position
            pos += self.dt * vel
            # compute acceleration on the helicopter
            vel_body = self._in_world_coord(vel, q)
            wind_body = self._in_world_coord(self.wind, q)
            wind_body[-1] = 0.  # the java implementation
                                # has it this way
            acc_body = -self.drag_vel_body * (vel_body + wind_body)
            acc_body[-1] += self.u_coeffs[-1] * a[-1]
            acc_body[1] += self.tail_rotor_side_thrust
            acc_body += gust_noise[:3]
            acc = self._in_body_coord(acc_body, q)
            acc[-1] += 9.81  # gravity

            # velocity
            vel += self.dt * acc
            # orientation
            tmp = self.dt * ang_rate
            qdt = trans.quaternion_about_axis(np.linalg.norm(tmp), tmp)
            q = trans.quaternion_multiply(q, qdt)
            #assert np.allclose(1., np.sum(q**2))
            # angular accelerations
            ang_acc = -ang_rate * self.drag_ang_rate + \
                self.u_coeffs[:3] * a[:3]
            ang_acc += gust_noise[3:]

            ang_rate += self.dt * ang_acc

        st = np.zeros_like(self.state)
        st[:3] = -self._in_body_coord(pos, q)
        st[3:6] = self._in_body_coord(vel, q)
        st[6:9] = ang_rate
        st[9:13] = q
        st[13:19] = gust_noise
        st[-1] = t + 1
        self.state = st.copy()
        return (
            self._get_reward(), st, self.isTerminal(), self.possibleActions()
        )

    def _state_in_world(self, s):
        """
        transforms state from body coordinates in world coordinates
        .. warning::

            angular rate still in body frame!

        """
        pos_body = s[:3]
        vel_body = s[3:6]
        ang_rate = s[6:9].copy()
        q = s[9:13].copy()

        pos = self._in_world_coord(-pos_body, q)
        vel = self._in_world_coord(vel_body, q)

        rot = trans.quaternion_matrix(trans.quaternion_conjugate(q))[:3, :3]
        return pos, vel, ang_rate, rot, q

    def _in_body_coord(self, p, q):
        """
        q is the inverse quaternion of the rotation of the helicopter in world coordinates
        """
        q_pos = np.zeros((4))
        q_pos[1:] = p
        q_p = trans.quaternion_multiply(trans.quaternion_multiply(q, q_pos),
                                        trans.quaternion_conjugate(q))
        return q_p[1:]

    def _in_world_coord(self, p, q):
        """
        q is the inverse quaternion of the rotation of the helicopter in world coordinates
        """
        return self._in_body_coord(p, trans.quaternion_conjugate(q))

    def showDomain(self, a=None):
        s = self.state
        if a is not None:
            a = self.actions[a].copy() * 3  # amplify for visualization
        pos, vel, ang_rate, ori_bases, _ = self._state_in_world(s)
        coords = np.zeros((3, 3, 2)) + pos[None, :, None]
        coords[:, :, 1] += ori_bases * 4
        u, v = np.mgrid[0:2 * np.pi:10j, 0:2:1.]

        # rotor coordinates
        coord = np.zeros([3] + list(u.shape))
        coord[0] = .1 * np.sin(u) * v
        coord[1] = 0.
        coord[2] = .1 * np.cos(u) * v
        coord[0] -= 0.8
        coord_side = np.einsum("ij,jkl->ikl", np.linalg.pinv(ori_bases), coord)
        coord_side += pos[:, None, None]

        coord = np.zeros([3] + list(u.shape))
        coord[0] = .6 * np.cos(u) * v
        coord[1] = .6 * np.sin(u) * v
        coord[2] = -.4
        coord_main = np.einsum("ij,jkl->ikl", np.linalg.pinv(ori_bases), coord)
        coord_main += pos[:, None, None]

        style = dict(fc="r", ec="r", lw=2., head_width=0.05, head_length=0.1)
        if self.domain_fig is None:
            self.domain_fig = plt.figure(figsize=(12, 8))
            # action axes
            ax1 = plt.subplot2grid((1, 3), (0, 0), frameon=False)
            ax1.get_xaxis().set_visible(False)
            ax1.get_yaxis().set_visible(False)
            lim = 2  # self.MAX_POS
            ax1.set_xlim(-lim, lim)
            ax1.set_ylim(-lim, lim)
            if a is None:
                a = np.zeros((4))
            # main rotor
            ax1.add_artist(Circle(np.zeros((2)), radius=0.6))
            ax1.add_artist(Ellipse(np.array([0, 1.5]), height=0.3, width=0.02))
            # TODO make sure the actions are plotted right
            # main rotor direction?
            arr1 = ax1.arrow(0, 0, a[0], 0, **style)
            arr2 = ax1.arrow(0, 0, 0, a[1], **style)
            # side rotor throttle?
            arr3 = ax1.arrow(0, 1.5, a[2], 0, **style)
            # main rotor throttle
            arr4 = ax1.arrow(1.5, 0, 0, a[3], **style)
            ax1.set_aspect("equal")

            self.action_arrows = (arr1, arr2, arr3, arr4)
            self.action_ax = ax1
            #ax = self.domain_fig.gca(projection='3d')
            ax = plt.subplot2grid((1, 3), (0, 1), colspan=2, projection='3d')
            ax.view_init(elev=np.pi)
            # print origin
            x = Arrow3D([0, 2], [0, 0], [0, 0], mutation_scale=30, lw=1,
                        arrowstyle="-|>", color="r")
            y = Arrow3D([0, 0], [0, 2], [0, 0], mutation_scale=30, lw=1,
                        arrowstyle="-|>", color="b")
            z = Arrow3D([0, 0], [0, 0], [0, 2], mutation_scale=30, lw=1,
                        arrowstyle="-|>", color="g")
            ax.add_artist(x)
            ax.add_artist(y)
            ax.add_artist(z)

            # print helicopter coordinate axes
            x = Arrow3D(*coords[0], mutation_scale=30, lw=2, arrowstyle="-|>",
                        color="r")
            y = Arrow3D(*coords[1], mutation_scale=30, lw=2, arrowstyle="-|>",
                        color="b")
            z = Arrow3D(*coords[2], mutation_scale=30, lw=2, arrowstyle="-|>",
                        color="g")
            ax.add_artist(x)
            ax.add_artist(y)
            ax.add_artist(z)
            self.heli_arrows = (x, y, z)

            self._wframe_main = ax.plot_wireframe(coord_main[0], coord_main[1],
                                                  coord_main[2], color="k")
            self._wframe_side = ax.plot_wireframe(coord_side[0], coord_side[1],
                                                  coord_side[2], color="k")
            self._ax = ax
            ax.set_aspect("equal")
            lim = 5  # self.MAX_POS
            ax.set_xlim(-lim, lim)
            ax.set_ylim(-lim, lim)
            ax.set_zlim(-lim, lim)
            ax.view_init(elev=-135)
            plt.show()
        else:
            self.heli_arrows[0]._verts3d = tuple(coords[0])
            self.heli_arrows[1]._verts3d = tuple(coords[1])
            self.heli_arrows[2]._verts3d = tuple(coords[2])
            ax = self._ax
            ax.collections.remove(self._wframe_main)
            ax.collections.remove(self._wframe_side)
            for arr in self.action_arrows:
                self.action_ax.artists.remove(arr)
            ax1 = self.action_ax
            # TODO make sure the actions are plotted right
            # main rotor direction?
            arr1 = ax1.arrow(0, 0, a[0], 0, **style)
            arr2 = ax1.arrow(0, 0, 0, a[1], **style)
            # side rotor throttle?
            arr3 = ax1.arrow(0, 1.5, a[2], 0, **style)
            # main rotor throttle
            arr4 = ax1.arrow(1.5, 0, 0, a[3], **style)
            self.action_arrows = (arr1, arr2, arr3, arr4)

            self._wframe_main = ax.plot_wireframe(coord_main[0], coord_main[1],
                                                  coord_main[2], color="k")
            self._wframe_side = ax.plot_wireframe(coord_side[0], coord_side[1],
                                                  coord_side[2], color="k")
            ax.set_aspect("equal")
            lim = 5  # self.MAX_POS
            ax.set_xlim(-lim, lim)
            ax.set_ylim(-lim, lim)
            ax.set_zlim(-lim, lim)
            ax.view_init(elev=-135)
            self.domain_fig.canvas.draw()

class HelicopterHover(HelicopterHoverExtended):

    """
    .. warning::

        This domain has an internal hidden state, as it actually is
        a POMDP. Besides the 12-dimensional observable state, there is an internal
        state saved as ``self.hidden_state_`` (time and long-term noise which
        simulated gusts of wind).
        be aware of this state if you use this class to produce samples which are
        not in order

    Implementation of a simulator that models one of the Stanford
    autonomous helicopters (an XCell Tempest helicopter) in the flight
    regime close to hover.

    Adapted from the
    `RL-Community Java Implementation <http://library.rl-community.org/wiki/Helicopter_(Java)>`_

    **STATE:**
    The state of the helicopter is described by a 12-dimensional vector
    with the following entries:

    * 0: xerr [helicopter x-coord position - desired x-coord position] -- helicopter's x-axis points forward
    * 1: yerr [helicopter y-coord position - desired y-coord position] -- helicopter's y-axis points to the right
    * 2: zerr [helicopter z-coord position - desired z-coord position] -- helicopter's z-axis points down
    * 3: u [forward velocity]
    * 4: v [sideways velocity (to the right)]
    * 5: w [downward velocity]
    * 6: p [angular rate around helicopter's x axis]
    * 7: q [angular rate around helicopter's y axis]
    * 8: r [angular rate around helicopter's z axis]
    * 9-11: orientation of the world in the heli system as quaterion

    **REFERENCE:**

    .. seealso::
        Abbeel, P., Ganapathi, V. & Ng, A. Learning vehicular dynamics,
        with application to modeling helicopters.
        Advances in Neural Information Systems (2006).
    """

    episodeCap = 6000
    MAX_POS = 20.  # m
    MAX_VEL = 10.  # m/s
    MAX_ANG_RATE = 4 * np.pi
    MAX_ANG = 1.
    WIND_MAX = 5.

    continuous_dims = np.arange(12)
    statespace_limits = np.array([[-MAX_POS, MAX_POS]] * 3
                                 + [[-MAX_VEL, MAX_VEL]] * 3
                                 + [[-MAX_ANG_RATE, MAX_ANG_RATE]] * 3
                                 + [[-MAX_ANG, MAX_ANG]] * 3)

    #full_state_ = np.zeros((20))

    def s0(self):
        #self.hidden_state_ = np.zeros((8))
        #self.hidden_state_[0] = 1.
        s_full, term, p_actions = super(HelicopterHover, self).s0()
        s, _ = self._split_state(s_full)
        return s, term, p_actions

    def _split_state(self, s):
        s_observable = np.zeros((12))
        s_observable[:9] = s[:9]
        s_observable[9:12] = s[10:13]
        s_hidden = np.zeros((8))
        s_hidden[0] = s[9]
        s_hidden[1:] = s[13:]
        return s_observable, s_hidden

    def step(self, a):
        #s_extended = self._augment_state(s)
        r, st, term, p_actions = super(HelicopterHover, self).step(a)
        st, _ = self._split_state(st)
        return (r, st, term, p_actions)


from __future__ import print_function, division, absolute_import

import numpy as np
import matplotlib.pyplot as plt
import os
import sys
import subprocess
from astropy.io import fits
from astropy import time
import DataStructures
from ._utils import combine_orders, read_grid_points, ensure_dir
from .analyzer import GSSP_Analyzer
import logging
import glob

home = os.environ['HOME']
GSSP_EXE = '{}/Applications/GSSP/GSSP_single/GSSP_single'.format(home)
GSSP_ABUNDANCE_TABLES = '{}/Applications/GSSPAbundance_Tables/'.format(home)
GSSP_MODELS = '/media/ExtraSpace/GSSP_Libraries/LLmodels/'

class GSSP_Fitter(object):
    teff_minstep = 100
    logg_minstep = 0.1
    feh_minstep = 0.1
    vsini_minstep = 10
    vmicro_minstep = 0.1
    def __init__(self, filename, gssp_exe=None, abund_tab=None, models_dir=None):
        """
        A python wrapper to the GSSP code (must already be installed)

        Parameters:
        ===========
        filename:       string
                        The filename of the (flattened) fits spectrum to fit.

        gssp_exe:       string (optional)
                        The full path to the gssp executable file

        abund_tab:      string (optional)
                        The full path to the directory containing
                        GSSP abundance tables.

        models_dir:     string:
                        The full path to the directory containing
                        GSSP atmosphere models.

        Methods:
        ==========
        fit:            Fit the parameters

        """

        if gssp_exe is None:
            gssp_exe = GSSP_EXE
        if abund_tab is None:
            abund_tab = GSSP_ABUNDANCE_TABLES
        if models_dir is None:
            models_dir = GSSP_MODELS

        # Read in the file and combine the orders
        orders = self._read_fits_file(filename)
        combined = combine_orders(orders)

        #TODO: Cross-correlate the data to get it close. GSSP might have trouble with huge RVs...

        # Get the object name/date
        header = fits.getheader(filename)
        star = header['OBJECT']
        date = header['DATE-OBS']
        try:
            jd = time.Time(date, format='isot', scale='utc').jd
        except TypeError:
            jd = time.Time('{}T{}'.format(date, header['UT']), format='isot',
                           scale='utc').jd

        # Save the data to an ascii file
        output_basename = '{}-{}'.format(star.replace(' ', ''), jd)
        np.savetxt('data_sets/{}.txt'.format(output_basename),
                   np.transpose((combined.x, combined.y)),
                   fmt='%.10f')

        # Save some instance variables
        self.data = combined
        self.jd = jd
        self.starname = star
        self.output_basename = output_basename
        self.gssp_exe = os.path.abspath(gssp_exe)
        self.abundance_table = abund_tab
        self.model_dir = models_dir
        self.gssp_gridpoints = read_grid_points(models_dir)

    def _run_gssp(self, teff_lims=(7000, 30000), teff_step=1000,
                  logg_lims=(3.0, 4.5), logg_step=0.5,
                  feh_lims=(-0.5, 0.5), feh_step=0.5,
                  vsini_lims=(50, 350), vsini_step=50,
                  vmicro_lims=(1, 5), vmicro_step=1,
                  R=80000, ncores=1):
        """
        Coarsely fit the parameters Teff, log(g), and [Fe/H].
        """
        # First, make sure the inputs are reasonable.
        teff_step = max(teff_step, self.teff_minstep)
        logg_step = max(logg_step, self.logg_minstep)
        feh_step = max(feh_step, self.feh_minstep)
        vsini_step = max(vsini_step, self.vsini_minstep)
        vmicro_step = max(vmicro_step, self.vmicro_minstep)
        teff_lims = (min(teff_lims), max(teff_lims))
        logg_lims = (min(logg_lims), max(logg_lims))
        feh_lims = (min(feh_lims), max(feh_lims))
        vsini_lims = (min(vsini_lims), max(vsini_lims))
        vmicro_lims = (min(vmicro_lims), max(vmicro_lims))
        teff_lims, logg_lims, feh_lims = self._check_grid_limits(teff_lims,
                                                                 logg_lims,
                                                                 feh_lims)

        # Make the input file for GSSP
        inp_file=self._make_input_file(teff_lims=teff_lims, teff_step=teff_step,
                              logg_lims=logg_lims, logg_step=logg_step,
                              feh_lims=feh_lims, feh_step=feh_step,
                              vsini_lims=vsini_lims, vsini_step=vsini_step,
                              vmicro_lims=vmicro_lims, vmicro_step=vmicro_step,
                              resolution=R)

        # Run GSSP
        subprocess.check_call(['mpirun', '-n', '{}'.format(ncores),
                               '{}'.format(self.gssp_exe),
                               '{}'.format(inp_file)])

        # Move the output directory to a new name that won't be overridden
        output_dir = '{}_output'.format(self.output_basename)
        ensure_dir(output_dir)
        for f in glob.glob('output_files/*'):
            subprocess.check_call(['mv', f, '{}/'.format(output_dir)])
        return

    def fit(self, teff_lims=(7000, 30000), teff_step=1000,
            logg_lims=(3.0, 4.5), logg_step=0.5,
            feh_lims=(-0.5, 0.5), feh_step=0.5,
            vsini_lims=(50, 350), vsini_step=50,
            vmicro_lims=(1, 5), vmicro_step=1,
            R=80000, ncores=1, refine=True):
        """
        Fit the stellar parameters with GSSP

        Parameters:
        =============
        par_lims:     iterable with (at least) two objects
                      The limits on the given parameter. 'par' can be one of:

                        1. teff:   The effective temperature
                        2. logg:   The surface gravity
                        3. feh:    The metallicity [Fe/H]
                        4. vsini:  The rotational velocity
                        5. vmicro: The microturbulent velocity

                      The default values are a very large, very course grid.
                      Consider refining based on spectral type first!

        par_step:     float
                      The initial step size to take in the given parameter.
                      'par' can be from the same list as above.

        R:            float
                      The spectrograph resolving power (lambda/delta-lambda)

        ncores:       integer, default=1
                      The number of cores to use in the GSSP run.

        refine:       boolean
                      Should we run GSSP again with a smaller grid after the
                      initial fit? If yes, the best answers will probably be
                      better.

        Returns:
        =========
        A pd.Series object with the best parameters
        """
        # Run GSSP
        self._run_gssp(teff_lims=teff_lims, teff_step=teff_step,
                       logg_lims=logg_lims, logg_step=logg_step,
                       feh_lims=feh_lims, feh_step=feh_step,
                       vsini_lims=vsini_lims, vsini_step=vsini_step,
                       vmicro_lims=vmicro_lims, vmicro_step=vmicro_step,
                       R=R, ncores=ncores)

        # Look at the output and save the figures
        output_dir = '{}_output'.format(self.output_basename)
        best_pars, figs = GSSP_Analyzer(output_dir).estimate_best_parameters()
        for par in figs.keys():
            fig = figs[par]
            fig.savefig(os.path.join(output_dir, '{}_course.pdf'.format(par)))
        plt.close('all')

        if not refine:
            return best_pars

        # If we get here, we should restrict the grid near the
        # best solution and fit again
        teff_lims = self._get_refined_limits(lower=best_pars['1sig_CI_lower_Teff'],
                                             upper=best_pars['1sig_CI_upper_Teff'],
                                             values=self.gssp_gridpoints.teff)
        logg_lims = self._get_refined_limits(lower=best_pars['1sig_CI_lower_logg'],
                                             upper=best_pars['1sig_CI_upper_logg'],
                                             values=self.gssp_gridpoints.logg)
        feh_lims = self._get_refined_limits(lower=best_pars['1sig_CI_lower_feh'],
                                            upper=best_pars['1sig_CI_upper_feh'],
                                            values=self.gssp_gridpoints.feh)
        vsini_lower = best_pars.best_vsini*(1-1.5) + 1.5*best_pars['1sig_CI_lower_vsini']
        vsini_upper = best_pars.best_vsini*(1-1.5) + 1.5*best_pars['1sig_CI_upper_vsini']
        vsini_lims = (max(10, vsini_lower), min(400, vsini_upper))
        vsini_step = max(self.vsini_minstep, (vsini_lims[1] - vsini_lims[0])/10)
        vmicro_lims = (best_pars.micro_turb, best_pars.micro_turb)

        # Rename the files in the output directory so they don't get overwritten
        file_list = ['CCF.dat', 'Chi2_table.dat',
                     'Observed_spectrum.dat', 'Synthetic_best_fit.rgs']
        ensure_dir(os.path.join(output_dir, 'course_output', ''))
        for f in file_list:
            original_fname = os.path.join(output_dir, f)
            new_fname = os.path.join(output_dir, 'course_output', f)
            subprocess.check_call(['mv', original_fname, new_fname])

        # Run GSSP on the refined grid
        self._run_gssp(teff_lims=teff_lims, teff_step=self.teff_minstep,
                       logg_lims=logg_lims, logg_step=self.logg_minstep,
                       feh_lims=feh_lims, feh_step=self.feh_minstep,
                       vsini_lims=vsini_lims, vsini_step=round(vsini_step),
                       vmicro_lims=vmicro_lims, vmicro_step=vmicro_step,
                       R=R, ncores=ncores)

        best_pars, figs = GSSP_Analyzer(output_dir).estimate_best_parameters()
        for par in figs.keys():
            fig = figs[par]
            fig.savefig(os.path.join(output_dir, '{}_fine.pdf'.format(par)))
            fig.close()

        return best_pars

    def _check_grid_limits_old(self, teff_lims, logg_lims, feh_lims):
        df = self.gssp_gridpoints[['teff', 'logg', 'feh']].drop_duplicates()

        # First, check if the limits are do-able
        lower = df.loc[(df.teff <= teff_lims[0]) &
                       (df.logg <= logg_lims[0]) &
                       (df.feh <= feh_lims[0])]
        upper = df.loc[(df.teff >= teff_lims[1]) &
                       (df.logg >= logg_lims[1]) &
                       (df.feh >= feh_lims[1])]
        if len(upper) >= 1 and len(lower) >= 1:
            return teff_lims, logg_lims, feh_lims

        # If we get here, there is a problem...
        # Check temperature first:
        if not (len(df.loc[df.teff <= teff_lims[0]]) >= 1 and
                len(df.loc[df.teff >= teff_lims[1]]) >= 1):
            # Temperature grid is no good.
            low_teff, high_teff = df.teff.min(), df.teff.max()
            print('The temperature grid is not available in the model library!')
            print('You wanted temperatures from {} - {}'.format(*teff_lims))
            print('The model grid extends from {} - {}'.format(low_teff, high_teff))
            new_teff_lims = (max(low_teff, teff_lims[0]),
                             min(high_teff, teff_lims[1]))
            print('Resetting temperature limits to {} - {}'.format(*new_teff_lims))
            return self._check_grid_limits(new_teff_lims, logg_lims, feh_lims)

        # Check log(g) next:
        teff_df = df.loc[(df.teff >= teff_lims[0]) & (df.teff <= teff_lims[1])]
        if not (len(teff_df.loc[df.logg <= logg_lims[0]]) >= 1 and
                len(teff_df.loc[df.logg >= logg_lims[1]]) >= 1):
            # Temperature grid is no good.
            low_logg, high_logg = df.logg.min(), df.logg.max()
            print('The log(g) grid is not available in the model library!')
            print('You wanted log(g) from {} - {}'.format(*logg_lims))
            print('The model grid extends from {} - {}'.format(low_logg, high_logg))
            new_logg_lims = (max(low_logg, logg_lims[0]),
                             min(high_logg, logg_lims[1]))
            print('Resetting log(g) limits to {} - {}'.format(*new_logg_lims))
            return self._check_grid_limits(teff_lims, new_logg_lims, feh_lims)

        # Finally, check [Fe/H]:
        subset_df = df.loc[(df.teff >= teff_lims[0]) &
                           (df.teff <= teff_lims[1]) *
                           (df.logg >= logg_lims[0]) &
                           (df.logg <= logg_lims[1])]
        if not (len(subset_df.loc[df.feh <= feh_lims[0]]) >= 1 and
                len(subset_df.loc[df.feh >= feh_lims[1]]) >= 1):
            # Temperature grid is no good.
            low_feh, high_feh = df.feh.min(), df.feh.max()
            print('The [Fe/H] grid is not available in the model library!')
            print('You wanted [Fe/H] from {} - {}'.format(*feh_lims))
            print('The model grid extends from {} - {}'.format(low_feh, high_feh))
            new_feh_lims = (max(low_feh, feh_lims[0]),
                            min(high_feh, feh_lims[1]))
            print('Resetting [Fe/H] limits to {} - {}'.format(*new_feh_lims))
            return self._check_grid_limits(teff_lims, logg_lims, new_feh_lims)

        # We should never get here
        raise ValueError('Something weird happened while checking limits!')

    def _check_grid_limits(self, teff_lims, logg_lims, feh_lims):
        df = self.gssp_gridpoints[['teff', 'logg', 'feh']].drop_duplicates()

        # First, check if the limits are do-able as is
        lower = df.loc[(df.teff == teff_lims[0]) & (df.feh == feh_lims[0])]
        upper = df.loc[(df.teff == teff_lims[1]) & (df.feh == feh_lims[1])]
        if (lower.logg.min() <= logg_lims[0] and
            lower.logg.max() >= logg_lims[1] and
            upper.logg.min() <= logg_lims[0] and
            upper.logg.max() >= logg_lims[1]):
            return teff_lims, logg_lims, feh_lims

        # If we get here, there is a problem...
        # Check temperature first:
        low_teff, high_teff = df.teff.min(), df.teff.max()
        if low_teff > teff_lims[0] or high_teff < teff_lims[1]:
            print('The temperature grid is not available in the model library!')
            print('You wanted temperatures from {} - {}'.format(*teff_lims))
            print('The model grid extends from {} - {}'.format(low_teff, high_teff))
            new_teff_lims = (max(low_teff, teff_lims[0]),
                             min(high_teff, teff_lims[1]))
            print('Resetting temperature limits to {} - {}'.format(*new_teff_lims))
            return self._check_grid_limits(new_teff_lims, logg_lims, feh_lims)

        # Check [Fe/H] next
        subset_df = df.loc[(df.teff >= teff_lims[0]) &
                           (df.teff <= teff_lims[1])]
        low_feh, high_feh = subset_df.feh.min(), subset_df.feh.max()
        if low_feh > feh_lims[0] or high_feh < feh_lims[1]:
            print('The [Fe/H] grid is not available in the model library!')
            print('You wanted [Fe/H] from {} - {}'.format(*feh_lims))
            print('The model grid extends from {} - {}'.format(low_feh, high_feh))
            new_feh_lims = (max(low_feh, feh_lims[0]),
                            min(high_feh, feh_lims[1]))
            print('Resetting [Fe/H] limits to {} - {}'.format(*new_feh_lims))
            return self._check_grid_limits(teff_lims, logg_lims, new_feh_lims)

        # Finally, check log(g)
        subset_df = subset_df.loc[(subset_df.feh >= feh_lims[0]) &
                                  (subset_df.feh <= feh_lims[1])]
        low_logg, high_logg = subset_df.logg.min(), subset_df.logg.max()
        if low_logg > logg_lims[0] or high_logg < logg_lims[1]:
            print('The log(g) grid is not available in the model library!')
            print('You wanted log(g) from {} - {}'.format(*logg_lims))
            print('The model grid extends from {} - {}'.format(low_logg, high_logg))
            new_logg_lims = (max(low_logg, logg_lims[0]),
                             min(high_logg, logg_lims[1]))
            print('Resetting log(g) limits to {} - {}'.format(*new_logg_lims))
            return self._check_grid_limits(teff_lims, new_logg_lims, feh_lims)

        # We should never get here
        raise ValueError('Something weird happened while checking limits!')

    def _get_refined_limits(self, lower, upper, values):
        """
        Get the items in the 'values' array that are just
        less than lower and just more than upper.
        """
        unique_values = sorted(np.unique(values))
        l_idx = np.searchsorted(unique_values, lower, side='left')
        r_idx = np.searchsorted(unique_values, upper, side='right')

        if l_idx > 0:
            l_idx -= 1
        if r_idx < len(unique_values) - 1:
            r_idx += 1
        return unique_values[l_idx], unique_values[r_idx]

    def _read_fits_file(self, fname):
        orders = []
        hdulist = fits.open(fname)
        for i, hdu in enumerate(hdulist[1:]):
            xypt = DataStructures.xypoint(x=hdu.data['wavelength'],
                                          y=hdu.data['flux'],
                                          cont=hdu.data['continuum'],
                                          err=hdu.data['error'])
            xypt.x *= 10 #Convert from nanometers to angstrom
            orders.append(xypt)
        return orders

    def _make_input_file(self, teff_lims, teff_step, logg_lims, logg_step,
                         feh_lims, feh_step, vsini_lims, vsini_step,
                         vmicro_lims, vmicro_step, resolution):
        """ Make the input file for the given star
        """
        output_string = '{:.1f} {:.0f} {:.1f}\n'.format(teff_lims[0],
                                                        teff_step,
                                                        teff_lims[-1])

        output_string += '{:.1f} {:.1f} {:.1f}\n'.format(logg_lims[0],
                                                         logg_step,
                                                         logg_lims[1])

        output_string += '{:.1f} {:.1f} {:.1f}\n'.format(vmicro_lims[0],
                                                         vmicro_step,
                                                         vmicro_lims[1])

        output_string += '{:.1f} {:.1f} {:.1f}\n'.format(vsini_lims[0],
                                                         vsini_step,
                                                         vsini_lims[1])

        output_string += "skip 0.03 0.02 0.07  !dilution factor\n"

        output_string += 'skip {:.1f} {:.1f} {:.1f}\n'.format(feh_lims[0],
                                                              feh_step,
                                                              feh_lims[1])

        output_string += 'He 0.04 0.005 0.06   ! Individual abundance\n'

        output_string += '0.0 {:.0f}\n'.format(resolution)

        output_string += '{}\n{}\n'.format(self.abundance_table, self.model_dir)

        output_string += '2 1   !atmosphere model vmicro and mass\n'
        output_string += 'ST    ! model atmosphere chemical composition flag\n'

        dx = self.data.x[1] - self.data.x[0]
        output_string += '1 {:.5f} fit\n'.format(dx)

        output_string += 'data_sets/{}.txt\n'.format(self.output_basename)

        output_string += '0.5 0.99 0.0 adjust ! RV determination stuff\n'

        xmin, xmax = self.data.x[0]-1, self.data.x[-1]+1
        output_string += '{:.1f} {:.1f}\n'.format(xmin, xmax)

        outfilename = '{}.inp'.format(self.output_basename)
        with open(outfilename, 'w') as outfile:
            outfile.write(output_string)

        return outfilename


#!/usr/bin/env python

"""Top level ``eval`` module.
"""

import tokenize
from pandas.core import common as com
from pandas.computation.expr import Expr, _parsers, tokenize_string
from pandas.computation.scope import _ensure_scope
from pandas.compat import DeepChainMap, builtins
from pandas.computation.engines import _engines
from distutils.version import LooseVersion

def _check_engine(engine):
    """Make sure a valid engine is passed.

    Parameters
    ----------
    engine : str

    Raises
    ------
    KeyError
      * If an invalid engine is passed
    ImportError
      * If numexpr was requested but doesn't exist
    """
    if engine not in _engines:
        raise KeyError('Invalid engine {0!r} passed, valid engines are'
                       ' {1}'.format(engine, list(_engines.keys())))

    # TODO: validate this in a more general way (thinking of future engines
    # that won't necessarily be import-able)
    # Could potentially be done on engine instantiation
    if engine == 'numexpr':
        try:
            import numexpr
        except ImportError:
            raise ImportError("'numexpr' not found. Cannot use "
                              "engine='numexpr' for query/eval "
                              "if 'numexpr' is not installed")
        else:
            ne_version = numexpr.__version__
            if ne_version < LooseVersion('2.1'):
                raise ImportError("'numexpr' version is %s, "
                                  "must be >= 2.1" % ne_version)

def _check_parser(parser):
    """Make sure a valid parser is passed.

    Parameters
    ----------
    parser : str

    Raises
    ------
    KeyError
      * If an invalid parser is passed
    """
    if parser not in _parsers:
        raise KeyError('Invalid parser {0!r} passed, valid parsers are'
                       ' {1}'.format(parser, _parsers.keys()))

def _check_resolvers(resolvers):
    if resolvers is not None:
        for resolver in resolvers:
            if not hasattr(resolver, '__getitem__'):
                name = type(resolver).__name__
                raise TypeError('Resolver of type %r does not implement '
                                'the __getitem__ method' % name)

def _check_expression(expr):
    """Make sure an expression is not an empty string

    Parameters
    ----------
    expr : object
        An object that can be converted to a string

    Raises
    ------
    ValueError
      * If expr is an empty string
    """
    if not expr:
        raise ValueError("expr cannot be an empty string")

def _convert_expression(expr):
    """Convert an object to an expression.

    Thus function converts an object to an expression (a unicode string) and
    checks to make sure it isn't empty after conversion. This is used to
    convert operators to their string representation for recursive calls to
    :func:`~pandas.eval`.

    Parameters
    ----------
    expr : object
        The object to be converted to a string.

    Returns
    -------
    s : unicode
        The string representation of an object.

    Raises
    ------
    ValueError
      * If the expression is empty.
    """
    s = com.pprint_thing(expr)
    _check_expression(s)
    return s

def _check_for_locals(expr, stack_level, parser):
    at_top_of_stack = stack_level == 0
    not_pandas_parser = parser != 'pandas'

    if not_pandas_parser:
        msg = "The '@' prefix is only supported by the pandas parser"
    elif at_top_of_stack:
        msg = ("The '@' prefix is not allowed in "
               "top-level eval calls, \nplease refer to "
               "your variables by name without the '@' "
               "prefix")

    if at_top_of_stack or not_pandas_parser:
        for toknum, tokval in tokenize_string(expr):
            if toknum == tokenize.OP and tokval == '@':
                raise SyntaxError(msg)

def eval(expr, parser='pandas', engine='numexpr', truediv=True,
         local_dict=None, global_dict=None, resolvers=(), level=0,
         target=None):
    """Evaluate a Python expression as a string using various backends.

    The following arithmetic operations are supported: ``+``, ``-``, ``*``,
    ``/``, ``**``, ``%``, ``//`` (python engine only) along with the following
    boolean operations: ``|`` (or), ``&`` (and), and ``~`` (not).
    Additionally, the ``'pandas'`` parser allows the use of :keyword:`and`,
    :keyword:`or`, and :keyword:`not` with the same semantics as the
    corresponding bitwise operators.  :class:`~pandas.Series` and
    :class:`~pandas.DataFrame` objects are supported and behave as they would
    with plain ol' Python evaluation.

    Parameters
    ----------
    expr : str or unicode
        The expression to evaluate. This string cannot contain any Python
        `statements
        <http://docs.python.org/2/reference/simple_stmts.html#simple-statements>`__,
        only Python `expressions
        <http://docs.python.org/2/reference/simple_stmts.html#expression-statements>`__.
    parser : string, default 'pandas', {'pandas', 'python'}
        The parser to use to construct the syntax tree from the expression. The
        default of ``'pandas'`` parses code slightly different than standard
        Python. Alternatively, you can parse an expression using the
        ``'python'`` parser to retain strict Python semantics.  See the
        :ref:`enhancing performance <enhancingperf.eval>` documentation for
        more details.
    engine : string, default 'numexpr', {'python', 'numexpr'}

        The engine used to evaluate the expression. Supported engines are

        - ``'numexpr'``: This default engine evaluates pandas objects using
                         numexpr for large speed ups in complex expressions
                         with large frames.
        - ``'python'``: Performs operations as if you had ``eval``'d in top
                        level python. This engine is generally not that useful.

        More backends may be available in the future.

    truediv : bool, optional
        Whether to use true division, like in Python >= 3
    local_dict : dict or None, optional
        A dictionary of local variables, taken from locals() by default.
    global_dict : dict or None, optional
        A dictionary of global variables, taken from globals() by default.
    resolvers : list of dict-like or None, optional
        A list of objects implementing the ``__getitem__`` special method that
        you can use to inject an additional collection of namespaces to use for
        variable lookup. For example, this is used in the
        :meth:`~pandas.DataFrame.query` method to inject the
        :attr:`~pandas.DataFrame.index` and :attr:`~pandas.DataFrame.columns`
        variables that refer to their respective :class:`~pandas.DataFrame`
        instance attributes.
    level : int, optional
        The number of prior stack frames to traverse and add to the current
        scope. Most users will **not** need to change this parameter.
    target : a target object for assignment, optional, default is None
        essentially this is a passed in resolver

    Returns
    -------
    ndarray, numeric scalar, DataFrame, Series

    Notes
    -----
    The ``dtype`` of any objects involved in an arithmetic ``%`` operation are
    recursively cast to ``float64``.

    See the :ref:`enhancing performance <enhancingperf.eval>` documentation for
    more details.

    See Also
    --------
    pandas.DataFrame.query
    pandas.DataFrame.eval
    """
    expr = _convert_expression(expr)
    _check_engine(engine)
    _check_parser(parser)
    _check_resolvers(resolvers)
    _check_for_locals(expr, level, parser)

    # get our (possibly passed-in) scope
    level += 1
    env = _ensure_scope(level, global_dict=global_dict,
                        local_dict=local_dict, resolvers=resolvers,
                        target=target)

    parsed_expr = Expr(expr, engine=engine, parser=parser, env=env,
                       truediv=truediv)

    # construct the engine and evaluate the parsed expression
    eng = _engines[engine]
    eng_inst = eng(parsed_expr)
    ret = eng_inst.evaluate()

    # assign if needed
    if env.target is not None and parsed_expr.assigner is not None:
        env.target[parsed_expr.assigner] = ret
        return None

    return ret


# -*- coding: utf-8 -*-

from datetime import timedelta, datetime
from dateutil import parser as dateparser
import io
import logging
import numpy as np
import pytz
from typing import Any, Dict, List, Tuple

from django.db import connection
from django.http import HttpRequest, HttpResponse
from django.utils import timezone
from django.shortcuts import get_object_or_404
from django.views.decorators.cache import never_cache

from catmaid.control.common import get_request_bool
from catmaid.control.authentication import requires_user_role
from catmaid.models import Connector, Project, Treenode, Review, UserRole

logger = logging.getLogger(__name__)

try:
    import matplotlib
    # Use a noninteractive backend since most CATMAID instances are headless.
    matplotlib.use('svg')

    import matplotlib.pyplot as plt
    from matplotlib.dates import DateFormatter, DayLocator
    from pylab import figure
    from matplotlib.backends.backend_svg import FigureCanvasSVG
except ImportError:
    logger.warning("CATMAID was unable to load the matplotlib module. "
        "User analytics will not be available")

class Bout(object):
    """ Represents one bout, based on a list of events. The first event ist the
    start date/time, the last event the end.
    """
    def __init__(self, start, end=None):
        self.events = [start]
        if end:
            self.events.append(end)

    def addEvent(self, e):
        """ Increments the event counter.
        """
        self.events.append(e)

    @property
    def nrEvents(self):
        return len(self.events)

    @property
    def start(self):
        return self.events[0]

    @property
    def end(self):
        return self.events[-1]

    def __str__(self):
        return "Bout with %s events [%s, %s]" % \
                (self.nrEvents, self.start, self.end)

@never_cache
@requires_user_role(UserRole.Browse)
def plot_useranalytics(request:HttpRequest, project_id) -> HttpResponse:
    """ Creates an SVG image containing different plots for analzing the
    performance of individual users over time.
    """
    time_zone = pytz.utc

    userid = request.GET.get('userid', None)
    if not (userid and userid.strip()):
        raise ValueError("Need user ID")
    project = get_object_or_404(Project, pk=project_id) if project_id else None
    all_writes = get_request_bool(request.GET, 'all_writes', False)
    maxInactivity = int(request.GET.get('max_inactivity', 3))

    # Get the start date for the query, defaulting to 7 days ago.
    start_date = request.GET.get('start', None)
    if start_date:
        start_date = dateparser.parse(start_date)
        start_date = time_zone.localize(start_date)
    else:
        with timezone.override(time_zone):
            start_date = timezone.now() - timedelta(7)

    # Get the end date for the query, defaulting to now.
    end_date = request.GET.get('end', None)
    if end_date:
        end_date = dateparser.parse(end_date)
        end_date = time_zone.localize(end_date)
    else:
        with timezone.override(time_zone):
            end_date = timezone.now()

    # The API is inclusive and should return stats for the end date as
    # well. The actual query is easier with an exclusive end and therefore
    # the end date is set to the beginning of the next day.
    end_date = end_date + timedelta(days=1)

    if request.user.is_superuser or \
            project and request.user.has_perm('can_browse', project):
        f = generateReport( userid, project_id, maxInactivity, start_date,
                end_date, all_writes )
    else:
        f = generateErrorImage('You lack permissions to view this report.')

    # Use raw text rather than SVG fonts or pathing.
    plt.rcParams['svg.fonttype'] = 'none'
    buf = io.BytesIO()
    plt.savefig(buf, format='svg')
    return HttpResponse(buf.getvalue(), content_type='image/svg+xml')

def eventTimes(user_id, project_id, start_date, end_date, all_writes=True) -> Dict[str, Any]:
    """ Returns a tuple containing a list of tree node edition times, connector
    edition times and tree node review times within the date range specified
    where the editor/reviewer is the given user.
    """
    dr = (start_date, end_date)
    tns = Treenode.objects.filter(
        editor_id=user_id,
        edition_time__range=dr)
    cns = Connector.objects.filter(
        editor_id=user_id,
        edition_time__range=dr)
    rns = Review.objects.filter(
        reviewer_id=user_id,
        review_time__range=dr)

    if project_id:
        tns = tns.filter(project_id=project_id)
        cns = cns.filter(project_id=project_id)
        rns = rns.filter(project_id=project_id)

    tns = tns.values_list('edition_time', flat=True)
    cns = cns.values_list('edition_time', flat=True)
    rns = rns.values_list('review_time', flat=True)

    events = {
        'treenode_events': list(tns),
        'connector_events': list(cns),
        'review_events': list(rns)
    }

    if all_writes:
        if project_id:
            params:Tuple[str, ...] = (start_date, end_date, user_id, project_id)
            project_filter = "AND project_id = %s"
        else:
            params = (start_date, end_date, user_id)
            project_filter = ""

        # Query transaction log. This makes this feature only useful of history
        # tracking is available.
        cursor = connection.cursor()
        cursor.execute(f"""
            SELECT execution_time
            FROM catmaid_transaction_info
            WHERE execution_time >= %s
            AND execution_time <= %s
            AND user_id = %s
            {project_filter}
        """, params)
        events['write_events'] = [r[0] for r in cursor.fetchall()]

    return events

def eventsPerInterval(times, start_date, end_date, interval='day') -> Tuple[np.ndarray, List]:
    """ Creates a histogram of how many events fall into all intervals between
    <start_data> and <end_date>. The interval type can be day, hour and
    halfhour. Returned is a tuple containing two elemens: the histogram and a
    time axis, labeling every bin.
    """
    if interval=='day':
        intervalsPerDay = 1
        secondsPerInterval = 86400
    elif interval=='hour':
        intervalsPerDay = 24
        secondsPerInterval = 3600
    elif interval=='halfhour':
        intervalsPerDay = 48
        secondsPerInterval = 1800
    else:
        raise ValueError('Interval options are day, hour, or halfhour')

    # Generate axis
    daycount = (end_date - start_date).days
    dt = timedelta(0, secondsPerInterval)
    timeaxis = [start_date + n*dt for n in range(intervalsPerDay * daycount)]
    # Calculate bins
    timebins = np.zeros(intervalsPerDay * daycount)
    intervalsPerSecond = 1.0 / secondsPerInterval
    for t in times:
        i = int((t - start_date).total_seconds() * intervalsPerSecond)
        timebins[i] += 1

    return timebins, timeaxis

def activeTimes(alltimes, gapThresh):
    """ Goes through the sorted array of time differences between all events
    stored in <alltimes>. If two events are closer together than <gapThresh>
    minutes, they are counted as events within one bout. A tuple containing a
    list of bout start dates as well as a list with total numbers of events for
    each bout is returned.
    """
    # Sort all events and create a list of (time) differences between them
    alltimes.sort()
    dts = np.diff(alltimes)
    # Threshold between to events to be counted as separate bouts (seconds)
    threshold = 60 * gapThresh
    # Indicates whether we are currently in a bout and since we haven't even
    # looked at the first event, we are initially not.
    bout = None
    # Go through all events
    for i, e in enumerate(alltimes):
        if i > 0 and dts[i-1].total_seconds() < threshold:
            # Increment current bout's event counter and continue with the
            # next element as long as the time difference to the next
            # element is below our threshold.
            bout.addEvent(e) # type: ignore # mypy cannot prove bout will not be None
            continue
        else:
            # Return current bout (not available in first iteration) and create
            # a new one.
            if bout:
                yield bout
            bout = Bout(e)

    # Return last bout, if it hasn't been returned, yet
    if bout:
        yield bout

def activeTimesPerDay(active_bouts) -> Tuple[Any, List]:
    """ Creates a tuple containing the active time in hours for every day
    between the first event of the first bout and the last event of the last
    bout as well as a list with the date for every day.
    """
    # Return right away if there are no bouts
    if not active_bouts:
        return [], []

    # Find first event of first bout
    daystart = active_bouts[0].start.replace(
            hour=0, minute=0, second=0, microsecond=0)
    # Find last event of last bout
    dayend = active_bouts[-1].end
    # Get total number of between first event and last event
    numdays = (dayend - daystart).days + 1
    # Create a list of dates for every day between first and last event
    timeaxis = [daystart.date() + timedelta(d) for d in range(numdays)]

    # Calculate the netto active time for each day
    net_active_time = np.array(np.zeros(numdays))
    for bout in active_bouts:
        active_time = (bout.end - bout.start).total_seconds()
        net_active_time[(bout.start - daystart).days] += active_time

    # Return a tuple containing the active time for every
    # day in hours and the list of days.
    return np.divide(net_active_time, 3600), timeaxis

def singleDayEvents(alltimes, start_hour, end_hour) -> Tuple[Any, List]:
    alltimes.sort()
    timeaxis = [n for n in np.add(start_hour,range(end_hour-start_hour+1))]
    activity = np.zeros(end_hour-start_hour+1)
    for a in alltimes:
        if a.hour >= start_hour:
            if a.hour < end_hour:
                activity[a.hour-start_hour] += 1
    return np.true_divide(activity, (alltimes[-1] - alltimes[0]).days), timeaxis

def singleDayActiveness(activebouts, increment, start_hour, end_hour) -> Tuple[Any, Any]:
    """ Returns a ... for all bouts between <start_hour> and <end_hour> of the
    day.
    """
    # Return right away, when there are no bouts given
    if not activebouts:
        return [], []
    # Make sure 60 can be cleanly devided by <incement>
    if np.mod(60, increment) > 0:
        raise ValueError('Increments must divide 60 evenly')

    # Some constants
    stepsPerHour = 60 / increment
    hoursConsidered = (end_hour - start_hour) + 1
    daysConsidered = (activebouts[-1].end - activebouts[0].start).days + 1

    # Get start of current day
    starttime = timezone.now().replace(hour=start_hour,minute=0,second=0,microsecond=0)
    # Create time axis list with entry for every <increment> minutes between
    # <start_hour> and <end_hour>.
    timeaxis = [starttime + timedelta(0, 0, 0, 0, n * increment) \
            for n in range(stepsPerHour * hoursConsidered)]

    # Loop through all days considered to find number of weekend days
    weekendCorrection = 0
    for d in range(daysConsidered):
        # TODO: Why is 0 and 6 used for comparison?
        saturday = (activebouts[0].start + timedelta(d)).isoweekday() == 0
        sunday = (activebouts[0].start + timedelta(d)).isoweekday() == 6
        if saturday or sunday:
            weekendCorrection += 1

    # Initialize list for minutes per period with zeros
    durPerPeriod = np.zeros(stepsPerHour * hoursConsidered)
    for bout in activebouts:
        # Ignore bouts what start after requested <end_hour> or end before
        # requested <start_hour>.
        if bout.start.hour > end_hour:
            continue
        elif bout.end.hour < start_hour:
            continue
        # Crop start and end times of every valid bout to request period
        elif bout.start.hour < start_hour:
            bout.start = bout.start.replace(hour=start_hour,minute=0,second=0,microsecond=0)
        elif bout.end.hour > end_hour:
            bout.end = bout.end.replace(hour=end_hour,minute=0,second=0,microsecond=0)

        # Go through every sub bout, defined by periods if <increment> minutes,
        # and store the number of minutes for every time-fraction considered.
        for subbout in splitBout(bout,increment):
            subboutSeconds = (subbout.end - subbout.start).total_seconds()
            i = stepsPerHour * (subbout.start.hour - start_hour) + \
                    subbout.start.minute / increment
            durPerPeriod[i] += np.true_divide(subboutSeconds, 60)

    # Divide each period (in seconds) by ?
    n = increment * (daysConsidered - weekendCorrection)
    durations = np.true_divide(durPerPeriod, n)
    # Return a tuple containing a list durations and a list of timepoints
    return durations, timeaxis

def splitBout(bout,increment) -> List[Bout]:
    """ Splits one bout in periods of <increment> minutes.
    """
    if np.mod(60, increment) > 0:
        raise RuntimeError('Increments must divide 60 evenly')

    boutListOut = []
    currtime = bout.start
    nexttime = bout.start
    while nexttime < bout.end:
        basemin = increment * ( currtime.minute / increment )
        nexttime = currtime.replace(minute=0,second=0,microsecond=0) + timedelta(0,0,0,0,basemin+increment)
        if nexttime > bout.end:
            nexttime = bout.end
        boutListOut.append(Bout(currtime, nexttime))
        currtime = nexttime
    return boutListOut

def generateErrorImage(msg) -> "matplotlib.figure.Figure":
    """ Creates an empty image (based on image nr. 1) and adds a message to it.
    """
    fig = plt.figure(1, figsize=(6,6))
    fig.clf()
    fig.suptitle(msg)
    return fig

def generateReport(
    user_id, project_id, activeTimeThresh, start_date, end_date, all_writes=True
) -> "matplotlib.figure.Figure":
    """ nts: node times
        cts: connector times
        rts: review times """
    events = eventTimes(user_id, project_id, start_date, end_date, all_writes)

    nts = events['treenode_events']
    cts = events['connector_events']
    rts = events['review_events']

    # If no nodes have been found, return an image with a descriptive text.
    if len(nts) == 0:
        return generateErrorImage("No tree nodes were edited during the " +
                "defined period if time.")

    annotationEvents, ae_timeaxis = eventsPerInterval( nts + cts, start_date, end_date )
    reviewEvents, re_timeaxis = eventsPerInterval( rts, start_date, end_date )

    if all_writes:
        write_events = events['write_events']
        other_write_events = write_events
        writeEvents, we_timeaxis = eventsPerInterval(other_write_events, start_date, end_date)
    else:
        other_write_events = []

    activeBouts = list(activeTimes( nts+cts+rts+other_write_events, activeTimeThresh ))
    netActiveTime, at_timeaxis = activeTimesPerDay( activeBouts )

    dayformat = DateFormatter('%b %d')

    fig = plt.figure(figsize=(9.6, 8))

    # Top left plot: created and edited nodes per day
    ax1 = plt.subplot2grid((2,2), (0,0))

    # If other writes should be shown, draw accumulated write bar first. This
    # makes the regular bar draw over it, so that only the difference is
    # visible, which is exactly what we want.
    if all_writes:
        we = ax1.bar(we_timeaxis, writeEvents, color='#00AA00', align='edge')

    an = ax1.bar(ae_timeaxis, annotationEvents, color='#0000AA', align='edge')
    rv = ax1.bar(re_timeaxis, reviewEvents, bottom=annotationEvents,
            color='#AA0000', align='edge')
    ax1.set_xlim((start_date,end_date))

    if all_writes:
        ax1.legend( (we, an, rv), ('Other changes','Annotated', 'Reviewed'), loc=2)
        ax1.set_ylabel('Nodes and changes')
    else:
        ax1.legend( (an, rv), ('Annotated', 'Reviewed'), loc=2 )
        ax1.set_ylabel('Nodes')

    yl = ax1.get_yticklabels()
    plt.setp(yl, fontsize=10)
    ax1.xaxis.set_major_formatter(dayformat)
    xl = ax1.get_xticklabels()
    plt.setp(xl, rotation=30, fontsize=10)
    ax1.set_title('Edit events', fontsize=10)

    # Bottom left plot: net active time per day
    ax2 = plt.subplot2grid((2,2), (1,0))
    ax2.bar(at_timeaxis, netActiveTime, color='k', align='edge')
    ax2.set_xlim((start_date,end_date))
    ax2.set_ylabel('Hours')
    yl = ax2.get_yticklabels()
    plt.setp(yl, fontsize=10)
    ax2.xaxis.set_major_formatter(dayformat)
    xl = ax2.get_xticklabels()
    plt.setp(xl, rotation=30, fontsize=10)
    ax2.set_title('Net daily active time', fontsize=10)

    """
    ax3 = fig.add_subplot(223)
    ax3 = eventsPerIntervalPerDayPlot(ax3, rts+nts+cts, start_date, end_date, 30 )
    """

    # Right column plot: bouts over days
    ax4 = plt.subplot2grid((2,2), (0,1), rowspan=2)
    ax4 = dailyActivePlotFigure(activeBouts, ax4, start_date, end_date)

    yl = ax4.get_yticklabels()
    plt.setp(yl, fontsize=10)
    ax4.xaxis.set_major_formatter(dayformat)
    xl = ax4.get_xticklabels()
    plt.setp(xl, rotation=30, fontsize=10)
    ax4.set_title('Active Bouts', fontsize=10)
    yl = ax4.get_yticklabels()
    plt.setp(yl, fontsize=10)
    ax4.set_ylabel('Time (24 hr)')

    fig.set_tight_layout(True)

    return fig

def dailyActivePlotFigure(activebouts, ax:"matplotlib.axes.Axes", start_date, end_date) -> "matplotlib.axes.Axes":
    """ Draws a plot of all bouts during each day between <start_date> and
    <end_date> to the plot given by <ax>.
    """
    # Y axis: Draw a line for each two hours in a day and set ticks accordingly
    for i in range(2, 24, 2):
        ax.axhline(i, color='#AAAAAA', linestyle = ':')
    ax.axhspan(8, 18, facecolor='#999999', alpha=0.25)
    ax.set_yticks(range(0, 25, 2))

    # X axis: Ticks and labels for every day
    ax.xaxis.set_major_locator(DayLocator())

    # Draw all bouts
    for bout in activebouts:
        # Ignore bouts that span accross midnight
        # TODO: Draw midnight spanning bouts, too.
        if bout.start.day == bout.end.day:
            isodate = bout.start.isocalendar()
            ax.bar( bout.start.replace(hour=0, minute=0, second=0, microsecond=0),
                    np.true_divide((bout.end-bout.start).total_seconds(), 3600),
                    bottom=bout.start.hour + bout.start.minute/60.0 + bout.start.second/3600.0,
                    alpha=0.5, color='#0000AA', align='edge', edgecolor="k")

    # Set Axis limits
    ax.set_ylim((0, 24))
    ax.invert_yaxis()
    ax.set_xlim((start_date, end_date))

    return ax

def eventsPerIntervalPerDayPlot(ax, times, start_date, end_date, interval=60) -> "matplotlib.axes.Axes":
    if np.mod(24 * 60, interval) > 0:
        raise ValueError('Interval in minutes must divide the day evenly')

    daycount = (end_date-start_date).days
    timebins = {}

    for i in range(daycount):
        timebins[i] = np.zeros(24 * 60 / interval)

    dayList = []
    daylabels = []

    for i in range(daycount):
        day = start_date + timedelta( i )
        dayList.append( day )
        daylabels.append( str(day.month) + '/' + str(day.day) )

    timeaxis = [i for i in range(24 * 60 / interval )]
    timelabels = []
    for i in range(int(24 * 60 / 30)):
        if np.mod(i,2)==0:
            timelabels.append( str(i/2) + ':00' )
        else:
            timelabels.append( str( (i-1)/2 ) + ':30' )

    for t in times:
        timebins[np.floor((t-start_date).days)][ np.floor(np.divide(t.hour*60+t.minute, interval)) ] += 1
    meandat = np.zeros(len(timebins[0]))
    ignoredDays = 0
    ind = 0
    cm = plt.get_cmap('jet',len(timebins))
    dats = []
    for dat in timebins.values():
        if np.sum(dat)==0:
            ignoredDays += 1
        else:
            tmp, = ax.plot(timeaxis, dat, marker='s', linestyle='-.',alpha=0.5, color=cm(ind))
            dats.append(tmp)
            meandat += dat
        ind += 1

    meandat = np.divide(meandat, daycount-ignoredDays)
    tmp, = ax.plot( timeaxis, meandat, color='k', linewidth=4, linestyle='-')
    dats.append(tmp)
    daylabels.append('Mean')

    ax.set_xticks(timeaxis)
    ax.set_xticklabels(timelabels)
    xl = ax.get_xticklabels()
    plt.setp(xl, rotation=30, fontsize=10)
    yl = ax.get_yticklabels()
    plt.setp(yl, fontsize=10)
    ax.set_ylabel('Events',fontsize=10)
    ax.set_xlim(8 * 60 / interval, 19 * 60 / interval)
    ax.legend(dats,daylabels,loc=2,frameon=False)

    return ax


#!/usr/bin/env python

"""@See preprocessed data
"""
from numpy import*
import matplotlib.pyplot as plt
from GestureModel import*
from Creator import*
from Classifier import*

def plotResults(gr_points,gr_sig, b_points,b_sig,name_model):
    from scipy import linalg
    import matplotlib.pyplot as plt

    gr_points = gr_points.transpose()
    b_points = b_points.transpose()

    gr_sigma = []
    b_sigma = []

    n,m = gr_points.shape

    maximum = zeros((m))
    minimum = zeros((m))

    x = arange(0,m,1)

    for i in range(m):
        gr_sigma.append(gr_sig[i*3:i*3+3])
        b_sigma.append(b_sig[i*3:i*3+3])

    for i in range(m):
        sigma = 3.*linalg.sqrtm(gr_sigma[i])
        maximum[i] =  gr_points[0,i]+ sigma[0,0];
        minimum[i] =  gr_points[0,i]- sigma[0,0];

    fig2 = plt.figure()
    import matplotlib.pyplot as plt
    plt.fill_between(x, maximum, minimum,lw=2, alpha=0.5 )
    plt.plot(x, gr_points[0])
    plt.savefig(name_model+ "_gravity_x_axis.png")

    for i in range(m):
        sigma = 3.*linalg.sqrtm(gr_sigma[i])
        maximum[i] =  gr_points[1,i]+ sigma[1,1];
        minimum[i] =  gr_points[1,i]- sigma[1,1];

    fig3 = plt.figure()
    plt.fill_between(x, maximum, minimum,lw=2, alpha=0.5 )
    plt.plot(x, gr_points[1])
    plt.savefig(name_model+ "_gravity_y_axis.png")

    for i in range(m):
        sigma = 3.*linalg.sqrtm(gr_sigma[i])
        maximum[i] =  gr_points[2,i]+ sigma[2,2];
        minimum[i] =  gr_points[2,i]- sigma[2,2];

    fig3 = plt.figure()
    import matplotlib.pyplot as plt
    plt.fill_between(x, maximum, minimum,lw=2, alpha=0.5 )
    plt.plot(x, gr_points[2])
    plt.savefig(name_model+ "_gravity_z_axis.png")

    for i in range(m):
        sigma = 3.*linalg.sqrtm(b_sigma[i])
        maximum[i] =  b_points[0,i]+ sigma[0,0];
        minimum[i] =  b_points[0,i]- sigma[0,0];

    fig4 = plt.figure()
    import matplotlib.pyplot as plt
    plt.fill_between(x, maximum, minimum,lw=2, alpha=0.5 )
    plt.plot(x, b_points[0])
    plt.savefig(name_model+ "_body_x_axis.png")

    for i in range(m):
        sigma = 3.*linalg.sqrtm(b_sigma[i])
        maximum[i] =  b_points[1,i]+ sigma[1,1];
        minimum[i] =  b_points[1,i]- sigma[1,1];

    fig5 = plt.figure()
    import matplotlib.pyplot as plt
    plt.fill_between(x, maximum, minimum,lw=2, alpha=0.5 )
    plt.plot(x, b_points[1])
    plt.savefig(name_model+ "_body_axis.png")

    for i in range(m):
        sigma = 3.*linalg.sqrtm(b_sigma[i])
        maximum[i] =  b_points[2,i]+ sigma[2,2];
        minimum[i] =  b_points[2,i]- sigma[2,2];

    fig6 = plt.figure()
    import matplotlib.pyplot as plt
    plt.fill_between(x, maximum, minimum,lw=2, alpha=0.5 )
    plt.plot(x, b_points[2])
    plt.savefig(name_model+ "_body_z_axis.png")

#NOTE: Add path
def newModel(name,files):
    g = Creator()
    #Read the data

    g.ReadFiles(files,[])
    g.CreateDatasets_Acc()
    g.ObtainNumberOfCluster()
    gravity = g.gravity
    K_gravity = g.K_gravity
    body = g.body
    K_body = g.K_body

    # 2) define the number of points to be used in GMR
    #    (current settings allow for CONSTANT SPACING only)
    numPoints = amax(gravity[0,:]);
    scaling_factor = 10/10;
    numGMRPoints = math.ceil(numPoints*scaling_factor);

    # 3) perform Gaussian Mixture Modelling and Regression to retrieve the
    #   expected curve and associated covariance matrices for each feature

    gr_points, gr_sigma = g.GetExpected(gravity,K_gravity,numGMRPoints)
    b_points, b_sigma = g.GetExpected(body,K_body,numGMRPoints)

    savetxt(name+"MuGravity.txt", gr_points,fmt='%.12f')
    savetxt(name+"SigmaGravity.txt", gr_sigma,fmt='%.12f')
    savetxt(name+"MuBody.txt", b_points,fmt='%.12f')
    savetxt(name+"SigmaBody.txt", b_sigma,fmt='%.12f')

def loadModel(file_name, th=1, plot=True):

    #Load files
    gr_points = loadtxt(file_name+"MuGravity.txt")
    gr_sigma = loadtxt(file_name+"SigmaGravity.txt")

    b_points = loadtxt(file_name+"MuBody.txt")
    b_sigma = loadtxt(file_name+"SigmaBody.txt")

    #Add model
    gm = GestureModel()
    gm.addModel("gravity",gr_points, gr_sigma,th)
    gm.addModel("body",b_points, b_sigma,th)

    if plot == True:
        plotResults(gr_points,gr_sigma, b_points,b_sigma,file_name)

    return gm

name_models = ['A','B','S1','S2']
num_samples = [10,14,9,10]
th = [25,20,10,65]
create_models = False
list_files = []

#Create a list of the list of files for each model

print "Defining files"
i = 0
for name in name_models:
    files = []
    for k in range(1,num_samples[i]+1):
        files.append('Models/' + name + '/data/mod('+ str(k) + ').txt')
    list_files.append(files)
    i = i + 1

#Create the models and save the list of files for calculate the weigths
if(create_models == True):
    print "Creating models"
    i = 0
    for model in name_models:
        print list_files[i]
        newModel(model,list_files[i])
        i = i + 1

list_models = []

print "Loading models"
#Load the models
for j in range(len(name_models)):
     #For the moment don't put True is there are more that 2 models in Ubuntu
     gm = loadModel(name_models[j],th[j],False)
     list_models.append(gm)

print "Calculating weigths"

#Used to calculate the weights
v0 = Classifier()

for j in range(len(name_models)):
    print "\nFor model " + name_models[j] + ":"
    w_g, w_b = v0.calculateW(list_files[j],list_models[j])
    list_models[j].addWeight("gravity",w_g)
    list_models[j].addWeight("body",w_b)

print "\n Init classifers"

l_class = []

for j in range(len(name_models)):
     l_class.append(Classifier())

print "Give the model to each classifier"

for j in range(len(name_models)):
    l_class[j].classify(list_models[j])

print "Validation"

sfile = "validation/mix3.txt"

import matplotlib.pyplot as plt

fig = plt.figure()
for j in range(len(name_models)):
    poss =  l_class[j].validate_from_file(sfile, ',')
    m,n = poss.shape
    x = arange(0,m,1)
    plt.plot(x, poss,'o',label= name_models[j])
    plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3,
           ncol=2, mode="expand", borderaxespad=0.)

plt.savefig("result.png")

print "Finish ..."


# -*-  Mode: Python; -*-
#  Copyright (c) 2009 INESC Porto
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License version 2 as
#  published by the Free Software Foundation;
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#  Authors: Gustavo Carneiro <gjc@inescporto.pt>

import sys

import ns.applications
import ns.core
import ns.flow_monitor
import ns.internet
import ns.mobility
import ns.network
import ns.olsr
import ns.wifi
try:
    import ns.visualizer
except ImportError:
    pass

DISTANCE = 100 # (m)
NUM_NODES_SIDE = 3

def main(argv):

    cmd = ns.core.CommandLine()

    cmd.NumNodesSide = None
    cmd.AddValue("NumNodesSide", "Grid side number of nodes (total number of nodes will be this number squared)")

    cmd.Results = None
    cmd.AddValue("Results", "Write XML results to file")

    cmd.Plot = None
    cmd.AddValue("Plot", "Plot the results using the matplotlib python module")

    cmd.Parse(argv)

    wifi = ns.wifi.WifiHelper.Default()
    wifiMac = ns.wifi.NqosWifiMacHelper.Default()
    wifiPhy = ns.wifi.YansWifiPhyHelper.Default()
    wifiChannel = ns.wifi.YansWifiChannelHelper.Default()
    wifiPhy.SetChannel(wifiChannel.Create())
    ssid = ns.wifi.Ssid("wifi-default")
    wifi.SetRemoteStationManager("ns3::ArfWifiManager")
    wifiMac.SetType ("ns3::AdhocWifiMac",
                     "Ssid", ns.wifi.SsidValue(ssid))

    internet = ns.internet.InternetStackHelper()
    list_routing = ns.internet.Ipv4ListRoutingHelper()
    olsr_routing = ns.olsr.OlsrHelper()
    static_routing = ns.internet.Ipv4StaticRoutingHelper()
    list_routing.Add(static_routing, 0)
    list_routing.Add(olsr_routing, 100)
    internet.SetRoutingHelper(list_routing)

    ipv4Addresses = ns.internet.Ipv4AddressHelper()
    ipv4Addresses.SetBase(ns.network.Ipv4Address("10.0.0.0"), ns.network.Ipv4Mask("255.255.255.0"))

    port = 9   # Discard port(RFC 863)
    onOffHelper = ns.applications.OnOffHelper("ns3::UdpSocketFactory",
                                  ns.network.Address(ns.network.InetSocketAddress(ns.network.Ipv4Address("10.0.0.1"), port)))
    onOffHelper.SetAttribute("DataRate", ns.network.DataRateValue(ns.network.DataRate("100kbps")))
    onOffHelper.SetAttribute("OnTime", ns.core.StringValue ("ns3::ConstantRandomVariable[Constant=1]"))
    onOffHelper.SetAttribute("OffTime", ns.core.StringValue ("ns3::ConstantRandomVariable[Constant=0]"))

    addresses = []
    nodes = []

    if cmd.NumNodesSide is None:
        num_nodes_side = NUM_NODES_SIDE
    else:
        num_nodes_side = int(cmd.NumNodesSide)

    for xi in range(num_nodes_side):
        for yi in range(num_nodes_side):

            node = ns.network.Node()
            nodes.append(node)

            internet.Install(ns.network.NodeContainer(node))

            mobility = ns.mobility.ConstantPositionMobilityModel()
            mobility.SetPosition(ns.core.Vector(xi*DISTANCE, yi*DISTANCE, 0))
            node.AggregateObject(mobility)

            devices = wifi.Install(wifiPhy, wifiMac, node)
            ipv4_interfaces = ipv4Addresses.Assign(devices)
            addresses.append(ipv4_interfaces.GetAddress(0))

    for i, node in enumerate(nodes):
        destaddr = addresses[(len(addresses) - 1 - i) % len(addresses)]
        #print i, destaddr
        onOffHelper.SetAttribute("Remote", ns.network.AddressValue(ns.network.InetSocketAddress(destaddr, port)))
        app = onOffHelper.Install(ns.network.NodeContainer(node))
        urv = ns.core.UniformRandomVariable()
        app.Start(ns.core.Seconds(urv.GetValue(20, 30)))

    #internet.EnablePcapAll("wifi-olsr")
    flowmon_helper = ns.flow_monitor.FlowMonitorHelper()
    #flowmon_helper.SetMonitorAttribute("StartTime", ns.core.TimeValue(ns.core.Seconds(31)))
    monitor = flowmon_helper.InstallAll()
    monitor = flowmon_helper.GetMonitor()
    monitor.SetAttribute("DelayBinWidth", ns.core.DoubleValue(0.001))
    monitor.SetAttribute("JitterBinWidth", ns.core.DoubleValue(0.001))
    monitor.SetAttribute("PacketSizeBinWidth", ns.core.DoubleValue(20))

    ns.core.Simulator.Stop(ns.core.Seconds(44.0))
    ns.core.Simulator.Run()

    def print_stats(os, st):
        print >> os, "  Tx Bytes: ", st.txBytes
        print >> os, "  Rx Bytes: ", st.rxBytes
        print >> os, "  Tx Packets: ", st.txPackets
        print >> os, "  Rx Packets: ", st.rxPackets
        print >> os, "  Lost Packets: ", st.lostPackets
        if st.rxPackets > 0:
            print >> os, "  Mean{Delay}: ", (st.delaySum.GetSeconds() / st.rxPackets)
	    print >> os, "  Mean{Jitter}: ", (st.jitterSum.GetSeconds() / (st.rxPackets-1))
            print >> os, "  Mean{Hop Count}: ", float(st.timesForwarded) / st.rxPackets + 1

        if 0:
            print >> os, "Delay Histogram"
            for i in range(st.delayHistogram.GetNBins () ):
              print >> os, " ",i,"(", st.delayHistogram.GetBinStart (i), "-", \
                  st.delayHistogram.GetBinEnd (i), "): ", st.delayHistogram.GetBinCount (i)
            print >> os, "Jitter Histogram"
            for i in range(st.jitterHistogram.GetNBins () ):
              print >> os, " ",i,"(", st.jitterHistogram.GetBinStart (i), "-", \
                  st.jitterHistogram.GetBinEnd (i), "): ", st.jitterHistogram.GetBinCount (i)
            print >> os, "PacketSize Histogram"
            for i in range(st.packetSizeHistogram.GetNBins () ):
              print >> os, " ",i,"(", st.packetSizeHistogram.GetBinStart (i), "-", \
                  st.packetSizeHistogram.GetBinEnd (i), "): ", st.packetSizeHistogram.GetBinCount (i)

        for reason, drops in enumerate(st.packetsDropped):
            print "  Packets dropped by reason %i: %i" % (reason, drops)
        #for reason, drops in enumerate(st.bytesDropped):
        #    print "Bytes dropped by reason %i: %i" % (reason, drops)

    monitor.CheckForLostPackets()
    classifier = flowmon_helper.GetClassifier()

    if cmd.Results is None:
        for flow_id, flow_stats in monitor.GetFlowStats():
            t = classifier.FindFlow(flow_id)
            proto = {6: 'TCP', 17: 'UDP'} [t.protocol]
            print "FlowID: %i (%s %s/%s --> %s/%i)" % \
                (flow_id, proto, t.sourceAddress, t.sourcePort, t.destinationAddress, t.destinationPort)
            print_stats(sys.stdout, flow_stats)
    else:
        print monitor.SerializeToXmlFile(cmd.Results, True, True)

    if cmd.Plot is not None:
        import pylab
        delays = []
        for flow_id, flow_stats in monitor.GetFlowStats():
            tupl = classifier.FindFlow(flow_id)
            if tupl.protocol == 17 and tupl.sourcePort == 698:
                continue
            delays.append(flow_stats.delaySum.GetSeconds() / flow_stats.rxPackets)
        pylab.hist(delays, 20)
        pylab.xlabel("Delay (s)")
        pylab.ylabel("Number of Flows")
        pylab.show()

    return 0

if __name__ == '__main__':
    sys.exit(main(sys.argv))


""" Produces plots and a summary html 'headless' """
import logging
import os

import matplotlib
import matplotlib.pyplot as ppl
import spyboat.plotting as spyplot

ppl.switch_backend('Agg')
matplotlib.rcParams["text.usetex"] = False
logger = logging.getLogger(__name__)

# figure resolution
DPI = 250

def produce_snapshots(input_movie, results, frame, Wkwargs, img_path="."):
    """
    Takes the *input_movie* and the *results* dictionary
    from spyboat.processing.run_parallel and produces phase,
    period and amplitude snapshot png's.

    For the period snapshot also the period range is needed,
    hence the analysis dictionary 'Wkwargs' also gets passed.

    The output files name pattern is:
    [input, phase, period, amplitude]_frame{frame}.png
    and the storage location in *img_path*.

    These get picked up by 'create_html'
    """

    spyplot.input_snapshot(input_movie[frame])
    fig = ppl.gcf()
    out_path = os.path.join(img_path, f"input_frame{frame}.png")
    fig.savefig(out_path, dpi=DPI)
    ppl.close(fig)

    spyplot.phase_snapshot(results["phase"][frame])
    fig = ppl.gcf()
    out_path = os.path.join(img_path, f"phase_frame{frame}.png")
    fig.savefig(out_path, dpi=DPI)
    ppl.close(fig)

    spyplot.period_snapshot(
        results["period"][frame], Wkwargs["Tmin"],
        Wkwargs["Tmax"], time_unit="a.u."
    )

    fig = ppl.gcf()
    out_path = os.path.join(img_path, f"period_frame{frame}.png")
    fig.savefig(out_path, dpi=DPI)
    ppl.close(fig)

    spyplot.amplitude_snapshot(results["amplitude"][frame])
    fig = ppl.gcf()
    out_path = os.path.join(img_path, f"amplitude_frame{frame}.png")
    fig.savefig(out_path, dpi=DPI)
    ppl.close(fig)

    logger.info(f"Produced 4 snapshots for frame {frame}..")

def produce_distr_plots(results, Wkwargs, img_path="."):
    """
    Output file names are:

    period_distr.png, power_distr.png and phase_distr.png
    """

    spyplot.period_distr_dynamics(results["period"], Wkwargs)
    fig = ppl.gcf()
    out_path = os.path.join(img_path, "period_distr.png")
    fig.savefig(out_path, dpi=DPI)

    spyplot.power_distr_dynamics(results["power"], Wkwargs)
    fig = ppl.gcf()
    out_path = os.path.join(img_path, "power_distr.png")
    fig.savefig(out_path, dpi=DPI)

    spyplot.phase_coherence_dynamics(results["phase"], Wkwargs)
    fig = ppl.gcf()
    out_path = os.path.join(img_path, "phase_distr.png")
    fig.savefig(out_path, dpi=DPI)

    logger.info("Produced 3 distribution plots..")

def create_html(frame_nums, par_str, html_fname="OutputReport.html"):
    """
    The html generated assumes the respective png's
    have been created with 'produce_snapshots' and 'produce_distr_plots'
    and can be found at the cwd (that's how Galaxy works..)
    """

    # -- create a gallery for every frame in frame_nums --

    galleries = ""
    for frame_num in frame_nums:
        new_gal = f"""

        Frame Nr. {frame_num}

        """
        galleries += new_gal

    parameter_cells = ''
    for line in par_str.split('\n'):
        # last str is empty..
        if not line:
            break
        par_name, par_val = line.split('->')
        parameter_cells += f'''

              {par_name}
              {par_val}

'''

    html_string = f"""

    <title>SpyBOAT Output Report</title>

SpyBOAT Results Report

 Spatial Summary Statistics

 Median and quartiles of the estimated periods for each frame

 Median and quartiles of the ridge wavelet power for each frame

 Kuramoto order parameter for the phases estimated for each frame

 Output Movie Snapshots

        <button class="w3-button" onclick="plusDivs(-1)">❮ Prev</button>
        <button class="w3-button" onclick="plusDivs(1)">Next ❯</button>

    {galleries}

 Parameters

            Name
            Value

         {parameter_cells}

    """

    with open(html_fname, "w") as OUT:
        OUT.write(html_string)

    logger.info("Created html report")
    return html_string

# for local testing
# create_html([0,20], 'par1 -> val1\n verylongpar2 -> val2')


# Elasticity of demand is a measure of how strongly consumers respond to a change in the price of a good
# Formally, % change in demand / % change in price

# Problem: Graph the histogram of average-elasticity for a linear-demand good with random coefficients (a, b)

import random
import matplotlib.pyplot as plt
import numpy as np

SIM = 1000;
UNIT_RANGE = range(1, 50)
AVGS = list()
COEF = [0, 0]

def generate_coefficients():
        global COEF
        a = random.randint(1, 25)
        b = random.randint(a*50, 25*50)
        COEF = [a, b]

def price(unit):
        return COEF[1] - COEF[0]*unit

def graph_price():
        x = np.linspace(1,50,50)
        y = price(x)
        plt.plot(x, y)
        plt.show()

def elasticity(d1, d2):
        cPrice = price(d2) - price(d1)
        cDemand = d2 - d1
        pPrice = cPrice / price(d1)
        pDemand = cDemand / d1
        return abs(pDemand / pPrice)

def simulate():
        global AVGS, COEF, UNIT_RANGE
        generate_coefficients()
        elast_list = list()
        for i in UNIT_RANGE:
                for j in UNIT_RANGE:
                        if(i != j):
                                elast_list.append(elasticity(i, j))
        mu = np.mean(elast_list)
        print(COEF, mu)
        AVGS.append(mu)

def init():
        for i in range(0, SIM):
                simulate()
init()
print(SIM)
plt.hist(AVGS)
plt.show()