"""Tests for Incremental PCA."""
import numpy as np

from sklearn.utils.testing import assert_almost_equal
from sklearn.utils.testing import assert_array_almost_equal
from sklearn.utils.testing import assert_raises

from sklearn import datasets
from sklearn.decomposition import PCA, IncrementalPCA

iris = datasets.load_iris()

def test_incremental_pca():
    # Incremental PCA on dense arrays.
    X = iris.data
    batch_size = X.shape[0] // 3
    ipca = IncrementalPCA(n_components=2, batch_size=batch_size)
    pca = PCA(n_components=2)
    pca.fit_transform(X)

    X_transformed = ipca.fit_transform(X)

    np.testing.assert_equal(X_transformed.shape, (X.shape[0], 2))
    assert_almost_equal(ipca.explained_variance_ratio_.sum(),
                        pca.explained_variance_ratio_.sum(), 1)

    for n_components in [1, 2, X.shape[1]]:
        ipca = IncrementalPCA(n_components, batch_size=batch_size)
        ipca.fit(X)
        cov = ipca.get_covariance()
        precision = ipca.get_precision()
        assert_array_almost_equal(np.dot(cov, precision),
                                  np.eye(X.shape[1]))

def test_incremental_pca_check_projection():
    # Test that the projection of data is correct.
    rng = np.random.RandomState(1999)
    n, p = 100, 3
    X = rng.randn(n, p) * .1
    X[:10] += np.array([3, 4, 5])
    Xt = 0.1 * rng.randn(1, p) + np.array([3, 4, 5])

    # Get the reconstruction of the generated data X
    # Note that Xt has the same "components" as X, just separated
    # This is what we want to ensure is recreated correctly
    Yt = IncrementalPCA(n_components=2).fit(X).transform(Xt)

    # Normalize
    Yt /= np.sqrt((Yt ** 2).sum())

    # Make sure that the first element of Yt is ~1, this means
    # the reconstruction worked as expected
    assert_almost_equal(np.abs(Yt[0][0]), 1., 1)

def test_incremental_pca_inverse():
    # Test that the projection of data can be inverted.
    rng = np.random.RandomState(1999)
    n, p = 50, 3
    X = rng.randn(n, p)  # spherical data
    X[:, 1] *= .00001  # make middle component relatively small
    X += [5, 4, 3]  # make a large mean

    # same check that we can find the original data from the transformed
    # signal (since the data is almost of rank n_components)
    ipca = IncrementalPCA(n_components=2, batch_size=10).fit(X)
    Y = ipca.transform(X)
    Y_inverse = ipca.inverse_transform(Y)
    assert_almost_equal(X, Y_inverse, decimal=3)

def test_incremental_pca_validation():
    # Test that n_components is >=1 and <= n_features.
    X = [[0, 1], [1, 0]]
    for n_components in [-1, 0, .99, 3]:
        assert_raises(ValueError, IncrementalPCA(n_components,
                                                 batch_size=10).fit, X)

def test_incremental_pca_set_params():
    # Test that components_ sign is stable over batch sizes.
    rng = np.random.RandomState(1999)
    n_samples = 100
    n_features = 20
    X = rng.randn(n_samples, n_features)
    X2 = rng.randn(n_samples, n_features)
    X3 = rng.randn(n_samples, n_features)
    ipca = IncrementalPCA(n_components=20)
    ipca.fit(X)
    # Decreasing number of components
    ipca.set_params(n_components=10)
    assert_raises(ValueError, ipca.partial_fit, X2)
    # Increasing number of components
    ipca.set_params(n_components=15)
    assert_raises(ValueError, ipca.partial_fit, X3)
    # Returning to original setting
    ipca.set_params(n_components=20)
    ipca.partial_fit(X)

def test_incremental_pca_num_features_change():
    # Test that changing n_components will raise an error.
    rng = np.random.RandomState(1999)
    n_samples = 100
    X = rng.randn(n_samples, 20)
    X2 = rng.randn(n_samples, 50)
    ipca = IncrementalPCA(n_components=None)
    ipca.fit(X)
    assert_raises(ValueError, ipca.partial_fit, X2)

def test_incremental_pca_batch_signs():
    # Test that components_ sign is stable over batch sizes.
    rng = np.random.RandomState(1999)
    n_samples = 100
    n_features = 3
    X = rng.randn(n_samples, n_features)
    all_components = []
    batch_sizes = np.arange(10, 20)
    for batch_size in batch_sizes:
        ipca = IncrementalPCA(n_components=None, batch_size=batch_size).fit(X)
        all_components.append(ipca.components_)

    for i, j in zip(all_components[:-1], all_components[1:]):
        assert_almost_equal(np.sign(i), np.sign(j), decimal=6)

def test_incremental_pca_batch_values():
    # Test that components_ values are stable over batch sizes.
    rng = np.random.RandomState(1999)
    n_samples = 100
    n_features = 3
    X = rng.randn(n_samples, n_features)
    all_components = []
    batch_sizes = np.arange(20, 40, 3)
    for batch_size in batch_sizes:
        ipca = IncrementalPCA(n_components=None, batch_size=batch_size).fit(X)
        all_components.append(ipca.components_)

    for i, j in zip(all_components[:-1], all_components[1:]):
        assert_almost_equal(i, j, decimal=1)

def test_incremental_pca_partial_fit():
    # Test that fit and partial_fit get equivalent results.
    rng = np.random.RandomState(1999)
    n, p = 50, 3
    X = rng.randn(n, p)  # spherical data
    X[:, 1] *= .00001  # make middle component relatively small
    X += [5, 4, 3]  # make a large mean

    # same check that we can find the original data from the transformed
    # signal (since the data is almost of rank n_components)
    batch_size = 10
    ipca = IncrementalPCA(n_components=2, batch_size=batch_size).fit(X)
    pipca = IncrementalPCA(n_components=2, batch_size=batch_size)
    # Add one to make sure endpoint is included
    batch_itr = np.arange(0, n + 1, batch_size)
    for i, j in zip(batch_itr[:-1], batch_itr[1:]):
        pipca.partial_fit(X[i:j, :])
    assert_almost_equal(ipca.components_, pipca.components_, decimal=3)

def test_incremental_pca_against_pca_iris():
    # Test that IncrementalPCA and PCA are approximate (to a sign flip).
    X = iris.data

    Y_pca = PCA(n_components=2).fit_transform(X)
    Y_ipca = IncrementalPCA(n_components=2, batch_size=25).fit_transform(X)

    assert_almost_equal(np.abs(Y_pca), np.abs(Y_ipca), 1)

def test_incremental_pca_against_pca_random_data():
    # Test that IncrementalPCA and PCA are approximate (to a sign flip).
    rng = np.random.RandomState(1999)
    n_samples = 100
    n_features = 3
    X = rng.randn(n_samples, n_features) + 5 * rng.rand(1, n_features)

    Y_pca = PCA(n_components=3).fit_transform(X)
    Y_ipca = IncrementalPCA(n_components=3, batch_size=25).fit_transform(X)

    assert_almost_equal(np.abs(Y_pca), np.abs(Y_ipca), 1)

def test_explained_variances():
    # Test that PCA and IncrementalPCA calculations match
    X = datasets.make_low_rank_matrix(1000, 100, tail_strength=0.,
                                      effective_rank=10, random_state=1999)
    prec = 3
    n_samples, n_features = X.shape
    for nc in [None, 99]:
        pca = PCA(n_components=nc).fit(X)
        ipca = IncrementalPCA(n_components=nc, batch_size=100).fit(X)
        assert_almost_equal(pca.explained_variance_, ipca.explained_variance_,
                            decimal=prec)
        assert_almost_equal(pca.explained_variance_ratio_,
                            ipca.explained_variance_ratio_, decimal=prec)
        assert_almost_equal(pca.noise_variance_, ipca.noise_variance_,
                            decimal=prec)

def test_whitening():
    # Test that PCA and IncrementalPCA transforms match to sign flip.
    X = datasets.make_low_rank_matrix(1000, 10, tail_strength=0.,
                                      effective_rank=2, random_state=1999)
    prec = 3
    n_samples, n_features = X.shape
    for nc in [None, 9]:
        pca = PCA(whiten=True, n_components=nc).fit(X)
        ipca = IncrementalPCA(whiten=True, n_components=nc,
                              batch_size=250).fit(X)

        Xt_pca = pca.transform(X)
        Xt_ipca = ipca.transform(X)
        assert_almost_equal(np.abs(Xt_pca), np.abs(Xt_ipca), decimal=prec)
        Xinv_ipca = ipca.inverse_transform(Xt_ipca)
        Xinv_pca = pca.inverse_transform(Xt_pca)
        assert_almost_equal(X, Xinv_ipca, decimal=prec)
        assert_almost_equal(X, Xinv_pca, decimal=prec)
        assert_almost_equal(Xinv_pca, Xinv_ipca, decimal=prec)


"""
PDBProp
=======
"""

import gzip
import json
import logging
import os.path as op
import mmtf
import os
from cobra.core import DictList
import pandas as pd
import requests
import deprecation
from Bio.PDB import PDBList
from lxml import etree
from six.moves.urllib_error import URLError
from six.moves.urllib.request import urlopen, urlretrieve

import ssbio.databases.pisa as pisa
import ssbio.utils
from ssbio.protein.structure.structprop import StructProp

try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO

log = logging.getLogger(__name__)

class PDBProp(StructProp):

    """Store information about a protein structure from the Protein Data Bank.

    Extends the :class:`~ssbio.protein.structure.structprop.StructProp` class to allow initialization of the structure
    by its PDB ID, and then enabling downloads of the structure file as well as parsing its metadata.

    Args:
        ident (str):
        description (str):
        chains (str):
        mapped_chains (str):
        structure_path (str):
        file_type (str): ``pdb``, ``mmCif``, ``xml``, ``mmtf`` - file type for files downloaded from the PDB

    """

    def __init__(self, ident, description=None, chains=None, mapped_chains=None, structure_path=None, file_type=None):
        StructProp.__init__(self, ident, description=description, chains=chains, mapped_chains=mapped_chains,
                            is_experimental=True, structure_path=structure_path, file_type=file_type)
        self.experimental_method = None
        self.resolution = None
        self.date = None
        self.taxonomy_name = None
        self.biological_assemblies = DictList()
        """DictList: A list for storing Bioassembly objects related to this PDB ID"""

    def download_structure_file(self, outdir, file_type=None, load_header_metadata=True, force_rerun=False):
        """Download a structure file from the PDB, specifying an output directory and a file type. Optionally download
        the mmCIF header file and parse data from it to store within this object.

        Args:
            outdir (str): Path to output directory
            file_type (str): ``pdb``, ``mmCif``, ``xml``, ``mmtf`` - file type for files downloaded from the PDB
            load_header_metadata (bool): If header metadata should be loaded into this object, fastest with mmtf files
            force_rerun (bool): If structure file should be downloaded even if it already exists

        """
        ssbio.utils.double_check_attribute(object=self, setter=file_type, backup_attribute='file_type',
                                           custom_error_text='Please set file type to be downloaded from the PDB: '
                                                             'pdb, mmCif, xml, or mmtf')

        # XTODO: check if outfile exists using ssbio.utils.force_rerun, pdblist seems to take long if it exists
        # I know why - it's because we're renaming the ent to pdb. need to have mapping from file type to final extension
        # Then check if file exists, if not then download again
        p = PDBList()
        with ssbio.utils.suppress_stdout():
            structure_file = p.retrieve_pdb_file(pdb_code=self.id, pdir=outdir, file_format=file_type, overwrite=force_rerun)
        if not op.exists(structure_file):
            log.debug('{}: {} file not available'.format(self.id, file_type))
            raise URLError('{}.{}: file not available to download'.format(self.id, file_type))
        else:
            log.debug('{}: {} file saved'.format(self.id, file_type))

            # Rename .ent files to .pdb
            if file_type == 'pdb':
                new_name = structure_file.replace('pdb', '').replace('ent', 'pdb')
                os.rename(structure_file, new_name)
                structure_file = new_name

            self.load_structure_path(structure_file, file_type)
            if load_header_metadata and file_type == 'mmtf':
                self.update(parse_mmtf_header(structure_file))
            if load_header_metadata and file_type != 'mmtf':
                self.update(parse_mmcif_header(download_mmcif_header(pdb_id=self.id, outdir=outdir, force_rerun=force_rerun)))

    def get_pisa_complex_predictions(self, outdir, existing_pisa_multimer_xml=None):
        if not existing_pisa_multimer_xml:
            pisa_xmls = pisa.download_pisa_multimers_xml(pdb_ids=self.id, outdir=outdir,
                                                         save_single_xml_files=True)
        else:
            pisa_xmls = {}
            pisa_xmls[self.id] = existing_pisa_multimer_xml
        pisa_dict = pisa.parse_pisa_multimers_xml(pisa_xmls[self.id], download_structures=True,
                                                  outdir=outdir)

    def __json_encode__(self):
        # TODO: investigate why saving with # does not work!
        to_return = {}
        for x in self.__dict__.keys():
            if x == 'pdb_title' or x == 'description':
                sanitized = ssbio.utils.force_string(getattr(self, x)).replace('#', '-')
            else:
                to_return.update({x: getattr(self, x)})
        return to_return

def parse_mmtf_header(infile):
    """Parse an MMTF file and return basic header-like information.

    Args:
        infile (str): Path to MMTF file

    Returns:
        dict: Dictionary of parsed header

    Todo:
        - Can this be sped up by not parsing the 3D coordinate info somehow?
        - OR just store the sequences when this happens since it is already being parsed.

    """
    infodict = {}

    mmtf_decoder = mmtf.parse(infile)
    infodict['date'] = mmtf_decoder.deposition_date
    infodict['release_date'] = mmtf_decoder.release_date
    try:
        infodict['experimental_method'] = [x.decode() for x in mmtf_decoder.experimental_methods]
    except AttributeError:
        infodict['experimental_method'] = [x for x in mmtf_decoder.experimental_methods]
    infodict['resolution'] = mmtf_decoder.resolution
    infodict['description'] = mmtf_decoder.title

    group_name_exclude = ['HOH']
    chem_comp_type_exclude = ['l-peptide linking', 'peptide linking']
    chemicals = list(set([mmtf_decoder.group_list[idx]['groupName'] for idx in mmtf_decoder.group_type_list if mmtf_decoder.group_list[idx]['chemCompType'].lower() not in chem_comp_type_exclude and mmtf_decoder.group_list[idx]['groupName'] not in group_name_exclude]))
    infodict['chemicals'] = chemicals
    return infodict

def download_mmcif_header(pdb_id, outdir='', force_rerun=False):
    """Download a mmCIF header file from the RCSB PDB by ID.

    Args:
        pdb_id: PDB ID
        outdir: Optional output directory, default is current working directory
        force_rerun: If the file should be downloaded again even if it exists

    Returns:
        str: Path to outfile

    """
    # TODO: keep an eye on https://github.com/biopython/biopython/pull/943 Biopython PR#493 for functionality of this
    # method in biopython. extra file types have not been added to biopython download yet

    pdb_id = pdb_id.lower()
    file_type = 'cif'
    folder = 'header'
    outfile = op.join(outdir, '{}.header.{}'.format(pdb_id, file_type))

    if ssbio.utils.force_rerun(flag=force_rerun, outfile=outfile):
        download_link = 'http://files.rcsb.org/{}/{}.{}'.format(folder, pdb_id, file_type)
        urlretrieve(download_link, outfile)
        log.debug('{}: saved header file'.format(outfile))
    else:
        log.debug('{}: header file already saved'.format(outfile))

    return outfile

def parse_mmcif_header(infile):
    """Parse a couple important fields from the mmCIF file format with some manual curation of ligands.

    If you want full access to the mmCIF file just use the MMCIF2Dict class in Biopython.

    Args:
        infile: Path to mmCIF file

    Returns:
        dict: Dictionary of parsed header

    """
    from Bio.PDB.MMCIF2Dict import MMCIF2Dict

    newdict = {}
    try:
        mmdict = MMCIF2Dict(infile)
    except ValueError as e:
        log.exception(e)
        return newdict

    chemical_ids_exclude = ['HOH']
    chemical_types_exclude = ['l-peptide linking','peptide linking']

    if '_struct.title' in mmdict:
        newdict['pdb_title'] = mmdict['_struct.title']
    else:
        log.debug('{}: No title field'.format(infile))

    if '_struct.pdbx_descriptor' in mmdict:
        newdict['description'] = mmdict['_struct.pdbx_descriptor']
    else:
        log.debug('{}: no description field'.format(infile))

    if '_pdbx_database_status.recvd_initial_deposition_date' in mmdict:
        newdict['date'] = mmdict['_pdbx_database_status.recvd_initial_deposition_date']
    elif '_database_PDB_rev.date' in mmdict:
        newdict['date'] = mmdict['_database_PDB_rev.date']
    else:
        log.debug('{}: no date field'.format(infile))

    if '_exptl.method' in mmdict:
        newdict['experimental_method'] = mmdict['_exptl.method']
    else:
        log.debug('{}: no experimental method field'.format(infile))

    # TODO: refactor how to get resolutions based on experimental method
    if '_refine.ls_d_res_high' in mmdict:
        try:
            if isinstance(mmdict['_refine.ls_d_res_high'], list):
                newdict['resolution'] = [float(x) for x in mmdict['_refine.ls_d_res_high']]
            else:
                newdict['resolution'] = float(mmdict['_refine.ls_d_res_high'])
        except:
            try:
                newdict['resolution'] = float(mmdict['_em_3d_reconstruction.resolution'])
            except:
                log.debug('{}: no resolution field'.format(infile))
    else:
        log.debug('{}: no resolution field'.format(infile))

    if '_chem_comp.id' in mmdict:
        chemicals_filtered = ssbio.utils.filter_list_by_indices(mmdict['_chem_comp.id'],
                                                            ssbio.utils.not_find(mmdict['_chem_comp.type'],
                                                                           chemical_types_exclude,
                                                                           case_sensitive=False))
        chemicals_fitered = ssbio.utils.filter_list(chemicals_filtered, chemical_ids_exclude, case_sensitive=True)
        newdict['chemicals'] = chemicals_fitered
    else:
        log.debug('{}: no chemical composition field'.format(infile))

    if '_entity_src_gen.pdbx_gene_src_scientific_name' in mmdict:
        newdict['taxonomy_name'] = mmdict['_entity_src_gen.pdbx_gene_src_scientific_name']
    else:
        log.debug('{}: no organism field'.format(infile))

    return newdict

def download_sifts_xml(pdb_id, outdir='', force_rerun=False):
    """Download the SIFTS file for a PDB ID.

    Args:
        pdb_id (str): PDB ID
        outdir (str): Output directory, current working directory if not specified.
        force_rerun (bool): If the file should be downloaded again even if it exists

    Returns:
        str: Path to downloaded file

    """
    baseURL = 'ftp://ftp.ebi.ac.uk/pub/databases/msd/sifts/xml/'
    filename = '{}.xml.gz'.format(pdb_id.lower())

    outfile = op.join(outdir, filename.split('.')[0] + '.sifts.xml')

    if ssbio.utils.force_rerun(flag=force_rerun, outfile=outfile):
        response = urlopen(baseURL + filename)
        with open(outfile, 'wb') as f:
            f.write(gzip.decompress(response.read()))

    return outfile

def map_uniprot_resnum_to_pdb(uniprot_resnum, chain_id, sifts_file):
    """Map a UniProt residue number to its corresponding PDB residue number.

    This function requires that the SIFTS file be downloaded,
    and also a chain ID (as different chains may have different mappings).

    Args:
        uniprot_resnum (int): integer of the residue number you'd like to map
        chain_id (str): string of the PDB chain to map to
        sifts_file (str): Path to the SIFTS XML file

    Returns:
        (tuple): tuple containing:

            mapped_resnum (int): Mapped residue number
            is_observed (bool): Indicates if the 3D structure actually shows the residue

    """
    # Load the xml with lxml
    parser = etree.XMLParser(ns_clean=True)
    tree = etree.parse(sifts_file, parser)
    root = tree.getroot()

    my_pdb_resnum = None

    # TODO: "Engineered_Mutation is also a possible annotation, need to figure out what to do with that
    my_pdb_annotation = False

    # Find the right chain (entities in the xml doc)
    ent = './/{http://www.ebi.ac.uk/pdbe/docs/sifts/eFamily.xsd}entity'
    for chain in root.findall(ent):
        # TODO: IMPORTANT - entityId is not the chain ID!!! it is just in alphabetical order!
        if chain.attrib['entityId'] == chain_id:
            # Find the "crossRefDb" tag that has the attributes dbSource="UniProt" and  dbResNum="your_resnum_here"
            # Then match it to the crossRefDb dbResNum that has the attribute dbSource="PDBresnum"

            # Check if uniprot + resnum even exists in the sifts file (it won't if the pdb doesn't contain the residue)
            ures = './/{http://www.ebi.ac.uk/pdbe/docs/sifts/eFamily.xsd}crossRefDb[@dbSource="UniProt"][@dbResNum="%s"]' % uniprot_resnum
            my_uniprot_residue = chain.findall(ures)
            if len(my_uniprot_residue) == 1:
                # Get crossRefDb dbSource="PDB"
                parent = my_uniprot_residue[0].getparent()
                pres = './/{http://www.ebi.ac.uk/pdbe/docs/sifts/eFamily.xsd}crossRefDb[@dbSource="PDB"]'
                my_pdb_residue = parent.findall(pres)
                my_pdb_resnum = int(my_pdb_residue[0].attrib['dbResNum'])

                # Get <residueDetail dbSource="PDBe" property="Annotation">
                # Will be Not_Observed if it is not seen in the PDB
                anno = './/{http://www.ebi.ac.uk/pdbe/docs/sifts/eFamily.xsd}residueDetail[@dbSource="PDBe"][@property="Annotation"]'
                my_pdb_annotation = parent.findall(anno)
                if len(my_pdb_annotation) == 1:
                    my_pdb_annotation = my_pdb_annotation[0].text
                    if my_pdb_annotation == 'Not_Observed':
                        my_pdb_annotation = False
                else:
                    my_pdb_annotation = True
            else:
                return None, False

    return my_pdb_resnum, my_pdb_annotation

def best_structures(uniprot_id, outname=None, outdir=None, seq_ident_cutoff=0.0, force_rerun=False):
    """Use the PDBe REST service to query for the best PDB structures for a UniProt ID.

    More information found here: https://www.ebi.ac.uk/pdbe/api/doc/sifts.html
    Link used to retrieve results: https://www.ebi.ac.uk/pdbe/api/mappings/best_structures/:accession
    The list of PDB structures mapping to a UniProt accession sorted by coverage of the protein and, if the same, resolution.

    Here is the ranking algorithm described by the PDB paper:
    https://nar.oxfordjournals.org/content/44/D1/D385.full

    "Finally, a single quality indicator is also calculated for each entry by taking the harmonic average
    of all the percentile scores representing model and model-data-fit quality measures and then subtracting
    10 times the numerical value of the resolution (in Angstrom) of the entry to ensure that resolution plays
    a role in characterising the quality of a structure. This single empirical 'quality measure' value is used
    by the PDBe query system to sort results and identify the 'best' structure in a given context. At present,
    entries determined by methods other than X-ray crystallography do not have similar data quality information
    available and are not considered as 'best structures'."

    Args:
        uniprot_id (str): UniProt Accession ID
        outname (str): Basename of the output file of JSON results
        outdir (str): Path to output directory of JSON results
        seq_ident_cutoff (float): Cutoff results based on percent coverage (in decimal form)
        force_rerun (bool): Obtain best structures mapping ignoring previously downloaded results

    Returns:
        list: Rank-ordered list of dictionaries representing chain-specific PDB entries. Keys are:
            * pdb_id: the PDB ID which maps to the UniProt ID
            * chain_id: the specific chain of the PDB which maps to the UniProt ID
            * coverage: the percent coverage of the entire UniProt sequence
            * resolution: the resolution of the structure
            * start: the structure residue number which maps to the start of the mapped sequence
            * end: the structure residue number which maps to the end of the mapped sequence
            * unp_start: the sequence residue number which maps to the structure start
            * unp_end: the sequence residue number which maps to the structure end
            * experimental_method: type of experiment used to determine structure
            * tax_id: taxonomic ID of the protein's original organism

    """
    outfile = ''

    if not outdir:
        outdir = ''

    # if output dir is specified but not outname, use the uniprot
    if not outname and outdir:
        outname = uniprot_id

    if outname:
        outname = op.join(outdir, outname)
        outfile = '{}.json'.format(outname)

    # Load a possibly existing json file
    if not ssbio.utils.force_rerun(flag=force_rerun, outfile=outfile):
        with open(outfile, 'r') as f:
            raw_data = json.load(f)
        log.debug('{}: loaded existing json file'.format(uniprot_id))

    # Otherwise run the web request
    else:
        # TODO: add a checker for a cached file of uniprot -> PDBs - can be generated within gempro pipeline and stored
        response = requests.get('https://www.ebi.ac.uk/pdbe/api/mappings/best_structures/{}'.format(uniprot_id),
                                data={'key': 'value'})
        if response.status_code == 404:
            log.debug('{}: 404 returned, probably no structures available.'.format(uniprot_id))
            raw_data = {uniprot_id: {}}
        else:
            log.debug('{}: Obtained best structures'.format(uniprot_id))
            raw_data = response.json()

        # Write the json file if specified
        if outfile:
            with open(outfile, 'w') as f:
                json.dump(raw_data, f)
            log.debug('{}: Saved json file of best structures'.format(uniprot_id))

    data = dict(raw_data)[uniprot_id]

    # Filter for sequence identity percentage
    if seq_ident_cutoff != 0:
        for result in data:
            if result['coverage'] < seq_ident_cutoff:
                data.remove(result)

    return data

def blast_pdb(seq, outfile='', outdir='', evalue=0.0001, seq_ident_cutoff=0.0, link=False, force_rerun=False):
    """Returns a list of BLAST hits of a sequence to available structures in the PDB.

    Args:
        seq (str): Your sequence, in string format
        outfile (str): Name of output file
        outdir (str, optional): Path to output directory. Default is the current directory.
        evalue (float, optional): Cutoff for the E-value - filters for significant hits. 0.001 is liberal, 0.0001 is stringent (default).
        seq_ident_cutoff (float, optional): Cutoff results based on percent coverage (in decimal form)
        link (bool, optional): Set to True if a link to the HTML results should be displayed
        force_rerun (bool, optional): If existing BLAST results should not be used, set to True. Default is False

    Returns:
        list: Rank ordered list of BLAST hits in dictionaries.

    """

    if len(seq) < 12:
        raise ValueError('Sequence must be at least 12 residues long.')
    if link:
        page = 'PDB results page: http://www.rcsb.org/pdb/rest/getBlastPDB1?sequence={}&eCutOff={}&maskLowComplexity=yes&matrix=BLOSUM62&outputFormat=HTML'.format(seq, evalue)
        print(page)

    parser = etree.XMLParser(ns_clean=True)

    outfile = op.join(outdir, outfile)
    if ssbio.utils.force_rerun(force_rerun, outfile):
        # Load the BLAST XML results if force_rerun=True
        page = 'http://www.rcsb.org/pdb/rest/getBlastPDB1?sequence={}&eCutOff={}&maskLowComplexity=yes&matrix=BLOSUM62&outputFormat=XML'.format(
                        seq, evalue)
        req = requests.get(page)
        if req.status_code == 200:
            response = req.text

            # Save the XML file
            if outfile:
                with open(outfile, 'w') as f:
                    f.write(response)

            # Parse the XML string
            tree = etree.ElementTree(etree.fromstring(response, parser))
            log.debug('Loaded BLAST results from REST server')
        else:
            log.error('BLAST request timed out')
            return []
    else:
        tree = etree.parse(outfile, parser)
        log.debug('{}: Loaded existing BLAST XML results'.format(outfile))

    # Get length of original sequence to calculate percentages
    len_orig = float(len(seq))

    root = tree.getroot()
    hit_list = []

    for hit in root.findall('BlastOutput_iterations/Iteration/Iteration_hits/Hit'):
        info = {}

        hitdef = hit.find('Hit_def')
        if hitdef is not None:
            info['hit_pdb'] = hitdef.text.split('|')[0].split(':')[0].lower()
            info['hit_pdb_chains'] = hitdef.text.split('|')[0].split(':')[2].split(',')

        # One PDB can align to different parts of the sequence
        # Will just choose the top hit for this single PDB
        hsp = hit.findall('Hit_hsps/Hsp')[0]

        # Number of identical residues
        hspi = hsp.find('Hsp_identity')
        if hspi is not None:
            info['hit_num_ident'] = int(hspi.text)
            info['hit_percent_ident'] = int(hspi.text)/len_orig

            if int(hspi.text)/len_orig < seq_ident_cutoff:
                log.debug('{}: does not meet sequence identity cutoff'.format(hitdef.text.split('|')[0].split(':')[0]))
                continue

        # Number of similar residues (positive hits)
        hspp = hsp.find('Hsp_positive')
        if hspp is not None:
            info['hit_num_similar'] = int(hspp.text)
            info['hit_percent_similar'] = int(hspp.text) / len_orig

        # Total number of gaps (unable to align in either query or subject)
        hspg = hsp.find('Hsp_gaps')
        if hspg is not None:
            info['hit_num_gaps'] = int(hspg.text)
            info['hit_percent_gaps'] = int(hspg.text) / len_orig

        # E-value of BLAST
        hspe = hsp.find('Hsp_evalue')
        if hspe is not None:
            info['hit_evalue'] = float(hspe.text)

        # Score of BLAST
        hsps = hsp.find('Hsp_score')
        if hsps is not None:
            info['hit_score'] = float(hsps.text)

        hit_list.append(info)

    log.debug("{}: Number of BLAST hits".format(len(hit_list)))
    return hit_list

def blast_pdb_df(blast_results):
    """Make a dataframe of BLAST results"""
    cols = ['hit_pdb', 'hit_pdb_chains', 'hit_evalue', 'hit_score', 'hit_num_ident', 'hit_percent_ident',
            'hit_num_similar', 'hit_percent_similar', 'hit_num_gaps', 'hit_percent_gaps']
    return pd.DataFrame.from_records(blast_results, columns=cols)

def _property_table():
    """Download the PDB -> resolution table directly from the RCSB PDB REST service.

    See the other fields that you can get here: http://www.rcsb.org/pdb/results/reportField.do

    Returns:
        Pandas DataFrame: table of structureId as the index, resolution and experimentalTechnique as the columns

    """
    url = 'http://www.rcsb.org/pdb/rest/customReport.csv?pdbids=*&customReportColumns=structureId,resolution,experimentalTechnique,releaseDate&service=wsfile&format=csv'
    r = requests.get(url)
    p = pd.read_csv(StringIO(r.text)).set_index('structureId')
    return p

def get_resolution(pdb_id):
    """Quick way to get the resolution of a PDB ID using the table of results from the REST service

    Returns infinity if the resolution is not available.

    Returns:
        float: resolution of a PDB ID in Angstroms

    TODO:
        - Unit test

    """

    pdb_id = pdb_id.upper()
    if pdb_id not in _property_table().index:
        raise ValueError('PDB ID not in property table')
    else:
        resolution = _property_table().ix[pdb_id, 'resolution']
        if pd.isnull(resolution):
            log.debug('{}: no resolution available, probably not an X-ray crystal structure')
            resolution = float('inf')

    return resolution

def get_release_date(pdb_id):
    """Quick way to get the release date of a PDB ID using the table of results from the REST service

    Returns None if the release date is not available.

    Returns:
        str: Organism of a PDB ID

    """

    pdb_id = pdb_id.upper()
    if pdb_id not in _property_table().index:
        raise ValueError('PDB ID not in property table')
    else:
        release_date = _property_table().ix[pdb_id, 'releaseDate']
        if pd.isnull(release_date):
            log.debug('{}: no release date available')
            release_date = None

    return release_date

def get_num_bioassemblies(pdb_id, cache=False, outdir=None, force_rerun=False):
    """Check if there are bioassemblies using the PDB REST API, and if there are, get the number of bioassemblies
    available.

    See: https://www.rcsb.org/pages/webservices/rest, section 'List biological assemblies'

    Not all PDB entries have biological assemblies available and some have multiple. Details that are necessary to
    recreate a biological assembly from the asymmetric unit can be accessed from the following requests.

    - Number of biological assemblies associated with a PDB entry
    - Access the transformation information needed to generate a biological assembly (nr=0 will return information
      for the asymmetric unit, nr=1 will return information for the first assembly, etc.)

    A query of https://www.rcsb.org/pdb/rest/bioassembly/nrbioassemblies?structureId=1hv4 returns this::

        <nrBioAssemblies structureId="1HV4" hasAssemblies="true" count="2"/>

    Args:
        pdb_id (str): PDB ID
        cache (bool): If the XML file should be downloaded
        outdir (str): If cache, then specify the output directory
        force_rerun (bool): If cache, and if file exists, specify if API should be queried again

    """
    parser = etree.XMLParser(ns_clean=True)

    if not outdir:
        outdir = os.getcwd()
    outfile = op.join(outdir, '{}_nrbiomols.xml'.format(pdb_id))

    if ssbio.utils.force_rerun(force_rerun, outfile):
        page = 'https://www.rcsb.org/pdb/rest/bioassembly/nrbioassemblies?structureId={}'.format(pdb_id)
        req = requests.get(page)

        if req.status_code == 200:
            response = req.text

            # Save the XML file
            if cache:
                with open(outfile, 'w') as f:
                    f.write(response)

            # Parse the XML string
            tree = etree.ElementTree(etree.fromstring(response, parser))
            log.debug('Loaded bioassembly information from REST server')
        else:
            log.error('Request timed out')
            req.raise_for_status()
    else:
        tree = etree.parse(outfile, parser)
        log.debug('{}: Loaded existing XML results'.format(outfile))

    r = tree.getroot()
    has_biomols = r.get('hasAssemblies')
    if has_biomols == 'true':
        has_biomols = True
    else:
        has_biomols = False

    if has_biomols:
        num_biomols = r.get('count')
    else:
        num_biomols = 0

    num_biomols = int(num_biomols)
    return num_biomols

def get_bioassembly_info(pdb_id, biomol_num, cache=False, outdir=None, force_rerun=False):
    """Get metadata about a bioassembly from the RCSB PDB's REST API.

    See: https://www.rcsb.org/pdb/rest/bioassembly/bioassembly?structureId=1hv4&nr=1
    The API returns an XML file containing the information on a biological assembly that looks like this::

        <bioassembly structureId="1HV4" assemblyNr="1" method="PISA" desc="author_and_software_defined_assembly">
            <transformations operator="1" chainIds="A,B,C,D">
                <transformation index="1">
                    <matrix m11="1.00000000" m12="0.00000000" m13="0.00000000" m21="0.00000000" m22="1.00000000" m23="0.00000000" m31="0.00000000" m32="0.00000000" m33="1.00000000"/>
                    <shift v1="0.00000000" v2="0.00000000" v3="0.00000000"/>
                </transformation>
            </transformations>
        </bioassembly>

    Args:
        pdb_id (str): PDB ID
        biomol_num (int): Biological assembly number you are interested in
        cache (bool): If the XML file should be downloaded
        outdir (str): If cache, then specify the output directory
        force_rerun (bool): If cache, and if file exists, specify if API should be queried again

    """
    parser = etree.XMLParser(ns_clean=True)
    #
    # if not outdir:
    #     outdir = os.getcwd()
    # outfile = op.join(outdir, '{}.xml'.format(self.id))
    #
    # if ssbio.utils.force_rerun(force_rerun, outfile):
    #     page = 'https://www.rcsb.org/pdb/rest/bioassembly/bioassembly?structureId={}&nr={}'.format(
    #         self.original_pdb_id, biomol_num)
    #     req = requests.get(page)
    #
    #     if req.status_code == 200:
    #         response = req.text
    #
    #         # Save the XML file
    #         if cache:
    #             with open(outfile, 'w') as f:
    #                 f.write(response)
    #
    #         # Parse the XML string
    #         r = xmltodict.parse(response)
    #         log.debug('Loaded bioassembly information from REST server')
    #     else:
    #         log.error('Request timed out')
    #         req.raise_for_status()
    # else:
    #     with open(outfile, 'r') as f:
    #         r = xmltodict.parse(f.read())
    #     log.debug('{}: Loaded existing XML results'.format(outfile))
    #
    # self.biomol_to_chain_dict[biomol_num] = {'chains': r['bioassembly']['transformations']['@chainIds'],
    #                                          'multiplier': len(r['bioassembly']['transformations']['transformation'])}
    # # TODO: figure out how to store matrices etc.
    #
    # log.info('{}_{}: ')

def download_biomol(pdb_id, biomol_num, outdir, file_type='pdb', force_rerun=False):
    import zlib
    from six.moves.urllib_error import URLError
    from six.moves.urllib.request import urlopen, urlretrieve
    import contextlib

    ssbio.utils.make_dir(outdir)
    server_folder = pdb_id[1:3]

    if file_type == 'pdb':
        # server = 'ftp://ftp.wwpdb.org/pub/pdb/data/biounit/coordinates/divided/{}/'.format(server_folder)
        server = 'https://files.rcsb.org/download/'
        server_filename = pdb_id + '.pdb%i.gz' % biomol_num
        local_filename = pdb_id + '_bio%i.pdb' % biomol_num
        outfile = op.join(outdir, local_filename)

    elif file_type.lower() == 'mmcif' or file_type.lower() == 'cif':
        server = 'ftp://ftp.wwpdb.org/pub/pdb/data/biounit/mmCIF/divided/{}/'.format(server_folder)
        server_filename = pdb_id + '-assembly%i.cif.gz' % biomol_num
        local_filename = pdb_id + '_bio%i.cif' % biomol_num
        outfile = op.join(outdir, local_filename)

    else:
        raise ValueError('Biological assembly only available in PDB or mmCIF file types.')

    if ssbio.utils.force_rerun(flag=force_rerun, outfile=outfile):
        download_link = op.join(server, server_filename)
        try:
            with contextlib.closing(urlopen(download_link)) as f:
                decompressed_data = zlib.decompress(f.read(), 16 + zlib.MAX_WBITS)
                with open(op.join(outdir, local_filename), 'wb') as f:
                    f.write(decompressed_data)
        except URLError as e:
            print(e)
            return None

    return outfile

########################################################################################################################
########################################################################################################################
# DEPRECATED FUNCTIONS
########################################################################################################################
########################################################################################################################

@deprecation.deprecated(deprecated_in="1.0", removed_in="2.0",
                        details="Use Biopython's PDBList.retrieve_pdb_file function instead")
def download_structure(pdb_id, file_type, outdir='', only_header=False, force_rerun=False):
    """Download a structure from the RCSB PDB by ID. Specify the file type desired.

    Args:
        pdb_id: PDB ID
        file_type: pdb, pdb.gz, mmcif, cif, cif.gz, xml.gz, mmtf, mmtf.gz
        outdir: Optional output directory
        only_header: If only the header file should be downloaded
        force_rerun: If the file should be downloaded again even if it exists

    Returns:
        str: Path to outfile

    """
    # method in biopython. extra file types have not been added to biopython download yet

    pdb_id = pdb_id.lower()
    file_type = file_type.lower()
    file_types = ['pdb', 'pdb.gz', 'mmcif', 'cif', 'cif.gz', 'xml.gz', 'mmtf', 'mmtf.gz']
    if file_type not in file_types:
        raise ValueError('Invalid file type, must be either: pdb, pdb.gz, cif, cif.gz, xml.gz, mmtf, mmtf.gz')

    if file_type == 'mmtf':
        file_type = 'mmtf.gz'

    if file_type.endswith('.gz'):
        gzipped = True
    else:
        gzipped = False

    if file_type == 'mmcif':
        file_type = 'cif'

    if only_header:
        folder = 'header'
        outfile = op.join(outdir, '{}.header.{}'.format(pdb_id, file_type))
    else:
        folder = 'download'
        outfile = op.join(outdir, '{}.{}'.format(pdb_id, file_type))

    if ssbio.utils.force_rerun(flag=force_rerun, outfile=outfile):
        if file_type == 'mmtf.gz' or file_type == 'mmtf':
            mmtf_api = '1.0'
            download_link = 'http://mmtf.rcsb.org/v{}/full/{}.mmtf.gz'.format(mmtf_api, pdb_id)
        else:
            download_link = 'http://files.rcsb.org/{}/{}.{}'.format(folder, pdb_id, file_type)

        urlretrieve(download_link, outfile)

        if gzipped:
            outfile = ssbio.utils.gunzip_file(infile=outfile,
                                              outfile=outfile.strip('.gz'),
                                              outdir=outdir,
                                              delete_original=False,
                                              force_rerun_flag=force_rerun)

        log.debug('{}: saved structure file'.format(outfile))
    else:
        if file_type == 'mmtf.gz':
            outfile = op.join(outdir, '{}.{}'.format(pdb_id, 'mmtf'))
        log.debug('{}: structure file already saved'.format(outfile))

    return outfile


# -*- coding: utf-8 -*-
#
# pulsepacket.py
#
# This file is part of NEST.
#
# Copyright (C) 2004 The NEST Initiative
#
# NEST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# NEST 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 NEST.  If not, see <http://www.gnu.org/licenses/>.

"""
Pulse packet example
--------------------

This script compares the average and individual membrane potential excursions
in response to a single pulse packet with an analytically acquired voltage
trace (see: Diesmann [1]_)
A pulse packet is a transient spike volley with a Gaussian rate profile.
The user can specify the neural parameters, the parameters of the
pulse-packet and the number of trials.

References
~~~~~~~~~~~~

.. [1] Diesmann M. 2002. Dissertation. Conditions for stable propagation of
       synchronous spiking in cortical neural networks: Single neuron dynamics
       and network properties.
       http://d-nb.info/968772781/34.

"""

###############################################################################
# First, we import all necessary modules for simulation, analysis and
# plotting.

import scipy.special as sp
import nest
import numpy
import matplotlib.pyplot as plt

# Properties of pulse packet:

a = 100            # number of spikes in one pulse packet
sdev = 10.         # width of pulse packet (ms)
weight = 0.1       # PSP amplitude (mV)
pulsetime = 500.   # occurrence time (center) of pulse-packet (ms)

# Network and neuron characteristics:

n_neurons = 100    # number of neurons
cm = 200.          # membrane capacitance (pF)
tau_s = 0.5        # synaptic time constant (ms)
tau_m = 20.        # membrane time constant (ms)
V0 = 0.0           # resting potential (mV)
Vth = numpy.inf    # firing threshold, high value to avoid spiking

# Simulation and analysis parameters:

simtime = 1000.               # how long we simulate (ms)
simulation_resolution = 0.1  # (ms)
sampling_resolution = 1.   # for voltmeter (ms)
convolution_resolution = 1.   # for the analytics (ms)

# Some parameters in base units.

Cm = cm * 1e-12            # convert to Farad
Weight = weight * 1e-12    # convert to Ampere
Tau_s = tau_s * 1e-3       # convert to sec
Tau_m = tau_m * 1e-3       # convert to sec
Sdev = sdev * 1e-3         # convert to sec
Convolution_resolution = convolution_resolution * 1e-3  # convert to sec

###############################################################################
# This function calculates the membrane potential excursion in response
# to a single input spike (the equation is given for example in Diesmann [1]_,
# eq.2.3).
# It expects:
#
# * ``Time``: a time array or a single time point (in sec)
# * ``Tau_s`` and ``Tau_m``: the synaptic and the membrane time constant (in sec)
# * ``Cm``: the membrane capacity (in Farad)
# * ``Weight``: the synaptic weight (in Ampere)
#
# It returns the provoked membrane potential (in mV)

def make_psp(Time, Tau_s, Tau_m, Cm, Weight):
    term1 = (1 / Tau_s - 1 / Tau_m)
    term2 = numpy.exp(-Time / Tau_s)
    term3 = numpy.exp(-Time / Tau_m)
    PSP = (Weight / Cm * numpy.exp(1) / Tau_s *
           (((-Time * term2) / term1) + (term3 - term2) / term1 ** 2))
    return PSP * 1e3

###############################################################################
# This function finds the exact location of the maximum of the PSP caused by a
# single input spike. The location is obtained by setting the first derivative
# of the equation for the PSP (see ``make_psp()``) to zero. The resulting
# equation can be expressed in terms of a `LambertW function`.
# This function expects:
#
# * ``Tau_s`` and ``Tau_m``: the synaptic and membrane time constant (in sec)
#
# It returns the location of the maximum (in sec)

def LambertWm1(x):
    # Using scipy to mimic the gsl_sf_lambert_Wm1 function.
    return sp.lambertw(x, k=-1 if x < 0 else 0).real

def find_loc_pspmax(tau_s, tau_m):
    var = tau_m / tau_s
    lam = LambertWm1(-numpy.exp(-1 / var) / var)
    t_maxpsp = (-var * lam - 1) / var / (1 / tau_s - 1 / tau_m) * 1e-3
    return t_maxpsp

###############################################################################
# First, we construct a Gaussian kernel for a given standard derivation
# (``sig``) and mean value (``mu``). In this case the standard derivation is
# the width of the pulse packet (see [1]_).

sig = Sdev
mu = 0.0
x = numpy.arange(-4 * sig, 4 * sig, Convolution_resolution)
term1 = 1 / (sig * numpy.sqrt(2 * numpy.pi))
term2 = numpy.exp(-(x - mu) ** 2 / (sig ** 2 * 2))
gauss = term1 * term2 * Convolution_resolution

###############################################################################
# Second, we calculate the PSP of a neuron due to a single spiking input.
# (see Diesmann 2002, eq. 2.3).
# Since we do that in discrete time steps, we first construct an array
# (``t_psp``) that contains the time points we want to consider. Then, the
# function ``make_psp()`` (that creates the PSP) takes the time array as its
# first argument.

t_psp = numpy.arange(0, 10 * (Tau_m + Tau_s), Convolution_resolution)
psp = make_psp(t_psp, Tau_s, Tau_m, Cm, Weight)

###############################################################################
# Now, we want to normalize the PSP amplitude to one. We therefore have to
# divide the PSP by its maximum ([1]_ sec 6.1). The function
# ``find_loc_pspmax()`` returns the exact time point (``t_pspmax``) when we
# expect the maximum to occur. The function ``make_psp()`` calculates the
# corresponding PSP value, which is our PSP amplitude (``psp_amp``).

t_pspmax = find_loc_pspmax(Tau_s, Tau_m)
psp_amp = make_psp(t_pspmax, Tau_s, Tau_m, Cm, Weight)
psp_norm = psp / psp_amp

###############################################################################
# Now we have all ingredients to compute the membrane potential excursion
# (`U`). This calculation implies a convolution of the Gaussian with the
# normalized PSP (see [1]_, eq. 6.9). In order to avoid an offset in the
# convolution, we need to add a pad of zeros on the left side of the
# normalized PSP. Later on we want to compare our analytical results with the
# simulation outcome. Therefore we need a time vector (`t_U`) with the correct
# temporal resolution, which places the excursion of the potential at the
# correct time.
psp_norm = numpy.pad(psp_norm, [len(psp_norm) - 1, 1])
U = a * psp_amp * numpy.convolve(gauss, psp_norm)
ulen = len(U)
t_U = (convolution_resolution * numpy.linspace(-ulen / 2., ulen / 2., ulen) +
       pulsetime + 1.)

###############################################################################
# In this section we simulate a network of multiple neurons.
# All these neurons receive an individual pulse packet that is drawn from a
# Gaussian distribution.
#
# We reset the Kernel, define the simulation resolution and set the
# verbosity using ``set_verbosity`` to suppress info messages.

nest.ResetKernel()
nest.SetKernelStatus({'resolution': simulation_resolution})
nest.set_verbosity("M_WARNING")

###############################################################################
# Afterwards we create several neurons, the same amount of
# pulse-packet-generators and a voltmeter. All these nodes/devices
# have specific properties that are specified in device specific
# dictionaries (here: `neuron_pars` for the neurons, `ppg_pars`
# for the and pulse-packet-generators and `vm_pars` for the voltmeter).

neuron_pars = {
    'V_th': Vth,
    'tau_m': tau_m,
    'tau_syn_ex': tau_s,
    'C_m': cm,
    'E_L': V0,
    'V_reset': V0,
    'V_m': V0
}
neurons = nest.Create('iaf_psc_alpha', n_neurons, neuron_pars)
ppg_pars = {
    'pulse_times': [pulsetime],
    'activity': a,
    'sdev': sdev
}
ppgs = nest.Create('pulsepacket_generator', n_neurons, ppg_pars)
vm_pars = {'interval': sampling_resolution}
vm = nest.Create('voltmeter', 1, vm_pars)

###############################################################################
# Now, we connect each pulse generator to one neuron via static synapses.
# We want to keep all properties of the static synapse constant except the
# synaptic weight. Therefore we change the weight with  the help of the command
# ``SetDefaults``.
# The command ``Connect`` connects all kinds of nodes/devices. Since multiple
# nodes/devices can be connected in different ways e.g., each source connects
# to all targets, each source connects to a subset of targets or each source
# connects to exactly one target, we have to specify the connection. In our
# case we use the ``one_to_one`` connection routine since we connect one pulse
# generator (source) to one neuron (target).
# In addition we also connect the `voltmeter` to the `neurons`.

nest.SetDefaults('static_synapse', {'weight': weight})
nest.Connect(ppgs, neurons, 'one_to_one')
nest.Connect(vm, neurons)

###############################################################################
# In the next step we run the simulation for a given duration in ms.

nest.Simulate(simtime)

###############################################################################
# Finally, we record the membrane potential, when it occurred and to which
# neuron it belongs. The sender and the time point of a voltage
# data point at position x in the voltage array (``V_m``), can be found at the
# same position x in the sender (`senders`) and the time array (`times`).

Vm = vm.get('events', 'V_m')
times = vm.get('events', 'times')
senders = vm.get('events', 'senders')

###############################################################################
# Here we plot the membrane potential derived from the theory and from the
# simulation. Since we simulate multiple neurons that received slightly
# different pulse packets, we plot the individual and the averaged membrane
# potentials.
#
# We plot the analytical solution U (the resting potential V0 shifts the
# membrane potential up or downwards).

plt.plot(t_U, U + V0, 'r', lw=2, zorder=3, label='analytical solution')

###############################################################################
# Then we plot all individual membrane potentials.
# The time axes is the range of the simulation time in steps of ms.

Vm_single = [Vm[senders == n.global_id] for n in neurons]
simtimes = numpy.arange(1, simtime)
for idn in range(n_neurons):
    if idn == 0:
        plt.plot(simtimes, Vm_single[idn], 'gray',
                 zorder=1, label='single potentials')
    else:
        plt.plot(simtimes, Vm_single[idn], 'gray', zorder=1)

###############################################################################
# Finally, we plot the averaged membrane potential.

Vm_average = numpy.mean(Vm_single, axis=0)
plt.plot(simtimes, Vm_average, 'b', lw=4,
         zorder=2, label='averaged potential')
plt.legend()
plt.xlabel('time (ms)')
plt.ylabel('membrane potential (mV)')
plt.xlim((-5 * (tau_m + tau_s) + pulsetime,
          10 * (tau_m + tau_s) + pulsetime))
plt.show()


"""
A backend for FLTK

Copyright: Gregory Lielens, Free Field Technologies SA and
           John D. Hunter 2004

This code is released under the matplotlib license

"""

from __future__ import division

import os, sys, math

import fltk as Fltk
from backend_agg import FigureCanvasAgg

import os.path

import matplotlib

from matplotlib import rcParams, verbose
from matplotlib.cbook import is_string_like
from matplotlib.backend_bases import \
     RendererBase, GraphicsContextBase, FigureManagerBase, FigureCanvasBase,\
     NavigationToolbar2, cursors
from matplotlib.figure import Figure
from matplotlib._pylab_helpers import Gcf
import matplotlib.windowing as windowing
from matplotlib.widgets import SubplotTool

import thread,time

Fl_running=thread.allocate_lock()
def Fltk_run_interactive():
    global Fl_running
    if Fl_running.acquire(0):
      while True:
        Fltk.Fl.check()
        time.sleep(0.005)
    else:
        print "fl loop already running"

# the true dots per inch on the screen; should be display dependent
# see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi
PIXELS_PER_INCH = 75

cursord= {
    cursors.HAND:     Fltk.FL_CURSOR_HAND,
    cursors.POINTER:     Fltk.FL_CURSOR_ARROW,
    cursors.SELECT_REGION:   Fltk.FL_CURSOR_CROSS,
    cursors.MOVE:   Fltk.FL_CURSOR_MOVE
    }

special_key={
    Fltk.FL_Shift_R:'shift',
    Fltk.FL_Shift_L:'shift',
    Fltk.FL_Control_R:'control',
    Fltk.FL_Control_L:'control',
    Fltk.FL_Control_R:'control',
    Fltk.FL_Control_L:'control',
    65515:'win',
    65516:'win',
    }

def error_msg_fltk(msg, parent=None):
    Fltk.fl_message(msg)

def draw_if_interactive():
    if matplotlib.is_interactive():
        figManager =  Gcf.get_active()
        if figManager is not None:
            figManager.canvas.draw()

def ishow():
    """
    Show all the figures and enter the fltk mainloop in another thread
    This allows to keep hand in interractive python session
    Warning: does not work under windows
    This should be the last line of your script
    """
    for manager in Gcf.get_all_fig_managers():
        manager.show()
    if show._needmain:
        thread.start_new_thread(Fltk_run_interactive,())
    show._needmain = False

def show():
    """
    Show all the figures and enter the fltk mainloop

    This should be the last line of your script
    """
    for manager in Gcf.get_all_fig_managers():
        manager.show()
    #mainloop, if an fltk program exist no need to call that
    #threaded (and interractive) version
    if show._needmain:
        Fltk.Fl.run()
        show._needmain = False

show._needmain = True

def new_figure_manager(num, *args, **kwargs):
    """
    Create a new figure manager instance
    """
    FigureClass = kwargs.pop('FigureClass', Figure)
    figure = FigureClass(*args, **kwargs)
    window = Fltk.Fl_Double_Window(10,10,30,30)
    canvas = FigureCanvasFltkAgg(figure)
    window.end()
    window.show()
    window.make_current()
    figManager = FigureManagerFltkAgg(canvas, num, window)
    if matplotlib.is_interactive():
        figManager.show()
    return figManager

class FltkCanvas(Fltk.Fl_Widget):

    def __init__(self,x,y,w,h,l,source):
        Fltk.Fl_Widget.__init__(self, 0, 0, w, h, "canvas")
        self._source=source
        self._oldsize=(None,None)
        self._draw_overlay = False
        self._button = None
        self._key = None

    def draw(self):
        newsize=(self.w(),self.h())
        if(self._oldsize !=newsize):
            self._oldsize =newsize
            self._source.resize(newsize)
            self._source.draw()
        t1,t2,w,h = self._source.figure.bbox.bounds
        Fltk.fl_draw_image(self._source.buffer_rgba(0,0),0,0,int(w),int(h),4,0)
        self.redraw()

    def blit(self,bbox=None):
        if bbox is None:
            t1,t2,w,h = self._source.figure.bbox.bounds
        else:
           t1o,t2o,wo,ho = self._source.figure.bbox.bounds
           t1,t2,w,h = bbox.bounds
        x,y=int(t1),int(t2)
        Fltk.fl_draw_image(self._source.buffer_rgba(x,y),x,y,int(w),int(h),4,int(wo)*4)
        #self.redraw()

    def handle(self, event):
        x=Fltk.Fl.event_x()
        y=Fltk.Fl.event_y()
        yf=self._source.figure.bbox.height() - y
        if event == Fltk.FL_FOCUS or event == Fltk.FL_UNFOCUS:
            return 1
        elif event == Fltk.FL_KEYDOWN:
            ikey= Fltk.Fl.event_key()
            if(ikey<=255):
                self._key=chr(ikey)
            else:
                try:
                    self._key=special_key[ikey]
                except:
                    self._key=None
            FigureCanvasBase.key_press_event(self._source, self._key)
            return 1
        elif event == Fltk.FL_KEYUP:
            FigureCanvasBase.key_release_event(self._source, self._key)
            self._key=None
        elif event == Fltk.FL_PUSH:
            self.window().make_current()
            if Fltk.Fl.event_button1():
                self._button = 1
            elif  Fltk.Fl.event_button2():
                self._button = 2
            elif  Fltk.Fl.event_button3():
                self._button = 3
            else:
                self._button = None

            if self._draw_overlay:
                self._oldx=x
                self._oldy=y
            if Fltk.Fl.event_clicks():
                FigureCanvasBase.button_press_event(self._source, x, yf, self._button)
                return 1
            else:
                FigureCanvasBase.button_press_event(self._source, x, yf, self._button)
                return 1
        elif event == Fltk.FL_ENTER:
            self.take_focus()
            return 1
        elif event == Fltk.FL_LEAVE:
            return 1
        elif event == Fltk.FL_MOVE:
            FigureCanvasBase.motion_notify_event(self._source, x, yf)
            return 1
        elif event == Fltk.FL_DRAG:
            self.window().make_current()
            if self._draw_overlay:
                self._dx=Fltk.Fl.event_x()-self._oldx
                self._dy=Fltk.Fl.event_y()-self._oldy
                Fltk.fl_overlay_rect(self._oldx,self._oldy,self._dx,self._dy)
            FigureCanvasBase.motion_notify_event(self._source, x, yf)
            return 1
        elif event == Fltk.FL_RELEASE:
            self.window().make_current()
            if self._draw_overlay:
                Fltk.fl_overlay_clear()
            FigureCanvasBase.button_release_event(self._source, x, yf, self._button)
            self._button = None
            return 1
        return 0

class FigureCanvasFltkAgg(FigureCanvasAgg):
    def __init__(self, figure):
        FigureCanvasAgg.__init__(self,figure)
        t1,t2,w,h = self.figure.bbox.bounds
        w, h = int(w), int(h)
        self.canvas=FltkCanvas(0, 0, w, h, "canvas",self)
        #self.draw()

    def resize(self,size):
        w, h = size
        # compute desired figure size in inches
        dpival = self.figure.dpi.get()
        winch = w/dpival
        hinch = h/dpival
        self.figure.set_size_inches(winch,hinch)

    def draw(self):
        FigureCanvasAgg.draw(self)
        self.canvas.redraw()

    def blit(self,bbox):
        self.canvas.blit(bbox)

    show = draw

    def widget(self):
        return self.canvas

    def start_event_loop(self,timeout):
        FigureCanvasBase.start_event_loop_default(self,timeout)
    start_event_loop.__doc__=FigureCanvasBase.start_event_loop_default.__doc__

    def stop_event_loop(self):
        FigureCanvasBase.stop_event_loop_default(self)
    stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__

def destroy_figure(ptr,figman):
    figman.window.hide()
    Gcf.destroy(figman._num)

class FigureManagerFltkAgg(FigureManagerBase):
    """
    Public attributes

    canvas      : The FigureCanvas instance
    num         : The Figure number
    toolbar     : The fltk.Toolbar
    window      : The fltk.Window
    """
    def __init__(self, canvas, num, window):
        FigureManagerBase.__init__(self, canvas, num)
        #Fltk container window
        t1,t2,w,h = canvas.figure.bbox.bounds
        w, h = int(w), int(h)
        self.window = window
        self.window.size(w,h+30)
        self.window_title="Figure %d" % num
        self.window.label(self.window_title)
        self.window.size_range(350,200)
        self.window.callback(destroy_figure,self)
        self.canvas = canvas
        self._num =  num
        if matplotlib.rcParams['toolbar']=='classic':
            self.toolbar = NavigationToolbar( canvas, self )
        elif matplotlib.rcParams['toolbar']=='toolbar2':
            self.toolbar = NavigationToolbar2FltkAgg( canvas, self )
        else:
            self.toolbar = None
        self.window.add_resizable(canvas.widget())
        if self.toolbar:
            self.window.add(self.toolbar.widget())
            self.toolbar.update()
        self.window.show()

        def notify_axes_change(fig):
            'this will be called whenever the current axes is changed'
            if self.toolbar != None: self.toolbar.update()
        self.canvas.figure.add_axobserver(notify_axes_change)

    def resize(self, event):
        width, height = event.width, event.height
        self.toolbar.configure(width=width) # , height=height)

    def show(self):
        _focus = windowing.FocusManager()
        self.canvas.draw()
        self.window.redraw()

    def set_window_title(self, title):
        self.window_title=title
        self.window.label(title)

class AxisMenu:
    def __init__(self, toolbar):
        self.toolbar=toolbar
        self._naxes = toolbar.naxes
        self._mbutton = Fltk.Fl_Menu_Button(0,0,50,10,"Axes")
        self._mbutton.add("Select All",0,select_all,self,0)
        self._mbutton.add("Invert All",0,invert_all,self,Fltk.FL_MENU_DIVIDER)
        self._axis_txt=[]
        self._axis_var=[]
        for i in range(self._naxes):
            self._axis_txt.append("Axis %d" % (i+1))
            self._mbutton.add(self._axis_txt[i],0,set_active,self,Fltk.FL_MENU_TOGGLE)
        for i in range(self._naxes):
            self._axis_var.append(self._mbutton.find_item(self._axis_txt[i]))
            self._axis_var[i].set()
    def adjust(self, naxes):
        if self._naxes < naxes:
            for i in range(self._naxes, naxes):
                self._axis_txt.append("Axis %d" % (i+1))
                self._mbutton.add(self._axis_txt[i],0,set_active,self,Fltk.FL_MENU_TOGGLE)
            for i in range(self._naxes, naxes):
                self._axis_var.append(self._mbutton.find_item(self._axis_txt[i]))
                self._axis_var[i].set()
        elif self._naxes > naxes:
            for i in range(self._naxes-1, naxes-1, -1):
                self._mbutton.remove(i+2)
            if(naxes):
                self._axis_var=self._axis_var[:naxes-1]
                self._axis_txt=self._axis_txt[:naxes-1]
            else:
                self._axis_var=[]
                self._axis_txt=[]
        self._naxes = naxes
        set_active(0,self)

    def widget(self):
        return self._mbutton

    def get_indices(self):
        a = [i for i in range(len(self._axis_var)) if self._axis_var[i].value()]
        return a

def set_active(ptr,amenu):
    amenu.toolbar.set_active(amenu.get_indices())

def invert_all(ptr,amenu):
    for a in amenu._axis_var:
        if not a.value(): a.set()
    set_active(ptr,amenu)

def select_all(ptr,amenu):
    for a in amenu._axis_var:
        a.set()
    set_active(ptr,amenu)

class FLTKButton:
    def __init__(self, text, file, command,argument,type="classic"):
        file = os.path.join(rcParams['datapath'], 'images', file)
        self.im = Fltk.Fl_PNM_Image(file)
        size=26
        if type=="repeat":
            self.b = Fltk.Fl_Repeat_Button(0,0,size,10)
            self.b.box(Fltk.FL_THIN_UP_BOX)
        elif type=="classic":
            self.b = Fltk.Fl_Button(0,0,size,10)
            self.b.box(Fltk.FL_THIN_UP_BOX)
        elif type=="light":
            self.b = Fltk.Fl_Light_Button(0,0,size+20,10)
            self.b.box(Fltk.FL_THIN_UP_BOX)
        elif type=="pushed":
            self.b = Fltk.Fl_Button(0,0,size,10)
            self.b.box(Fltk.FL_UP_BOX)
            self.b.down_box(Fltk.FL_DOWN_BOX)
            self.b.type(Fltk.FL_TOGGLE_BUTTON)
        self.tooltiptext=text+" "
        self.b.tooltip(self.tooltiptext)
        self.b.callback(command,argument)
        self.b.image(self.im)
        self.b.deimage(self.im)
        self.type=type

    def widget(self):
        return self.b

class NavigationToolbar:
    """
    Public attriubutes

      canvas   - the FigureCanvas  (FigureCanvasFltkAgg = customised fltk.Widget)

    """

    def __init__(self, canvas, figman):
        #xmin, xmax = canvas.figure.bbox.intervalx().get_bounds()
        #height, width = 50, xmax-xmin
        self.canvas = canvas
        self.figman = figman

        Fltk.Fl_File_Icon.load_system_icons()
        self._fc = Fltk.Fl_File_Chooser( ".", "*", Fltk.Fl_File_Chooser.CREATE, "Save Figure" )
        self._fc.hide()
        t1,t2,w,h = canvas.figure.bbox.bounds
        w, h = int(w), int(h)
        self._group = Fltk.Fl_Pack(0,h+2,1000,26)
        self._group.type(Fltk.FL_HORIZONTAL)
        self._axes=self.canvas.figure.axes
        self.naxes = len(self._axes)
        self.omenu = AxisMenu( toolbar=self)

        self.bLeft = FLTKButton(
            text="Left", file="stock_left.ppm",
            command=pan,argument=(self,1,'x'),type="repeat")

        self.bRight = FLTKButton(
            text="Right", file="stock_right.ppm",
            command=pan,argument=(self,-1,'x'),type="repeat")

        self.bZoomInX = FLTKButton(
            text="ZoomInX",file="stock_zoom-in.ppm",
            command=zoom,argument=(self,1,'x'),type="repeat")

        self.bZoomOutX = FLTKButton(
            text="ZoomOutX", file="stock_zoom-out.ppm",
            command=zoom, argument=(self,-1,'x'),type="repeat")

        self.bUp = FLTKButton(
            text="Up", file="stock_up.ppm",
            command=pan,argument=(self,1,'y'),type="repeat")

        self.bDown = FLTKButton(
            text="Down", file="stock_down.ppm",
            command=pan,argument=(self,-1,'y'),type="repeat")

        self.bZoomInY = FLTKButton(
            text="ZoomInY", file="stock_zoom-in.ppm",
            command=zoom,argument=(self,1,'y'),type="repeat")

        self.bZoomOutY = FLTKButton(
            text="ZoomOutY",file="stock_zoom-out.ppm",
            command=zoom, argument=(self,-1,'y'),type="repeat")

        self.bSave = FLTKButton(
            text="Save", file="stock_save_as.ppm",
            command=save_figure, argument=self)

        self._group.end()

    def widget(self):
        return self._group

    def close(self):
        Gcf.destroy(self.figman._num)

    def set_active(self, ind):
        self._ind = ind
        self._active = [ self._axes[i] for i in self._ind ]

    def update(self):
        self._axes = self.canvas.figure.axes
        naxes = len(self._axes)
        self.omenu.adjust(naxes)

def pan(ptr, arg):
    base,direction,axe=arg
    for a in base._active:
        if(axe=='x'):
            a.panx(direction)
        else:
            a.pany(direction)
    base.figman.show()

def zoom(ptr, arg):
    base,direction,axe=arg
    for a in base._active:
        if(axe=='x'):
             a.zoomx(direction)
        else:
            a.zoomy(direction)
    base.figman.show()

def save_figure(ptr,base):
    filetypes = base.canvas.get_supported_filetypes()
    default_filetype = base.canvas.get_default_filetype()
    sorted_filetypes = filetypes.items()
    sorted_filetypes.sort()

    selected_filter = 0
    filters = []
    for i, (ext, name) in enumerate(sorted_filetypes):
        filter = '%s (*.%s)' % (name, ext)
        filters.append(filter)
        if ext == default_filetype:
            selected_filter = i
    filters = '\t'.join(filters)

    file_chooser=base._fc
    file_chooser.filter(filters)
    file_chooser.filter_value(selected_filter)
    file_chooser.show()
    while file_chooser.visible() :
        Fltk.Fl.wait()
    fname=None
    if(file_chooser.count() and file_chooser.value(0) != None):
        fname=""
        (status,fname)=Fltk.fl_filename_absolute(fname, 1024, file_chooser.value(0))

    if fname is None: # Cancel
        return
    #start from last directory
    lastDir = os.path.dirname(fname)
    file_chooser.directory(lastDir)
    format = sorted_filetypes[file_chooser.filter_value()][0]

    try:
        base.canvas.print_figure(fname, format=format)
    except IOError, msg:
        err = '\n'.join(map(str, msg))
        msg = 'Failed to save %s: Error msg was\n\n%s' % (
            fname, err)
        error_msg_fltk(msg)

class NavigationToolbar2FltkAgg(NavigationToolbar2):
    """
    Public attriubutes

      canvas   - the FigureCanvas
      figman   - the Figure manager

    """

    def __init__(self, canvas, figman):
        self.canvas = canvas
        self.figman = figman
        NavigationToolbar2.__init__(self, canvas)
        self.pan_selected=False
        self.zoom_selected=False

    def set_cursor(self, cursor):
        Fltk.fl_cursor(cursord[cursor],Fltk.FL_BLACK,Fltk.FL_WHITE)

    def dynamic_update(self):
        self.canvas.draw()

    def pan(self,*args):
        self.pan_selected=not  self.pan_selected
        self.zoom_selected = False
        self.canvas.canvas._draw_overlay= False
        if self.pan_selected:
            self.bPan.widget().value(1)
        else:
            self.bPan.widget().value(0)
        if self.zoom_selected:
            self.bZoom.widget().value(1)
        else:
            self.bZoom.widget().value(0)
        NavigationToolbar2.pan(self,args)

    def zoom(self,*args):
        self.zoom_selected=not  self.zoom_selected
        self.canvas.canvas._draw_overlay=self.zoom_selected
        self.pan_selected = False
        if self.pan_selected:
            self.bPan.widget().value(1)
        else:
            self.bPan.widget().value(0)
        if self.zoom_selected:
            self.bZoom.widget().value(1)
        else:
            self.bZoom.widget().value(0)
        NavigationToolbar2.zoom(self,args)

    def configure_subplots(self,*args):
        window = Fltk.Fl_Double_Window(100,100,480,240)
        toolfig = Figure(figsize=(6,3))
        canvas = FigureCanvasFltkAgg(toolfig)
        window.end()
        toolfig.subplots_adjust(top=0.9)
        tool =  SubplotTool(self.canvas.figure, toolfig)
        window.show()
        canvas.show()

    def _init_toolbar(self):
        Fltk.Fl_File_Icon.load_system_icons()
        self._fc = Fltk.Fl_File_Chooser( ".", "*", Fltk.Fl_File_Chooser.CREATE, "Save Figure" )
        self._fc.hide()
        t1,t2,w,h = self.canvas.figure.bbox.bounds
        w, h = int(w), int(h)
        self._group = Fltk.Fl_Pack(0,h+2,1000,26)
        self._group.type(Fltk.FL_HORIZONTAL)
        self._axes=self.canvas.figure.axes
        self.naxes = len(self._axes)
        self.omenu = AxisMenu( toolbar=self)

        self.bHome = FLTKButton(
            text="Home", file="home.ppm",
            command=self.home,argument=self)

        self.bBack = FLTKButton(
            text="Back", file="back.ppm",
            command=self.back,argument=self)

        self.bForward = FLTKButton(
            text="Forward", file="forward.ppm",
            command=self.forward,argument=self)

        self.bPan = FLTKButton(
            text="Pan/Zoom",file="move.ppm",
            command=self.pan,argument=self,type="pushed")

        self.bZoom = FLTKButton(
            text="Zoom to rectangle",file="zoom_to_rect.ppm",
            command=self.zoom,argument=self,type="pushed")

        self.bsubplot = FLTKButton( text="Configure Subplots", file="subplots.ppm",
                                   command = self.configure_subplots,argument=self,type="pushed")
        self.bSave = FLTKButton(
            text="Save", file="filesave.ppm",
            command=save_figure, argument=self)

        self._group.end()
        self.message = Fltk.Fl_Output(0,0,w,8)
        self._group.add_resizable(self.message)
        self.update()

    def widget(self):
        return self._group

    def close(self):
        Gcf.destroy(self.figman._num)

    def set_active(self, ind):
        self._ind = ind
        self._active = [ self._axes[i] for i in self._ind ]

    def update(self):
        self._axes = self.canvas.figure.axes
        naxes = len(self._axes)
        self.omenu.adjust(naxes)
        NavigationToolbar2.update(self)

    def set_message(self, s):
        self.message.value(s)

FigureManager = FigureManagerFltkAgg


#########################################################################
## This program is part of 'MOOSE', the
## Messaging Object Oriented Simulation Environment.
##           Copyright (C) 2014 Upinder S. Bhalla. and NCBS
## It is made available under the terms of the
## GNU Lesser General Public License version 2.1
## See the file COPYING.LIB for the full notice.
#########################################################################

import moose
import pylab
import numpy
import matplotlib.pyplot as plt
import sys

def runAndSavePlots( name ):
    runtime = 20.0
    moose.reinit()
    moose.start( runtime )
    pa = moose.Neutral( '/model/graphs/' + name )
    for x in moose.wildcardFind( '/model/#graphs/conc#/#' ):
        if ( x.tick != -1 ):
            tabname = '/model/graphs/' + name + '/' + x.name + '.' + name
            y = moose.Table( tabname )
            y.vector = x.vector
            y.tick = -1

# Takes args ee, gsl, or gssa
def switchSolvers( solver ):
        if ( moose.exists( 'model/kinetics/stoich' ) ):
            moose.delete( '/model/kinetics/stoich' )
            moose.delete( '/model/kinetics/ksolve' )
        compt = moose.element( '/model/kinetics' )
        if ( solver == 'gsl' ):
            ksolve = moose.Ksolve( '/model/kinetics/ksolve' )
        if ( solver == 'gssa' ):
            ksolve = moose.Gsolve( '/model/kinetics/ksolve' )
        if ( solver != 'ee' ):
            stoich = moose.Stoich( '/model/kinetics/stoich' )
            stoich.compartment = compt
            stoich.ksolve = ksolve
            stoich.path = "/model/kinetics/##"

def main():
    """
    At zero order, you can select the solver you want to use within the
    function moose.loadModel( filename, modelpath, solver ).
    Having loaded in the model, you can change the solver to use on it.
    This example illustrates how to assign and change solvers for a
    kinetic model. This process is necessary in two situations:

    * If we want to change the numerical method employed, for example,
      from deterministic to stochastic.
    * If we are already using a solver, and we have changed the reaction
      network by adding or removing molecules or reactions.

    Note that we do not have to change the solvers if the volume or
    reaction rates change.
    In this example the model is loaded in with a gsl solver. The
    sequence of solver calculations is:

    #. gsl
    #. ee
    #. gsl
    #. gssa
    #. gsl

    If you're removing the solvers, you just delete the stoichiometry
    object and the associated ksolve/gsolve. Should there be diffusion
    (a dsolve)then you should delete that too. If you're
    building the solvers up again, then you must do the following
    steps in order:

    #. build up the ksolve/gsolve and stoich (any order)
    #. Assign stoich.ksolve
    #. Assign stoich.path.

    See the Reaction-diffusion section should you want to do diffusion
    as well.

    """

    solver = "gsl"  # Pick any of gsl, gssa, ee..
    mfile = '../genesis/kkit_objects_example.g'
    modelId = moose.loadModel( mfile, 'model', solver )
    # Increase volume so that the stochastic solver gssa
    # gives an interesting output
    compt = moose.element( '/model/kinetics' )
    compt.volume = 1e-19
    runAndSavePlots( 'gsl' )
    #########################################################
    switchSolvers( 'ee' )
    runAndSavePlots( 'ee' )
    #########################################################
    switchSolvers( 'gsl' )
    runAndSavePlots( 'gsl2' )
    #########################################################
    switchSolvers( 'gssa' )
    runAndSavePlots( 'gssa' )
    #########################################################
    switchSolvers( 'gsl' )
    runAndSavePlots( 'gsl3' )
    #########################################################

    # Display all plots.
    fig = plt.figure( figsize = (12, 10) )
    orig = fig.add_subplot( 511 )
    gsl = fig.add_subplot( 512 )
    ee = fig.add_subplot( 513 )
    gsl2 = fig.add_subplot( 514 )
    gssa = fig.add_subplot( 515 )
    plotdt = moose.element( '/clock' ).tickDt[18]
    for x in moose.wildcardFind( '/model/#graphs/conc#/#' ):
        t = numpy.arange( 0, x.vector.size, 1 ) * plotdt
        orig.plot( t, x.vector, label=x.name )
    for x in moose.wildcardFind( '/model/graphs/gsl/#' ):
        t = numpy.arange( 0, x.vector.size, 1 ) * plotdt
        gsl.plot( t, x.vector, label=x.name )
    for x in moose.wildcardFind( '/model/graphs/ee/#' ):
        t = numpy.arange( 0, x.vector.size, 1 ) * plotdt
        ee.plot( t, x.vector, label=x.name )
    for x in moose.wildcardFind( '/model/graphs/gsl2/#' ):
        t = numpy.arange( 0, x.vector.size, 1 ) * plotdt
        gsl2.plot( t, x.vector, label=x.name )
    for x in moose.wildcardFind( '/model/graphs/gssa/#' ):
        t = numpy.arange( 0, x.vector.size, 1 ) * plotdt
        gssa.plot( t, x.vector, label=x.name )
    plt.legend()

    pylab.show()
    quit()

# Run the 'main' if this script is executed standalone.
if __name__ == '__main__':
	main()