from __future__ import division, print_function, absolute_import, unicode_literals
#*****************************************************************
#    pyGSTi 0.9:  Copyright 2015 Sandia Corporation
#    This Software is released under the GPL license detailed
#    in the file "license.txt" in the top-level pyGSTi directory
#*****************************************************************
""" Functions for generating plots """

import numpy             as _np
import matplotlib.pyplot as _plt
import matplotlib        as _matplotlib
import os                as _os
import warnings          as _warnings

from scipy.stats       import chi2             as _chi2

from .. import algorithms   as _alg
from .. import tools        as _tools
from .. import construction as _construction
from .. import objects      as _objs

from .figure import ReportFigure as _ReportFigure

import time as _time  #DEBUG TIMER


def get_gatestring_map(gateString, dataset, strs, fidpair_filter=None,
                       gatestring_filter=None):
    """ 
    Pre-compute a list of (i,j,gstr) tuples for use in other matrix-
    generation functions.  

    This consolidates all the logic for selecting a subset (via fidpairs_filter,
    gatestring_filter, or  dataset membership) of prep + base + effect
    strings to compute.  The element (i,j,gstr) means that the (i,j)-th
    element of a resulting matrix corresponds to the gate string gstr.
    Typically gstr = prep[j] + gateString + effect[i].  Matrix indices that
    are absent correspond to Nan entries in a resulting matrix.

    Parameters
    ----------
    gateString : tuple of gate labels
        The base gate sequence that is sandwiched between each effectStr
        and prepStr.

    dataset : DataSet
        The data used to test for gate sequence membership

    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    fidpair_filter : list, optional
        If not None, a list of (iRhoStr,iEStr) tuples specifying a subset of
        all the prepStr,effectStr pairs to include in a result matrix.

    gatestring_filter : list, optional
        If not None, a list of GateString objects specifying which elements of
        result matrices should be computed.  Any matrix entry corresponding to
        an gate string *not* in this list is set to NaN.  When both
        fidpair_filter and gatesetring_filter are non-None, gatestring_filter
        is given precedence.


    Returns
    -------
    tuples : list
        A list of (i,j,gstr) tuples.
    rows : int
        The number of rows in resulting matrices
    cols : int
        The number of columns in resulting matrices
    """
    tuples = []
    prepStrs, effectStrs = strs # LEXICOGRAPHICAL VS MATRIX ORDER
    if gateString is None: 
        return tuples, len(effectStrs),len(prepStrs) #all-NaN mxs

    if gatestring_filter is not None:
        gs_filter_dict = { gs: True for gs in gatestring_filter } #fast lookups

    #No filtering -- just fiducial pair check
    for i,effectStr in enumerate(effectStrs):
        for j,prepStr in enumerate(prepStrs):
            gstr = prepStr + gateString + effectStr
            if dataset is None or gstr in dataset:
                #Note: gatestring_filter trumps fidpair_filter
                if gatestring_filter is None:
                    if fidpair_filter is None or (j,i) in fidpair_filter:
                        tuples.append((i,j,gstr))
                elif gstr in gs_filter_dict:
                    tuples.append((i,j,gstr))

    return tuples,len(effectStrs),len(prepStrs)


def total_count_matrix(gatestring_map, dataset):
    """
    Computes the total count matrix for a base gatestring.

    Parameters
    ----------
    gatestring_map : tuple of (tuples, rows, cols)
        A tuple specifying how to map gate strings to matrix indices.

    dataset : DataSet
        The data used to specify the counts

    Returns
    -------
    numpy array of shape (M,N)
        total count values (sum of count values for each SPAM label)
        corresponding to gate sequences where gateString is sandwiched
        between the specified set of N prep-fiducial and M effect-fiducial
        gate strings.
    """
    tuples,rows,cols = gatestring_map
    ret = _np.nan * _np.ones( (rows,cols), 'd')
    for i,j,gstr in tuples:
        ret[i,j] = dataset[ gstr ].total()
    return ret


def count_matrices(gatestring_map, dataset, spamlabels):
    """
    Computes spamLabel's count matrix for a base gatestring.

    Parameters
    ----------
    gatestring_map : tuple of (tuples, rows, cols)
        A tuple specifying how to map gate strings to matrix indices.

    dataset : DataSet
        The data used to specify the counts

    spamlabels : list of strings
        The spam labels to extract counts for, e.g. ['plus']

    Returns
    -------
    numpy array of shape ( len(spamlabels), len(effectStrs), len(prepStrs) )
        count values corresponding to spamLabel and gate sequences
        where gateString is sandwiched between the each prep-fiducial and
        effect-fiducial pair.
    """
    tuples,rows,cols = gatestring_map
    ret = _np.nan * _np.ones( (len(spamlabels),rows,cols), 'd')
    for i,j,gstr in tuples:
        datarow = dataset[ gstr ]
        ret[:,i,j] = [datarow[sl] for sl in spamlabels]
    return ret


def frequency_matrices(gatestring_map, dataset, spamlabels):
    """
    Computes spamLabel's frequency matrix for a base gatestring.

    Parameters
    ----------
    gatestring_map : tuple of (tuples, rows, cols)
        A tuple specifying how to map gate strings to matrix indices.

    dataset : DataSet
        The data used to specify the frequencies

    spamlabels : list of strings
        The spam labels to extract frequencies for, e.g. ['plus']


    Returns
    -------
    numpy array of shape ( len(spamlabels), len(effectStrs), len(prepStrs) )
        frequency values corresponding to spamLabel and gate sequences
        where gateString is sandwiched between the each prep-fiducial,
        effect-fiducial pair.
    """
    return count_matrices(gatestring_map, dataset, spamlabels) \
           / total_count_matrix( gatestring_map, dataset)[None,:,:]



def probability_matrices(gatestring_map, gateset, spamlabels,
                         probs_precomp_dict=None):
    """
    Computes spamLabel's probability matrix for a base gatestring.

    Parameters
    ----------
    gatestring_map : tuple of (tuples, rows, cols)
        A tuple specifying how to map gate strings to matrix indices.

    gateset : GateSet
        The gate set used to specify the probabilities

    spamlabels : list of strings
        The spam labels to extract probabilities for, e.g. ['plus']

    probs_precomp_dict : dict, optional
        A dictionary of precomputed probabilities.  Keys are gate strings
        and values are prob-dictionaries (as returned from GateSet.probs)
        corresponding to each gate string.

    Returns
    -------
    numpy array of shape ( len(spamlabels), len(effectStrs), len(prepStrs) )
        probability values corresponding to spamLabel and gate sequences
        where gateString is sandwiched between the each prep-fiducial, 
        effect-fiducial pair.
    """
    tuples,rows,cols = gatestring_map
    ret = _np.nan * _np.ones( (len(spamlabels),rows,cols), 'd')
    if probs_precomp_dict is None:
        for i,j,gstr in tuples:
            probs = gateset.probs(gstr)
            ret[:,i,j] = [probs[sl] for sl in spamlabels]
    else:
        for i,j,gstr in tuples:
            probs = probs_precomp_dict[gstr]
            ret[:,i,j] = [probs[sl] for sl in spamlabels]

    return ret


def chi2_matrix(gatestring_map, dataset, gateset, minProbClipForWeighting=1e-4,
                probs_precomp_dict=None):
    """
    Computes the chi^2 matrix for a base gatestring.

    Parameters
    ----------
    gatestring_map : tuple of (tuples, rows, cols)
        A tuple specifying how to map gate strings to matrix indices.

    dataset : DataSet
        The data used to specify frequencies and counts

    gateset : GateSet
        The gate set used to specify the probabilities and SPAM labels

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight (see chi2fn).

    probs_precomp_dict : dict, optional
        A dictionary of precomputed probabilities.  Keys are gate strings
        and values are prob-dictionaries (as returned from GateSet.probs)
        corresponding to each gate string.

    Returns
    -------
    numpy array of shape ( len(effectStrs), len(prepStrs) )
        chi^2 values corresponding to gate sequences where
        gateString is sandwiched between the each prep-fiducial,
        effect-fiducial pair.
    """
    spamlabels = gateset.get_spam_labels()
    cntMxs  = total_count_matrix(   gatestring_map, dataset)[None,:,:]
    probMxs = probability_matrices( gatestring_map, gateset, spamlabels,
                                    probs_precomp_dict)
    freqMxs = frequency_matrices(   gatestring_map, dataset, spamlabels)
    chiSqMxs= _tools.chi2fn( cntMxs, probMxs, freqMxs,
                                     minProbClipForWeighting)
    return chiSqMxs.sum(axis=0) # sum over spam labels


def logl_matrix(gatestring_map, dataset, gateset, minProbClip=1e-6,
                probs_precomp_dict=None):
    """
    Computes the log-likelihood matrix of 2*( log(L)_upperbound - log(L) )
    values for a base gatestring.

    Parameters
    ----------
    gatestring_map : tuple of (tuples, rows, cols)
        A tuple specifying how to map gate strings to matrix indices.

    dataset : DataSet
        The data used to specify frequencies and counts

    gateset : GateSet
        The gate set used to specify the probabilities and SPAM labels

    minProbClip : float, optional
        defines the minimum probability "patch-point" of the log-likelihood function.

    probs_precomp_dict : dict, optional
        A dictionary of precomputed probabilities.  Keys are gate strings
        and values are prob-dictionaries (as returned from GateSet.probs)
        corresponding to each gate string.


    Returns
    -------
    numpy array of shape ( len(effectStrs), len(prepStrs) )
        logl values corresponding to gate sequences where
        gateString is sandwiched between the each prep-fiducial,
        effect-fiducial pair.
    """
    spamlabels = gateset.get_spam_labels()
    cntMxs  = total_count_matrix(   gatestring_map, dataset)[None,:,:]
    probMxs = probability_matrices( gatestring_map, gateset, spamlabels,
                                    probs_precomp_dict)
    freqMxs = frequency_matrices(   gatestring_map, dataset, spamlabels)
    logLMxs = _tools.two_delta_loglfn( cntMxs, probMxs, freqMxs, minProbClip)
    return logLMxs.sum(axis=0) # sum over spam labels


def small_eigval_err_rate(sigma, dataset, directGSTgatesets):
    """
    Compute per-gate error rate.

    The per-gate error rate, extrapolated from the smallest eigvalue
    of the Direct GST estimate of the given gate string sigma.

    Parameters
    ----------
    sigma : GateString or tuple of gate labels
        The gate sequence that is used to estimate the error rate

    dataset : DataSet
        The dataset used obtain gate string frequencies

    directGSTgatesets : dictionary of GateSets
        A dictionary with keys = gate strings and
        values = GateSets.

    Returns
    -------
    float
        the approximate per-gate error rate.
    """
    if sigma is None: return _np.nan # in plot processing, "None" gatestrings = no plot output = nan values
    gs_direct = directGSTgatesets[sigma]
    minEigval = min(abs(_np.linalg.eigvals( gs_direct.gates["GsigmaLbl"] )))
    return 1.0 - minEigval**(1.0/max(len(sigma),1)) # (approximate) per-gate error rate; max averts divide by zero error

def besttxtcolor( x, cmap, norm ):
    """
    Determinining function for whether text should be white or black

    Parameters
    ----------
    x : float
        Value of the cell in question
    cmap : matplotlib colormap
        Colormap assigning colors to the cells
    norm : matplotlib normalizer
        Function to map cell values to the interval [0, 1] for use by a
        colormap

    Returns
    -------
    {"white","black"}
    """
    cell_color = cmap(norm(x))
    R, G, B = cell_color[:3]
    # Perceived brightness calculation from http://alienryderflex.com/hsp.html
    P = _np.sqrt(0.299*R**2 + 0.587*G**2 + 0.114*B**2)
    return "black" if 0.5 <= P else "white"

class LinLogNorm(_matplotlib.colors.Normalize):
    def __init__(self, trans=None, vmin=None, vmax=None, clip=False):
        super(LinLogNorm, self).__init__(vmin=vmin, vmax=vmax, clip=clip)
        self.trans = trans

    def inverse(self, value):
        norm_trans = super(LinLogNorm, self).__call__(self.trans)
        deltav = self.vmax - self.vmin
        return_value = _np.where(_np.greater(0.5, value),
                                 2*value*(self.trans - self.vmin) + self.vmin,
                                 deltav*_np.power(norm_trans, 2*(1 - value)))
        if return_value.shape==():
            return return_value.item()
        else:
            return return_value.view(_np.ma.MaskedArray)

    def __call__(self, value, clip=None):

        if isinstance(value, _np.ma.MaskedArray) and value.count() == 0:
            # no unmasked elements, in which case a matplotlib bug causes the
            # __call__ below to fail (numpy.bool_ has no attribute '_mask')
            return_value = _np.ma.array( _np.zeros(value.shape),
                                         mask=_np.ma.getmask(value))
            # so just create a dummy return value with the correct size
            # that has all it's entries masked (like value does)
            if return_value.shape==(): return return_value.item()
            else: return return_value.view(_np.ma.MaskedArray)

        lin_norm_value = super(LinLogNorm, self).__call__(value)

        if self.trans is None:
            self.trans = (self.vmax - self.vmin)/10 + self.vmin

        norm_trans = super(LinLogNorm, self).__call__(self.trans)
        log10_norm_trans = _np.ma.log10(norm_trans)
        with _np.errstate(divide='ignore'):
            # Ignore the division-by-zero error that occurs when 0 is passed to
            # log10 (the resulting NaN is filtered out by the where and is
            # harmless).

            #deal with numpy bug in handling masked nan values (nan still gives
            # "invalid value" warnings/errors even when masked)
            if _np.ma.is_masked(lin_norm_value):
                lin_norm_value = _np.ma.array(lin_norm_value.filled(1e100),
                                              mask=_np.ma.getmask(lin_norm_value))
            return_value = _np.ma.where(_np.ma.greater(norm_trans, lin_norm_value),
                                        lin_norm_value/(2*norm_trans),
                                        (log10_norm_trans -
                                         _np.ma.log10(lin_norm_value)) /
                                        (2*log10_norm_trans) + 0.5)

        if return_value.shape==():
            return return_value.item()
        else:
            return return_value.view(_np.ma.MaskedArray)

class MidPointNorm(_matplotlib.colors.Normalize):
    """
    A class for normalizing data which takes on
    positive and negative values.

    Taken from http://stackoverflow.com/questions/7404116/defining-the-midpoint-of-a-colormap-in-matplotlib
    """

    def __init__(self, midpoint=0, vmin=None, vmax=None, clip=False):
        super(MidPointNorm, self).__init__(vmin=vmin, vmax=vmax, clip=clip)
        self.midpoint = midpoint

    def __call__(self, value, clip=None):
        if clip is None:
            clip = self.clip

        result, is_scalar = self.process_value(value)

        self.autoscale_None(result)
        vmin, vmax, midpoint = self.vmin, self.vmax, self.midpoint

        if not (vmin < midpoint < vmax):
            raise ValueError("midpoint must be between maxvalue and minvalue.")
        elif vmin == vmax:
            result.fill(0) # Or should it be all masked? Or 0.5?
        elif vmin > vmax:
            raise ValueError("maxvalue must be bigger than minvalue")
        else:
            vmin = float(vmin)
            vmax = float(vmax)
            if clip:
                mask = _np.ma.getmask(result)
                result = _np.ma.array(_np.clip(result.filled(vmax), vmin, vmax),
                                  mask=mask)

            # ma division is very slow; we can take a shortcut
            resdat = result.filled(0) #masked entries to 0 to avoid nans

            #First scale to -1 to 1 range, than to from 0 to 1.
            resdat -= midpoint
            resdat[resdat>0] /= abs(vmax - midpoint)
            resdat[resdat<0] /= abs(vmin - midpoint)

            resdat /= 2.
            resdat += 0.5
            result = _np.ma.array(resdat, mask=result.mask, copy=False)

        if is_scalar:
            result = result[0]
        return result

    def inverse(self, value):
        if not self.scaled():
            raise ValueError("Not invertible until scaled")
        vmin, vmax, midpoint = self.vmin, self.vmax, self.midpoint

        if _matplotlib.cbook.iterable(value):
            val = _np.ma.asarray(value)
            val = 2 * (val-0.5)
            val[val>0] *= abs(vmax - midpoint) #pylint: disable=unsubscriptable-object
            val[val<0] *= abs(vmin - midpoint) #pylint: disable=unsubscriptable-object
            val += midpoint
            return val
        else:
            val = 2 * (val - 0.5)
            if val < 0:
                return  val*abs(vmin-midpoint) + midpoint
            else:
                return  val*abs(vmax-midpoint) + midpoint

def splice_cmaps(cmaps, name=None, splice_points=None):
    """
    Take a list of cmaps and create a new cmap that joins them at specified
    points.

    Parameters
    ----------
    cmaps : list of matplotlib.colors.Colormap
        The colormaps ordered according to how they should appear in the final
        colormap

    name : string, optional
        The name for the colormap. If no name is given, the name
        ``"spliced_cmap1name_cmap2name_..."`` is assigned to the colormap.

    splice_points : ordered list of floats in (0, 1), optional
        The transition points when one colormap should end and the next should
        begin. Should have one less point than the number of cmaps provided. If
        no list is provided, the splice points will be arranged to split the
        interval (0, 1) up into equal seqments.

    Returns
    -------
    matplotlib.colors.LinearSegmentedColormap
        A cmap combining the provided cmaps
    """
    if name is None:
        name = '_'.join(['spliced'] + [cmap.name for cmap in cmaps])

    n_cmaps = len(cmaps)

    if splice_points is None:
        splice_points = _np.linspace(0, 1, n_cmaps + 1)[1:-1].tolist()

    n_sps = len(splice_points)

    if n_sps != n_cmaps - 1:
        raise ValueError(('The number of splice points, {0}, is not one less' +
            ' than the number of colormaps, {1}.').format(n_sps, n_cmaps))

    ranges = list(zip([0.0] + splice_points, splice_points + [1.0]))

    red_list = []
    green_list = []
    blue_list = []
    alpha_list = []

    # First segment
    cmap = cmaps[0]
    N = cmap.N
    low_val, high_val = ranges[0]
    input_values = _np.linspace(0.0, 1.0, N)
    scaled_values = _np.linspace(low_val, high_val, N)
    colors = cmap(input_values)
    for color, value in zip(colors[:-1], scaled_values[:-1]):
        r, g, b, a = color
        red_list.append((value, r, r))
        green_list.append((value, g, g))
        blue_list.append((value, b, b))
        alpha_list.append((value, a, a))

    # Middle segments
    for cmap, prev_cmap, rng in zip(cmaps[1:-1], cmaps[:-2], ranges[1:-1]):
        N = cmap.N
        low_val, high_val = rng
        input_values = _np.linspace(0.0, 1.0, N)
        scaled_values = _np.linspace(low_val, high_val, N)
        colors = cmap(input_values)
        prev_r, prev_g, prev_b, prev_a = prev_cmap(1.0)
        r, g, b, a = colors[0]
        red_list.append((low_val, prev_r, r))
        green_list.append((low_val, prev_g, g))
        blue_list.append((low_val, prev_b, b))
        alpha_list.append((low_val, prev_a, a))
        for color, value in zip(colors[1:-1], scaled_values[1:-1]):
            r, g, b, a = color
            red_list.append((value, r, r))
            green_list.append((value, g, g))
            blue_list.append((value, b, b))
            alpha_list.append((value, a, a))

    # Final segment
    cmap = cmaps[-1]
    prev_cmap = cmaps[-2]
    N = cmap.N
    low_val, high_val = ranges[-1]
    input_values = _np.linspace(0.0, 1.0, N)
    scaled_values = _np.linspace(low_val, high_val, N)
    colors = cmap(input_values)
    prev_r, prev_g, prev_b, prev_a = prev_cmap(1.0)
    r, g, b, a = colors[0]
    red_list.append((low_val, prev_r, r))
    green_list.append((low_val, prev_g, g))
    blue_list.append((low_val, prev_b, b))
    alpha_list.append((low_val, prev_a, a))
    for color, value in zip(colors[1:], scaled_values[1:]):
        r, g, b, a = color
        red_list.append((value, r, r))
        green_list.append((value, g, g))
        blue_list.append((value, b, b))
        alpha_list.append((value, a, a))

    cdict = {'red': red_list, 'green': green_list, 'blue': blue_list,
             'alpha': alpha_list}
    spliced_cmap = _matplotlib.colors.LinearSegmentedColormap(name, cdict)

    # return name, splice_points, cdict, spliced_cmap

    return spliced_cmap

def make_linear_cmap(start_color, final_color, name=None):
    """
    Make a color map that simply linearly interpolates between a start color
    and final color in RGB(A) space.

    Parameters
    ----------
    start_color : 3- (or 4-) tuple
        The (r, g, b[, a]) values for the start color.

    final_color : 3- (or 4-) tuple
        The (r, g, b[, a]) values for the final color.

    name : string
        A name for the colormap. If not provided, a name will be constructed
        from the colors at the two endpoints.

    Returns
    -------
    A cmap that interpolates between the endpoints in RGB(A) space.
    """
    labels = ['red', 'green', 'blue', 'alpha']
    cdict = {label: [(0, start_color[idx], start_color[idx]),
                     (1, final_color[idx], final_color[idx])]
             for label, idx in zip(labels, list(range(len(start_color))))}

    if name is None:
        name = 'linear_' + str(start_color) + '-' + str(final_color)

    return _matplotlib.colors.LinearSegmentedColormap(name, cdict)

def get_transition(N, eps=.1):
    '''
    Computes the transition point for the LinLogNorm class.

    Parameters
    ----------
    N : int
      number of chi2_1 random variables

    eps : float
      The quantile

    Returns
    -------
    trans : float
       An approximate 1-eps quantile for the maximum of N chi2_1 random
       variables.
    '''

    trans = _np.ceil(_chi2.ppf(1 - eps / N, 1))

    return trans

class StdColormapFactory(object):
    """
    Class used to create a standard GST colormap.
    """

    def __init__(self, kind, vmin=None, vmax=None, n_boxes=None, linlg_pcntle=.05, dof=1,\
                        midpoint=0):

        assert kind in ['linlog', 'div', 'seq'],\
            'Please instantiate the StdColormapFactory with a valid kind of colormap.'

        if kind != 'linlog':
            if (vmin is None) or (vmax is None):
                raise ValueError('vmin and vmax must both not be None for non-linlog colormap types.')
        else:
            if n_boxes is None:
                raise ValueError('linlog colormap type requires a non-None value for n_boxes.')

        self.kind = kind
        self.vmin = vmin
        self.vmax = vmax
        self.N = n_boxes
        self.percentile = linlg_pcntle
        self.dof = dof
        self.midpoint = midpoint

    def get_norm(self):
        #Creates the normalization class
        if self.kind == 'seq':
            norm = _matplotlib.colors.Normalize(vmin=self.vmin, vmax=self.vmax, clip=False)
        elif self.kind == 'div':
            norm = MidPointNorm(midpoint=self.midpoint, vmin=self.vmin, vmax=self.vmax)
        else:
            N = max(self.N,1) #don't divide by N == 0 (if there are no boxes)
            linlog_trans = _np.ceil(_chi2.ppf(1 - self.percentile / N, self.dof))
            norm = LinLogNorm(trans=linlog_trans)

        return norm

    def get_cmap(self):
        #Creates the colormap
        if self.kind == 'seq':
            cmap = _matplotlib.cm.get_cmap('Greys')
        elif self.kind == 'div':
            cmap = _matplotlib.cm.get_cmap('bwr')
        else:
            # Colors ranging from white to gray on [0.0, 0.5) and pink to red on
            # [0.5, 1.0] such that the perceived brightness of the pink matches the
            # gray.
            grayscale_cmap = make_linear_cmap((1, 1, 1), (0.5, 0.5, 0.5))
            red_cmap = make_linear_cmap((.698, .13, .133), (1, 0, 0))
            cmap = splice_cmaps([grayscale_cmap, red_cmap], 'linlog')

        cmap.set_bad('w',1)

        return cmap


def _eformat(f, prec):
    """
    Formatting routine for writing compact representations of
    numbers in plot boxes
    """
    if prec == 'compact' or prec == 'compacthp':
        if f < 0:
            return "-" + _eformat(-f,prec)

        if prec == 'compacthp':
            if f < 0.005: #can't fit in 2 digits; would just be .00, so just print "0"
                return "0"
            if f < 1:
                z = "%.2f" % f # print first two decimal places
                if z.startswith("0."): return z[1:]  # fails for '1.00'; then thunk down to next f<10 case
            if f < 10:
                return "%.1f" % f # print whole number and tenths

        if f < 100:
            return "%.0f" % f # print nearest whole number if only 1 or 2 digits

        #if f >= 100, minimal scientific notation, such as "4e7", not "4e+07"
        s = "%.0e" % f
        try:
            mantissa, exp = s.split('e')
            exp = int(exp)
            if exp >= 100: return "B" #if number is too big to print
            if exp >= 10: return "*%d" % exp
            return "%se%d" % (mantissa, exp)
        except:
            return str(s)[0:3]

    elif type(prec) == int:
        if prec >= 0:
            return "%.*f" % (prec,f)
        else:
            return "%.*g" % (-prec,f)
    else:
        return "%g" % f #fallback to general format


def color_boxplot(plt_data, cmapFactory, title=None, xlabels=None, ylabels=None, xtics=None, ytics=None,
                 colorbar=True, fig=None, axes=None, size=None, prec=0, boxLabels=True,
                 xlabel=None, ylabel=None, save_to=None, ticSize=14, grid=False):
    """
    Create a color box plot.

    Creates a figure composed of colored boxes and possibly labels.

    Parameters
    ----------
    plt_data : numpy array
        A 2D array containing the values to be plotted.

    cmapFactory: ColormapFactory class
        An instance of a ColormapFactory class

    title : string, optional
        Plot title (latex can be used)

    xlabels, ylabels : list of strings, optional
        Tic labels for x and y axes.  If both are None, then tics are not drawn.

    xtics, ytics : list or array of floats, optional
        Values of x and y axis tics.  If None, then half-integers from 0.5 to
        0.5 + (nCols-1) or 0.5 + (nRows-1) are used, respectively.

    colorbar : bool, optional
        Whether to display a colorbar or not.

    fig, axes : matplotlib figure and axes, optional
        If non-None, use these figure and axes objects instead of creating new ones
        via fig,axes = pyplot.supblots()

    size : 2-tuple, optional
        The width and heigh of the final figure in inches.

    prec : int or {'compact','compacthp'}, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    xlabel, ylabel : str, optional
        X and Y axis labels

    save_to : str, optional
        save figure as this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    grid : bool, optional
        Whether or not grid lines should be displayed.

    Returns
    -------
    ReportFigure
        The encapsulated matplotlib figure that was generated
    """
    if axes is None: fig,axes = _plt.subplots()  # create a new figure if no axes are given

    cmap, norm = cmapFactory.get_cmap(), cmapFactory.get_norm()

    masked_data = _np.ma.array(plt_data, mask=_np.isnan(plt_data))

    heatmap = axes.pcolormesh( masked_data, cmap=cmap, norm=norm)

    if size is not None and fig is not None:
        fig.set_size_inches(size[0],size[1]) # was 12,8 for "super" color plot

    axes.set_xlim(0,plt_data.shape[1])
    axes.set_ylim(0,plt_data.shape[0])

    if xlabels is not None:
        if xtics is None:
            xtics = _np.arange(plt_data.shape[1])+0.5
        axes.set_xticks(xtics, minor=False)
        axes.set_xticklabels( xlabels,rotation=0, fontsize=ticSize )
    if ylabels is not None:
        if ytics is None:
            ytics = _np.arange(plt_data.shape[0])+0.5
        axes.set_yticks(ytics, minor=False)
        axes.set_yticklabels( ylabels, fontsize=ticSize )

    if grid:
        def get_minor_tics(t):
            return [ (t[i]+t[i+1])/2.0 for i in range(len(t)-1) ]
        axes.set_xticks(get_minor_tics(xtics), minor=True)
        axes.set_yticks(get_minor_tics(ytics), minor=True)
        axes.grid(which='minor', axis='both', linestyle='-', linewidth=2)


    if xlabels is None and ylabels is None:
        axes.tick_params(labelcolor='w', top='off', bottom='off', left='off', right='off') #white tics
    else:
        axes.tick_params(top='off', bottom='off', left='off', right='off')

    if title is not None:
        axes.set_title( title, fontsize=(ticSize+4) )

    if xlabel is not None:
        axes.set_xlabel( xlabel, fontsize=(ticSize+4) )

    if ylabel is not None:
        axes.set_ylabel( ylabel, fontsize=(ticSize+4) )

    if boxLabels:
        # Write values on colored squares
        for y in range(plt_data.shape[0]):
            for x in range(plt_data.shape[1]):
                if _np.isnan(plt_data[y, x]): continue
                axes.text(x + 0.5, y + 0.5, _eformat(plt_data[y, x], prec),
                        horizontalalignment='center',
                        verticalalignment='center', color=besttxtcolor( plt_data[y,x], cmap, norm) )

    if colorbar:
        _plt.colorbar(heatmap)

    rptFig = _ReportFigure(axes)

    if save_to is not None:
        if len(save_to) > 0: #So you can pass save_to="" and figure will be closed but not saved to a file
            _plt.savefig(save_to, bbox_extra_artists=(axes,), bbox_inches='tight') #need extra artists otherwise axis labels get clipped
        if fig is not None: _plt.close(fig) #close the figure if we're saving it to a file

    return rptFig



def nested_color_boxplot(plt_data_list_of_lists, cmapFactory, title=None, xlabels=None, ylabels=None, xtics=None, ytics=None,
                       colorbar=True, fig=None, axes=None, size=None, prec=0,
                       boxLabels=True, xlabel=None, ylabel=None, save_to=None, ticSize=14, grid=False):
    """
    Create a color box plot.

    Creates a figure composed of colored boxes and possibly labels.

    Parameters
    ----------
    plt_data_list_of_lists : list of lists of numpy arrays
        A complete square 2D list of lists, such that each element is a
        2D numpy array of the same size.

    cmapFactory: instance of the ColormapFactory class

    title : string, optional
        Plot title (latex can be used)

    xlabels, ylabels : list of strings, optional
        Tic labels for x and y axes.  If both are None, then tics are not drawn.

    xtics, ytics : list or array of floats, optional
        Values of x and y axis tics.  If None, then half-integers from 0.5 to
        0.5 + (nCols-1) or 0.5 + (nRows-1) are used, respectively.

    colorbar : bool, optional
        Whether to display a colorbar or not.

    fig, axes : matplotlib figure and axes, optional
        If non-None, use these figure and axes objects instead of creating new ones
        via fig,axes = pyplot.supblots()

    size : 2-tuple, optional
        The width and heigh of the final figure in inches.

    prec : int, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    xlabel, ylabel : str, optional
        X and Y axis labels

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    grid : bool, optional
        Whether or not grid lines should be displayed.

    Returns
    -------
    ReportFigure
        The encapsulated matplotlib figure that was generated
    """

    #Assume a complete 2D rectangular list of lists, and that each element is a numpy array of the same size
    if len(plt_data_list_of_lists) == 0 or len(plt_data_list_of_lists[0]) == 0: return
    elRows,elCols = plt_data_list_of_lists[0][0].shape #nE,nr
    nRows = len(plt_data_list_of_lists)
    nCols = len(plt_data_list_of_lists[0])

    data = _np.zeros( ( elRows*nRows + (nRows-1), elCols*nCols + (nCols-1)) )
    for i in range(1,nRows):
        data[(elRows+1)*i-1:(elRows+1)*i,:] = _np.nan
    for j in range(1,nCols):
        data[:, (elCols+1)*j-1:(elCols+1)*j] = _np.nan

    for i in range(nRows):
        for j in range(nCols):
            data[(elRows+1)*i:(elRows+1)*(i+1)-1, (elCols+1)*j:(elCols+1)*(j+1)-1] = plt_data_list_of_lists[i][j]

    xtics = []; ytics = []
    for i in range(nRows):   ytics.append( float((elRows+1)*(i+0.5)) )
    for j in range(nCols):   xtics.append( float((elCols+1)*(j+0.5)) )

    return color_boxplot(data, cmapFactory, title, xlabels, ylabels, _np.array(xtics), _np.array(ytics),
                        colorbar, fig, axes, size, prec, boxLabels, xlabel, ylabel,
                        save_to, ticSize, grid)

def _num_non_nan(array):
    ixs = _np.where(_np.isnan(_np.array(array).flatten()) == False)[0]

    return int(len(ixs))

def _all_same(items):
    return all(x == items[0] for x in items)

def _compute_num_boxes_dof(subMxs, used_xvals, used_yvals, sumUp):
    """
    A helper function to compute the number of boxes, and corresponding
    number of degrees of freedom, for the GST chi2/logl boxplots.

    """
    if sumUp:
        s = _np.shape(subMxs)
        # Reshape the subMxs into a "flattened" form (as opposed to a
        # two-dimensional one)
        reshape_subMxs = _np.array(_np.reshape(subMxs, (s[0] * s[1], s[2], s[3])))

        #Get all the boxes where the entries are not all NaN
        non_all_NaN = reshape_subMxs[_np.where(_np.array([_np.isnan(k).all() for k in reshape_subMxs]) == False)]
        s = _np.shape(non_all_NaN)
        dof_each_box = [_num_non_nan(k) for k in non_all_NaN]

        # Don't assert this anymore -- just use average below
        if not _all_same(dof_each_box):
            _warnings.warn('Number of degrees of freedom different for different boxes!')

        # The number of boxes is equal to the number of rows in non_all_NaN
        n_boxes = s[0]

        if n_boxes > 0:
            # Each box is a chi2_(sum) random variable
            # dof_per_box = dof_each_box[0] #OLD
            dof_per_box = _np.average(dof_each_box)
        else:
            dof_per_box = None #unknown, since there are no boxes
    else:
        # Each box is a chi2_1 random variable
        dof_per_box = 1

        # Gets all the non-NaN boxes, flattens the resulting
        # array, and does the sum.
        n_boxes = _np.sum(~_np.isnan(subMxs).flatten())

    return n_boxes, dof_per_box

def _computeGateStringMaps(xvals, yvals, xyGateStringDict, dataset,
                           strs, fidpair_filters, gatestring_filters):
    """ 
    Return a dictionary of all the gatestring maps,
    indexed by base string. 
    """
    used_xvals = [x for x in xvals 
                  if any([(xyGateStringDict[(x,y)] is not None) for y in yvals])]
    used_yvals = [y for y in yvals 
                  if any([(xyGateStringDict[(x,y)] is not None) for x in xvals])]
    return { xyGateStringDict[(x,y)] :
                 get_gatestring_map(xyGateStringDict[(x,y)], dataset, strs,
                                    fidpair_filters[(x,y)] 
                                    if fidpair_filters is not None else None,
                                    gatestring_filters[(x,y)] 
                                    if gatestring_filters is not None else None)
             for x in used_xvals for y in used_yvals }
    
def _computeProbabilities(maps, gateset, dataset):
    """ 
    Returns a dictionary of probabilities for each gate sequence in any of
    the maps contained in `maps` (a dict of gatestring maps as returned by
    _computeGateStringMaps).
    """
    #concatenate all gatestrings in maps
    gatestringList = [] 
    for m in maps.values():
        gatestringList.extend( [tup[2] for tup in m[0]] )

    #compute probabilities
    spamLabels = dataset.get_spam_labels()
    evt = gateset.bulk_evaltree(gatestringList)
    bulk_probs = gateset.bulk_probs(evt) # LATER use comm?
    probs_dict = \
        { gatestringList[i]: {sl: bulk_probs[sl][i] for sl in spamLabels}
          for i in range(len(gatestringList)) }
    return probs_dict

    

def _computeSubMxs(xvals, yvals, xyGateStringDict, subMxCreationFn, sumUp):

    used_xvals = [ x for x in xvals if any([ (xyGateStringDict[(x,y)] is not None) for y in yvals]) ]
    used_yvals = [ y for y in yvals if any([ (xyGateStringDict[(x,y)] is not None) for x in xvals]) ]
    subMxs = [ [ subMxCreationFn(xyGateStringDict[(x,y)],x,y) for x in used_xvals ] 
               for y in used_yvals]
    #Note: subMxs[y-index][x-index] is proper usage
    n_boxes, dof_per_box = _compute_num_boxes_dof(subMxs, used_xvals, used_yvals, sumUp)

    return used_xvals, used_yvals, subMxs, n_boxes, dof_per_box


def generate_boxplot( xvals, yvals, xyGateStringDict, subMxs, cmapFactory, xlabel="", ylabel="", scale=1.0, prec=0,
                     title='sub-mx', sumUp=False, boxLabels=True, histogram=False, histBins=50, save_to=None,
                     ticSize=20, invert=False, inner_x_labels=None, inner_y_labels=None, inner_x_label=None, inner_y_label=None,
                     grid=False):
    """
    Creates a view of nested box plot data (i.e. a matrix for each (x,y) pair).

    Given lists of x and y values, a dictionary to convert (x,y) pairs into gate strings,
    and a function to convert a "base" gate string into a matrix of floating point values,
    this function computes (x,y) => matrix data and displays it in one of two ways:

    1. As a full nested color box plot, showing all the matrix values individually
    2. As a color box plot containing the sum of the elements in the (x,y) matrix as
       the (x,y) box.

    A histogram of the values can also be computed and displayed.

    Parameters
    ----------
    xvals, yvals : list
        List of x and y values. Elements can be any hashable quantity, and will be converted
        into x and y tic labels.  Tuples of strings are converted specially for nice latex
        rendering of gate strings.

    xyGateStringDict : dict
        Dictionary with keys == (x_value,y_value) tuples and values == gate strings, where
        a gate string can either be a GateString object or a tuple of gate labels.  Provides
        the mapping between x,y pairs and gate strings.  None values are allowed, and
        indicate that there is not data for that x,y pair and nothing should be plotted.

    subMxs : list
        A list of lists of 2D numpy.ndarrays.  subMxs[iy][ix] specifies the matrix of values
        or sum (if sumUp == True) displayed in iy-th row and ix-th column of the plot.  NaNs
        indicate elements should not be displayed.

    cmapFactory: instance of the ColormapFactory class

    xlabel, ylabel : str, optional
        X and Y axis labels

    scale : float, optional
        Scaling factor to adjust the size of the final figure.

    prec : int, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    title : string, optional
        Plot title (latex can be used)

    sumUp : bool, optional
        False displays each matrix element as it's own color box
        True sums the elements of each (x,y) matrix and displays
        a single color box for the sum.

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    histogram : bool, optional
        Whether a histogram of the matrix element values or summed matrix
        values (depending on sumUp) should also be computed and displayed.

    histBins : int, optional
        The number of bins to use in the histogram.

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    invert : bool, optional
        If True, invert the nesting order of the nested color box plot (applicable
        only when sumUp == False).  Use inner_x_labels and inner_y_labels to label
        the x and y axes.

    inner_x_labels, inner_y_labels : list, optional
        Similar to xvals, yvals but labels for the columns and rows of the (x,y) matrices
        computed by subMxCreationFn.  Used when invert == True.

    grid : bool, optional
        Whether or not grid lines should be displayed.

    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.  Note that
        figure extra info is a dict with keys:

        nUsedXs : int
            The number of used X-values, proportional to the overall final figure width
        nUsedYs : int
            The number of used Y-values, proportional to the overall final figure height
    """

    nXs,nYs = len(xvals),len(yvals)

    def val_filter(vals):  #filter to latex-ify gate strings.  Later add filter as a possible parameter
        formatted_vals = []
        for val in vals:
            if type(val) in (tuple,_objs.GateString) and all([type(el) == str for el in val]):
                if len(val) == 0:
                    formatted_vals.append(r"$\{\}$")
                else:
                    formatted_vals.append( "$" + "\\cdot".join([("\\mathrm{%s}" % el) for el in val]) + "$" )
            else:
                formatted_vals.append(val)
        return formatted_vals

    def sum_up_mx(mx):
        flat_mx = mx.flatten()
        if any([_np.isnan(x) for x in flat_mx]):
            if all([_np.isnan(x) for x in flat_mx]):
                return _np.nan
            return sum(_np.nan_to_num(flat_mx)) #replace NaNs with zeros for purpose of summing (when there's at least one non-NaN)
        else:
            return sum(flat_mx)

    #Setup and create plotting functions
    if sumUp:
        subMxSums = _np.array( [ [ sum_up_mx(subMxs[iy][ix]) for ix in range(nXs) ] for iy in range(nYs) ], 'd' )
        if invert: _warnings.warn("Cannot invert a summed-up plot.  Ignoring invert=True.")

        fig,ax = _plt.subplots( 1, 1, figsize=(nXs*scale, nYs*scale))
        rptFig = color_boxplot( subMxSums, cmapFactory, fig=fig, axes=ax, title=title,
                               xlabels=val_filter(xvals), ylabels=val_filter(yvals),
                               colorbar=False, prec=prec, xlabel=xlabel, ylabel=ylabel,
                               ticSize=ticSize, grid=grid)
        rptFig.save_to(save_to)

        if histogram:
            fig = _plt.figure()
            histdata = subMxSums.flatten()
            histdata_finite = _np.take(histdata, _np.where(_np.isfinite(histdata)))[0] #take gives back (1,N) shaped array (why?)
            histMin = min( histdata_finite ) if cmapFactory.vmin is None else cmapFactory.vmin
            histMax = max( histdata_finite ) if cmapFactory.vmax is None else cmapFactory.vmax
            _plt.hist(_np.clip(histdata_finite,histMin,histMax), histBins,
                      range=[histMin, histMax], facecolor='gray', align='mid')
            if save_to is not None:
                if len(save_to) > 0:
                    _plt.savefig( _makeHistFilename(save_to) )
                _plt.close(fig)


    else: #not summing up

        nIYs = nIXs = 0
        for ix in range(nXs):
            for iy in range(nYs):
                if subMxs[iy][ix] is not None:
                    nIYs,nIXs = subMxs[iy][ix].shape
                    break

        # flip so [0,0] el of original subMxs is at *top*-left (FLIP)
        subMxs = [ [ _np.flipud(subMx) for subMx in row ] for row in subMxs]

        if invert:
            invertedSubMxs = []  #will be indexed as invertedSubMxs[inner-y][inner-x]
            for iny in range(nIYs):
                invertedSubMxs.append( [] )
                for inx in range(nIXs):
                    mx = _np.array( [[ subMxs[iy][ix][iny,inx] for ix in range(nXs) ] for iy in range(nYs)],  'd' )
                    invertedSubMxs[-1].append( mx )

            #Replace usual params with ones corresponding to "inverted" plot
            subMxs = invertedSubMxs
            xvals = inner_x_labels if inner_x_labels else [""]*nIXs
            yvals = inner_y_labels if inner_y_labels else [""]*nIYs
            yvals = list(reversed(yvals)) # to match flipud call above (FLIP)
            xlabel = inner_x_label if inner_x_label else ""
            ylabel = inner_y_label if inner_y_label else ""
            nXs, nYs, nIXs, nIYs = nIXs, nIYs, nXs, nYs #swap nXs <=> nIXs b/c of inversion

        fig,ax = _plt.subplots( 1, 1, figsize=(nXs*nIXs*scale*0.4, nYs*nIYs*scale*0.4))
        rptFig = nested_color_boxplot(subMxs, cmapFactory, fig=fig, axes=ax, title=title, prec=prec,
                                      ylabels=val_filter(yvals), xlabels=val_filter(xvals), boxLabels=boxLabels,
                                      colorbar=False, ylabel=ylabel, xlabel=xlabel, ticSize=ticSize, grid=grid)
        rptFig.save_to(save_to)

        if histogram:
            fig = _plt.figure()
            histdata = _np.concatenate( [ subMxs[iy][ix].flatten() for ix in range(nXs) for iy in range(nYs)] )
            histdata_finite = _np.take(histdata, _np.where(_np.isfinite(histdata)))[0] #take gives back (1,N) shaped array (why?)
            histMin = min( histdata_finite ) if cmapFactory.vmin is None else cmapFactory.vmin
            histMax = max( histdata_finite ) if cmapFactory.vmax is None else cmapFactory.vmax
            _plt.hist(_np.clip(histdata_finite,histMin,histMax), histBins,
                      range=[histMin, histMax], facecolor='gray', align='mid')
            if save_to is not None:
                if len(save_to) > 0:
                    _plt.savefig( _makeHistFilename(save_to) )
                _plt.close(fig)

    if rptFig:
        rptFig.set_extra_info( { 'nUsedXs': len(xvals),
                                 'nUsedYs': len(yvals) } )
    # rptFig.check() #DEBUG - test that figure can unpickle correctly --
    #                # if not, probably used magic matplotlib (don't do that)
    return rptFig


def gof_boxplot_keyplot(strs, xlabel="$\\rho_i$", ylabel="$E_i$",
                        title="", size=None, save_to=None, ticSize=15):
    """
    Create a plot showing the layout of a single sub-block of a goodness-of-fit
    box plot (such as those produced by chi2_boxplot or logl_boxplot).

    Parameters
    ----------
    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    xlabel, ylabel : str, optional
        X and Y axis labels

    title : string, optional
        Plot title (latex can be used)

    size : tuple, optional
      The (width,height) figure size in inches.  None
      enables automatic calculation based on gateMatrix
      size.

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.
    """
    prepStrs, effectStrs = strs

    fig, axes = _plt.subplots()
    if size is not None:
        fig.set_size_inches(size[0],size[1])
    else:
        fig.set_size_inches(len(prepStrs)*0.5,
                            len(effectStrs)*0.5)

    if title is not None:
        axes.set_title( title, fontsize=(ticSize+4) )

    if xlabel is not None:
        axes.set_xlabel( xlabel, fontsize=(ticSize+4) )

    if ylabel is not None:
        axes.set_ylabel( ylabel, fontsize=(ticSize+4) )

    #Copied from generate_boxplot
    def val_filter(vals):  #filter to latex-ify gate strings.  Later add filter as a possible parameter
        formatted_vals = []
        for val in vals:
            if type(val) in (tuple,_objs.GateString) and all([type(el) == str for el in val]):
                if len(val) == 0:
                    formatted_vals.append(r"$\{\}$")
                else:
                    formatted_vals.append( "$" + "\\cdot".join([("\\mathrm{%s}" % el) for el in val]) + "$" )
            else:
                formatted_vals.append(val)
        return formatted_vals

    axes.yaxis.tick_right()
    axes.xaxis.set_label_position("top")
    axes.set_xticklabels(val_filter(prepStrs), rotation=90, ha='center', fontsize=ticSize)
    axes.set_yticklabels(list(reversed(val_filter(effectStrs))), fontsize=ticSize) # FLIP
    axes.set_xticks(_np.arange(len(prepStrs))+.5)
    axes.set_xticks(_np.arange(len(prepStrs)+1), minor = True)
    axes.set_yticks(_np.arange(len(effectStrs))+.5)
    axes.set_yticks(_np.arange(len(effectStrs)+1), minor = True)
    axes.tick_params(which='major', bottom='off', top='off', left='off', right='off', pad=5 )
    axes.yaxis.grid(True,linestyle='-',linewidth=1.0, which='minor')
    axes.xaxis.grid(True,linestyle='-',linewidth=1.0, which='minor')

    rptFig = _ReportFigure(axes)

    if save_to is not None:
        if len(save_to) > 0: #So you can pass save_to="" and figure will be closed but not saved to a file
            _plt.savefig(save_to, bbox_extra_artists=(axes,), bbox_inches='tight')
        if fig is not None: _plt.close(fig) #close the figure if we're saving it to a file

    return rptFig



def chi2_boxplot( xvals, yvals, xy_gatestring_dict, dataset, gateset, strs,
                  xlabel="", ylabel="",  scale=1.0, prec='compact',
                  title='$\\chi^2$', linlg_pcntle=.05, sumUp=False,
                  boxLabels=True, histogram=False, histBins=50,
                  minProbClipForWeighting=1e-4, save_to=None, ticSize=20,
                  invert=False, fidpair_filters=None, gatestring_filters=None):
    """
    Create a color box plot of chi^2 values.

    Parameters
    ----------
    xvals, yvals : list
        List of x and y values. Elements can be any hashable quantity, and will be converted
        into x and y tic labels.  Tuples of strings are converted specially for nice latex
        rendering of gate strings.

    xy_gatestring_dict : dict
        Dictionary with keys == (x_value,y_value) tuples and values == gate strings, where
        a gate string can either be a GateString object or a tuple of gate labels.  Provides
        the mapping between x,y pairs and gate strings.  None values are allowed, and
        indicate that there is not data for that x,y pair and nothing should be plotted.

    dataset : DataSet
        The data used to specify frequencies and counts

    gateset : GateSet
        The gate set used to specify the probabilities and SPAM labels

    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    xlabel, ylabel : str, optional
        X and Y axis labels

    scale : float, optional
        Scaling factor to adjust the size of the final figure.

    prec : int, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    title : string, optional
        Plot title (latex can be used)

    linlg_pcntle: float, optional
        Specifies the (1 - linlg_pcntle) percentile to compute for the boxplots

    sumUp : bool, optional
        False displays each matrix element as it's own color box
        True sums the elements of each (x,y) matrix and displays
        a single color box for the sum.

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    histogram : bool, optional
        Whether a histogram of the matrix element values or summed matrix
        values (depending on sumUp) should also be computed and displayed.

    histBins : int, optional
        The number of bins to use in the histogram.

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight used
        within the chi^2 function (see chi2fn).

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    invert : bool, optional
        If True, invert the nesting order of the nested color box plot (applicable
        only when sumUp == False).  Use inner_x_labels and inner_y_labels to label
        the x and y axes.

    fidpair_filters : dict, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose values
        are lists of (iRhoStr,iEStr) tuples specifying a subset of all the
        prepStr,effectStr pairs to include in the plot for each particular
        (x,y) sub-block.

    gatestring_filters : dict, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose
        values are lists of GateString objects specifying which elements should
        be computed and displayed in the (x,y) sub-block of the plot.


    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.  Extra figure
        info is a dict with keys:

        nUsedXs : int
            The number of used X-values, proportional to the overall final figure width
        nUsedYs : int
            The number of used Y-values, proportional to the overall final figure height
    """

    prepStrs, effectStrs = strs

    #bulk-compute probabilities for performance
    maps = _computeGateStringMaps(xvals, yvals, xy_gatestring_dict, dataset,
                                  strs, fidpair_filters, gatestring_filters)
    probs_precomp_dict = _computeProbabilities(maps, gateset, dataset)

    def mx_fn(gateStr,x,y):
        return chi2_matrix( maps[gateStr], dataset, gateset, minProbClipForWeighting,
                            probs_precomp_dict)

    xvals,yvals,subMxs,n_boxes,dof = _computeSubMxs(xvals,yvals,xy_gatestring_dict,mx_fn,sumUp)
    stdcmap = StdColormapFactory('linlog', n_boxes=n_boxes, linlg_pcntle=linlg_pcntle, dof=dof)

    return generate_boxplot( xvals, yvals, xy_gatestring_dict, subMxs, stdcmap, xlabel,ylabel,
                            scale,prec,title,sumUp,boxLabels,histogram,histBins,save_to,ticSize,
                            invert, prepStrs, effectStrs, r"$\rho_i$", r"$E_i$")

def logl_boxplot( xvals, yvals, xy_gatestring_dict, dataset, gateset, strs,
                  xlabel="", ylabel="", scale=1.0, prec='compact',
                  title='$\\log(\\mathcal{L})$', linlg_pcntle=.05, sumUp=False,
                  boxLabels=True, histogram=False, histBins=50,
                  minProbClipForWeighting=1e-4, save_to=None, ticSize=20,
                  invert=False, fidpair_filters=None, gatestring_filters=None):
    """
    Create a color box plot of log-likelihood values.

    Parameters
    ----------
    xvals, yvals : list
        List of x and y values. Elements can be any hashable quantity, and will be converted
        into x and y tic labels.  Tuples of strings are converted specially for nice latex
        rendering of gate strings.

    xy_gatestring_dict : dict
        Dictionary with keys == (x_value,y_value) tuples and values == gate strings, where
        a gate string can either be a GateString object or a tuple of gate labels.  Provides
        the mapping between x,y pairs and gate strings.  None values are allowed, and
        indicate that there is not data for that x,y pair and nothing should be plotted.

    dataset : DataSet
        The data used to specify frequencies and counts

    gateset : GateSet
        The gate set used to specify the probabilities and SPAM labels

    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    xlabel, ylabel : str, optional
        X and Y axis labels

    scale : float, optional
        Scaling factor to adjust the size of the final figure.

    prec : int, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    title : string, optional
        Plot title (latex can be used)

    linlg_pcntle: float, optional
        Specifies the (1 - linlg_pcntle) percentile to compute for the boxplots

    sumUp : bool, optional
        False displays each matrix element as it's own color box
        True sums the elements of each (x,y) matrix and displays
        a single color box for the sum.

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    histogram : bool, optional
        Whether a histogram of the matrix element values or summed matrix
        values (depending on sumUp) should also be computed and displayed.

    histBins : int, optional
        The number of bins to use in the histogram.

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight used
        within the logl function.

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    invert : bool, optional
        If True, invert the nesting order of the nested color box plot (applicable
        only when sumUp == False).  Use inner_x_labels and inner_y_labels to label
        the x and y axes.

    fidpair_filters : list, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose values
        are lists of (iRhoStr,iEStr) tuples specifying a subset of all the
        prepStr,effectStr pairs to include in the plot for each particular
        (x,y) sub-block.

    gatestring_filters : dict, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose
        values are lists of GateString objects specifying which elements should
        be computed and displayed in the (x,y) sub-block of the plot.


    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.  Extra figure
        info is a dict with keys:

        nUsedXs : int
            The number of used X-values, proportional to the overall final figure width
        nUsedYs : int
            The number of used Y-values, proportional to the overall final figure height
    """
    prepStrs, effectStrs = strs
    
    #bulk-compute probabilities for performance
    maps = _computeGateStringMaps(xvals, yvals, xy_gatestring_dict, dataset,
                                  strs, fidpair_filters, gatestring_filters)
    probs_precomp_dict = _computeProbabilities(maps, gateset, dataset)

    def mx_fn(gateStr,x,y):
        return logl_matrix( maps[gateStr], dataset, gateset, minProbClipForWeighting,
                            probs_precomp_dict)

    xvals,yvals,subMxs,n_boxes,dof = _computeSubMxs(xvals,yvals,xy_gatestring_dict,mx_fn,sumUp)
    stdcmap = StdColormapFactory('linlog', n_boxes=n_boxes, linlg_pcntle=linlg_pcntle, dof=dof)

    return generate_boxplot( xvals, yvals, xy_gatestring_dict, subMxs, stdcmap, xlabel,ylabel,
                        scale,prec,title,sumUp,boxLabels,histogram,histBins,save_to,ticSize,
                        invert, prepStrs, effectStrs, r"$\rho_i$", r"$E_i$")



def blank_boxplot( xvals, yvals, xy_gatestring_dict, strs, xlabel="", ylabel="",
                  scale=1.0, title='', sumUp=False, save_to=None, ticSize=20, invert=False):
    """
    Create only the outline of a color box plot.

    This function has been useful for creating presentations
    containing box plots to introduce the viewer to these
    types of plots.

    Parameters
    ----------
    xvals, yvals : list
        List of x and y values. Elements can be any hashable quantity, and will be converted
        into x and y tic labels.  Tuples of strings are converted specially for nice latex
        rendering of gate strings.

    xy_gatestring_dict : dict
        Dictionary with keys == (x_value,y_value) tuples and values == gate strings, where
        a gate string can either be a GateString object or a tuple of gate labels.  Provides
        the mapping between x,y pairs and gate strings.  None values are allowed, and
        indicate that there is not data for that x,y pair and nothing should be plotted.

    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    xlabel, ylabel : str, optional
        X and Y axis labels

    scale : float, optional
        Scaling factor to adjust the size of the final figure.

    title : string, optional
        Plot title (latex can be used)

    sumUp : bool, optional
        False displays each matrix element as it's own color box
        True sums the elements of each (x,y) matrix and displays
        a single color box for the sum.

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    invert : bool, optional
        If True, invert the nesting order of the nested color box plot (applicable
        only when sumUp == False).

    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.  Extra figure
        info is a dict with keys:

        nUsedXs : int
            The number of used X-values, proportional to the overall final figure width
        nUsedYs : int
            The number of used Y-values, proportional to the overall final figure height
    """
    prepStrs, effectStrs = strs
    def mx_fn(gateStr,x,y):
        return _np.nan * _np.zeros( (len(strs[1]),len(strs[0])), 'd')
    xvals,yvals,subMxs,n_boxes,dof = _computeSubMxs(xvals,yvals,xy_gatestring_dict,mx_fn,sumUp)
    stdcmap = StdColormapFactory('seq', n_boxes=n_boxes, vmin=0, vmax=1, dof=dof)

    return generate_boxplot( xvals, yvals, xy_gatestring_dict, subMxs,stdcmap,xlabel,ylabel,
                            scale,'compact',title,sumUp,False,False,0,save_to,ticSize,
                            invert, prepStrs, effectStrs, r"$\rho_i$", r"$E_i$",True)



def small_eigval_err_rate_boxplot( xvals, yvals, xy_gatestring_dict, dataset, directGSTgatesets,
                               xlabel="", ylabel="", m=None, M=None, scale=1.0, prec=-1,
                               title='Error rate, extrap. from small eigenvalue of Direct GST estimate',
                               boxLabels=True, histogram=False, histBins=50,
                               save_to=None, ticSize=14):
    """
    Create a color box plot of per-gate error rates.

    Parameters
    ----------
    xvals, yvals : list
        List of x and y values. Elements can be any hashable quantity, and will be converted
        into x and y tic labels.  Tuples of strings are converted specially for nice latex
        rendering of gate strings.

    xy_gatestring_dict : dict
        Dictionary with keys == (x_value,y_value) tuples and values == gate strings, where
        a gate string can either be a GateString object or a tuple of gate labels.  Provides
        the mapping between x,y pairs and gate strings.  None values are allowed, and
        indicate that there is not data for that x,y pair and nothing should be plotted.

    dataset : DataSet
        The data used to specify frequencies and counts

    directGSTgatesets : dict
        Dictionary with keys == gate strings and values == GateSets linking a gate
        string to correponding direct-GST gate set.

    xlabel, ylabel : str, optional
        X and Y axis labels

    m, M : float, optional
        Min and max values of the color scale.

    scale : float, optional
        Scaling factor to adjust the size of the final figure.

    prec : int, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    title : string, optional
        Plot title (latex can be used)

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    histogram : bool, optional
        Whether a histogram of the matrix element values or summed matrix
        values (depending on sumUp) should also be computed and displayed.

    histBins : int, optional
        The number of bins to use in the histogram.

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.  Extra figure
        info is a dict with keys:

        nUsedXs : int
            The number of used X-values, proportional to the overall final figure width
        nUsedYs : int
            The number of used Y-values, proportional to the overall final figure height
    """

    def mx_fn(gateStr,x,y): #error rate as 1x1 matrix which we have plotting function sum up
        return _np.array( [[ small_eigval_err_rate(gateStr, dataset,  directGSTgatesets) ]] )
    xvals, yvals, subMxs, _, _ = _computeSubMxs(xvals,yvals,xy_gatestring_dict,mx_fn,True)
    max_abs = max([ _np.max(_np.abs(_np.nan_to_num(subMxs[iy][ix])))
                    for ix in range(len(xvals))
                    for iy in range(len(yvals)) ])
    if max_abs == 0: max_abs = 1e-6 # pick a nonzero value if all entries are zero or nan
    m = 0 if m is None else m
    M = max_abs if M is None else M
    stdcmap = StdColormapFactory('seq', vmin=m, vmax=M)

    return generate_boxplot( xvals, yvals, xy_gatestring_dict, subMxs, stdcmap, xlabel,ylabel,
                            scale,prec,title, True,boxLabels,histogram,histBins,save_to,ticSize)



def gateset_with_lgst_gatestring_estimates( gateStringsToEstimate, dataset, specs,
                                        targetGateset=None, includeTargetGates=True,
                                        spamDict=None, guessGatesetForGauge=None,
                                        gateStringLabels=None, svdTruncateTo=0, verbosity=0 ):
    """
    Constructs a gateset that contains LGST estimates for gateStringsToEstimate.

    For each gate string s in gateStringsToEstimate, the constructed gateset
    contains the LGST estimate for s as separate gate, labeled either by
    the corresponding element of gateStringLabels or by the tuple of s itself.

    Parameters
    ----------
    gateStringsToEstimate : list of GateStrings or tuples
        The gate strings to estimate using LGST

    dataset : DataSet
        The data to use for LGST

    specs : 2-tuple
        A (prepSpecs,effectSpecs) tuple usually generated by calling build_spam_specs(...)

    targetGateset : GateSet, optional
        The target gate set used by LGST to extract gate labels and an initial gauge

    includeTargetGates : bool, optional
        If True, the gate labels in targetGateset will be included in the
        returned gate set.

    spamDict : dict, optional
        Dictionary mapping (rhoVec_index,EVec_index) integer tuples to string spam labels.
        Defaults to the spam dictionary of targetGateset

    guessGatesetForGauge : GateSet, optional
        A gateset used to compute a gauge transformation that is applied to
        the LGST estimates.  This gauge transformation is computed such that
        if the estimated gates matched the gateset given, then the gate
        matrices would match, i.e. the gauge would be the same as
        the gateset supplied. Defaults to the targetGateset.

    gateStringLabels : list of strings, optional
        A list of labels in one-to-one correspondence with the
        gate string in gateStringsToEstimate.  These labels are
        the keys to access the gate matrices in the returned
        GateSet, i.e. gate_matrix = returned_gateset[gate_label]

    svdTruncateTo : int, optional
        The Hilbert space dimension to truncate the gate matrices to using
        a SVD to keep only the largest svdToTruncateTo singular values of
        the I_tildle LGST matrix. Defaults to no truncation.

    verbosity : int, optional
        Verbosity value to send to do_lgst(...) call.

    Returns
    -------
    Gateset
        A gateset containing LGST estimates for all the requested
        gate strings and possibly the gates in targetGateset.
    """
    gateLabels = [] #list of gate labels for LGST to estimate

    #Add gate strings to estimate as aliases
    aliases = { }
    if gateStringLabels is not None:
        assert(len(gateStringLabels) == len(gateStringsToEstimate))
        for gateLabel,gateStr in zip(gateStringLabels,gateStringsToEstimate):
            aliases[gateLabel] = tuple(gateStr)
            gateLabels.append(gateLabel)
    else:
        for gateStr in gateStringsToEstimate:
            newLabel = 'G'+'.'.join(tuple(gateStr))
            aliases[newLabel] = tuple(gateStr) #use gatestring tuple as label
            gateLabels.append(newLabel)

    #Add target gateset labels (not aliased) if requested
    if includeTargetGates and targetGateset is not None:
        for targetGateLabel in targetGateset.gates:
            if targetGateLabel not in gateLabels: #very unlikely that this is false
                gateLabels.append(targetGateLabel)

    return _alg.do_lgst( dataset, specs, targetGateset, gateLabels, aliases,
               spamDict, guessGatesetForGauge, svdTruncateTo, None, verbosity )

def direct_lgst_gateset( gateStringToEstimate, gateStringLabel, dataset,
                       specs, targetGateset, svdTruncateTo=0, verbosity=0 ):
    """
    Constructs a gateset of LGST estimates for target gates and gateStringToEstimate.

    Parameters
    ----------
    gateStringToEstimate : GateString or tuple
        The single gate string to estimate using LGST

    gateStringLabel : string
        The label for the estimate of gateStringToEstimate.
        i.e. gate_matrix = returned_gateset[gate_label]

    dataset : DataSet
        The data to use for LGST

    specs : 2-tuple
        A (prepSpecs,effectSpecs) tuple usually generated by calling build_spam_specs(...)

    targetGateset : GateSet
        The target gate set used by LGST to extract gate labels and an initial gauge

    svdTruncateTo : int, optional
        The Hilbert space dimension to truncate the gate matrices to using
        a SVD to keep only the largest svdToTruncateTo singular values of
        the I_tildle LGST matrix. Defaults to no truncation.

    verbosity : int, optional
        Verbosity value to send to do_lgst(...) call.

    Returns
    -------
    Gateset
        A gateset containing LGST estimates of gateStringToEstimate
        and the gates of targetGateset.
    """
    return gateset_with_lgst_gatestring_estimates( [gateStringToEstimate], dataset, specs, targetGateset,
                                               True, None, None, [gateStringLabel], svdTruncateTo, verbosity )

def direct_lgst_gatesets(gateStrings, dataset, specs, targetGateset, svdTruncateTo=0, verbosity=0):
    """
    Constructs a dictionary with keys == gate strings and values == Direct-LGST GateSets.

    Parameters
    ----------
    gateStrings : list of GateString or tuple objects
        The gate strings to estimate using LGST.  The elements of this list
        are the keys of the returned dictionary.

    dataset : DataSet
        The data to use for all LGST estimates.

    specs : 2-tuple
        A (prepSpecs,effectSpecs) tuple usually generated by calling build_spam_specs(...)

    targetGateset : GateSet
        The target gate set used by LGST to extract gate labels and an initial gauge

    svdTruncateTo : int, optional
        The Hilbert space dimension to truncate the gate matrices to using
        a SVD to keep only the largest svdToTruncateTo singular values of
        the I_tildle LGST matrix. Defaults to no truncation.

    verbosity : int, optional
        Verbosity value to send to do_lgst(...) call.

    Returns
    -------
    dict
        A dictionary that relates each gate string of gateStrings to a
        GateSet containing the LGST estimate of that gate string stored under
        the gate label "GsigmaLbl", along with LGST estimates of the gates in
        targetGateset.
    """
    printer = _objs.VerbosityPrinter.build_printer(verbosity)

    directLGSTgatesets = {}
    printer.log("--- Direct LGST precomputation ---")
    with printer.progress_logging(1):
        for i,sigma in enumerate(gateStrings):
            printer.show_progress(i, len(gateStrings), prefix="--- Computing gateset for string -", suffix='---' )
            directLGSTgatesets[sigma] = direct_lgst_gateset( sigma, "GsigmaLbl", dataset, specs, targetGateset,
                                                            svdTruncateTo, verbosity)
    return directLGSTgatesets



def direct_mc2gst_gateset( gateStringToEstimate, gateStringLabel, dataset, specs, targetGateset, svdTruncateTo=0,
                        minProbClipForWeighting=1e-4, probClipInterval=(-1e6,1e6), verbosity=0 ):
    """
    Constructs a gateset of LSGST estimates for target gates and gateStringToEstimate.

    Starting with a Direct-LGST estimate for gateStringToEstimate, runs LSGST
    using the same strings that LGST would have used to estimate gateStringToEstimate
    and each of the target gates.  That is, LSGST is run with strings of the form:

    1. prepStr
    2. effectStr
    3. prepStr + effectStr
    4. prepStr + singleGate + effectStr
    5. prepStr + gateStringToEstimate + effectStr

    and the resulting Gateset estimate is returned.

    Parameters
    ----------
    gateStringToEstimate : GateString or tuple
        The single gate string to estimate using LSGST

    gateStringLabel : string
        The label for the estimate of gateStringToEstimate.
        i.e. gate_matrix = returned_gateset[gate_label]

    dataset : DataSet
        The data to use for LGST

    specs : 2-tuple
        A (prepSpecs,effectSpecs) tuple usually generated by calling build_spam_specs(...)

    targetGateset : GateSet
        The target gate set used by LGST to extract gate labels and an initial gauge

    svdTruncateTo : int, optional
        The Hilbert space dimension to truncate the gate matrices to using
        a SVD to keep only the largest svdToTruncateTo singular values of
        the I_tildle LGST matrix. Defaults to no truncation.

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight used
        within the chi^2 function (see chi2fn).

    probClipInterval : 2-tuple, optional
        (min,max) to clip probabilities to within GateSet probability
        computation routines (see GateSet.bulk_fill_probs)

    verbosity : int, optional
        Verbosity value to send to do_lgst(...) and do_mc2gst(...) calls.

    Returns
    -------
    Gateset
        A gateset containing LSGST estimates of gateStringToEstimate
        and the gates of targetGateset.
    """
    direct_lgst = gateset_with_lgst_gatestring_estimates( [gateStringToEstimate], dataset, specs, targetGateset,
                                                      True, None, None, [gateStringLabel], svdTruncateTo, verbosity )

    prepStrs, effectStrs = _construction.get_spam_strs(specs)

    # LEXICOGRAPHICAL VS MATRIX ORDER
    gatestrings = prepStrs + effectStrs + [ prepStr + effectStr for prepStr in prepStrs for effectStr in effectStrs ]
    for gateLabel in direct_lgst.gates:
        gatestrings.extend( [ prepStr + _objs.GateString( (gateLabel,), bCheck=False) + effectStr
                              for prepStr in prepStrs for effectStr in effectStrs ] )

    _, direct_lsgst = _alg.do_mc2gst(
        dataset, direct_lgst, gatestrings,
        minProbClipForWeighting=minProbClipForWeighting,
        probClipInterval=probClipInterval, verbosity=verbosity,
        gateLabelAliases={gateStringLabel: gateStringToEstimate} )
                                         #opt_gates=[gateStringLabel])
    return direct_lsgst


def direct_mc2gst_gatesets(gateStrings, dataset, specs, targetGateset, svdTruncateTo=0,
                        minProbClipForWeighting=1e-4, probClipInterval=(-1e6,1e6), verbosity=0):
    """
    Constructs a dictionary with keys == gate strings and values == Direct-LSGST GateSets.

    Parameters
    ----------
    gateStrings : list of GateString or tuple objects
        The gate strings to estimate using LSGST.  The elements of this list
        are the keys of the returned dictionary.

    dataset : DataSet
        The data to use for all LGST and LSGST estimates.

    specs : 2-tuple
        A (prepSpecs,effectSpecs) tuple usually generated by calling build_spam_specs(...)

    targetGateset : GateSet
        The target gate set used by LGST to extract gate labels and an initial gauge

    svdTruncateTo : int, optional
        The Hilbert space dimension to truncate the gate matrices to using
        a SVD to keep only the largest svdToTruncateTo singular values of
        the I_tildle LGST matrix. Defaults to no truncation.

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight used
        within the chi^2 function (see chi2fn).

    probClipInterval : 2-tuple, optional
        (min,max) to clip probabilities to within GateSet probability
        computation routines (see GateSet.bulk_fill_probs)

    verbosity : int, optional
        Verbosity value to send to do_lgst(...) and do_mc2gst(...) calls.

    Returns
    -------
    dict
        A dictionary that relates each gate string of gateStrings to a
        GateSet containing the LSGST estimate of that gate string stored under
        the gate label "GsigmaLbl", along with LSGST estimates of the gates in
        targetGateset.
    """
    printer = _objs.VerbosityPrinter.build_printer(verbosity)
    directLSGSTgatesets = {}
    printer.log("--- Direct LSGST precomputation ---")
    with printer.progress_logging(1):
        for i,sigma in enumerate(gateStrings):
            printer.show_progress(i, len(gateStrings), prefix="--- Computing gateset for string-", suffix='---')
            directLSGSTgatesets[sigma] = direct_mc2gst_gateset( sigma, "GsigmaLbl", dataset, specs, targetGateset,
                                                            svdTruncateTo, minProbClipForWeighting,
                                                            probClipInterval, verbosity)
    return directLSGSTgatesets


def direct_mlgst_gateset( gateStringToEstimate, gateStringLabel, dataset, specs, targetGateset, svdTruncateTo=0,
                        minProbClip=1e-6, probClipInterval=(-1e6,1e6), verbosity=0 ):
    """
    Constructs a gateset of MLEGST estimates for target gates and gateStringToEstimate.

    Starting with a Direct-LGST estimate for gateStringToEstimate, runs MLEGST
    using the same strings that LGST would have used to estimate gateStringToEstimate
    and each of the target gates.  That is, MLEGST is run with strings of the form:

    1. prepStr
    2. effectStr
    3. prepStr + effectStr
    4. prepStr + singleGate + effectStr
    5. prepStr + gateStringToEstimate + effectStr

    and the resulting Gateset estimate is returned.

    Parameters
    ----------
    gateStringToEstimate : GateString or tuple
        The single gate string to estimate using LSGST

    gateStringLabel : string
        The label for the estimate of gateStringToEstimate.
        i.e. gate_matrix = returned_gateset[gate_label]

    dataset : DataSet
        The data to use for LGST

    specs : 2-tuple
        A (prepSpecs,effectSpecs) tuple usually generated by calling build_spam_specs(...)

    targetGateset : GateSet
        The target gate set used by LGST to extract gate labels and an initial gauge

    svdTruncateTo : int, optional
        The Hilbert space dimension to truncate the gate matrices to using
        a SVD to keep only the largest svdToTruncateTo singular values of
        the I_tildle LGST matrix. Defaults to no truncation.

    minProbClip : float, optional
        defines the minimum probability "patch point" used
        within the logl function.

    probClipInterval : 2-tuple, optional
        (min,max) to clip probabilities to within GateSet probability
        computation routines (see GateSet.bulk_fill_probs)

    verbosity : int, optional
        Verbosity value to send to do_lgst(...) and do_mlgst(...) calls.

    Returns
    -------
    Gateset
        A gateset containing MLEGST estimates of gateStringToEstimate
        and the gates of targetGateset.
    """
    direct_lgst = gateset_with_lgst_gatestring_estimates( [gateStringToEstimate], dataset, specs, targetGateset,
                                                      True, None, None, [gateStringLabel], svdTruncateTo, verbosity )

    prepStrs, effectStrs = _construction.get_spam_strs(specs)

    # LEXICOGRAPHICAL VS MATRIX ORDER
    gatestrings = prepStrs + effectStrs + [ prepStr + effectStr for prepStr in prepStrs for effectStr in effectStrs ]
    for gateLabel in direct_lgst.gates:
        gatestrings.extend( [ prepStr + _objs.GateString( (gateLabel,), bCheck=False) + effectStr
                              for prepStr in prepStrs for effectStr in effectStrs ] )

    _, direct_mlegst = _alg.do_mlgst(
        dataset, direct_lgst, gatestrings, minProbClip=minProbClip,
        probClipInterval=probClipInterval, verbosity=verbosity,
        gateLabelAliases={gateStringLabel: gateStringToEstimate} )
    return direct_mlegst


def direct_mlgst_gatesets(gateStrings, dataset, specs, targetGateset, svdTruncateTo=0,
                        minProbClip=1e-6, probClipInterval=(-1e6,1e6), verbosity=0):
    """
    Constructs a dictionary with keys == gate strings and values == Direct-MLEGST GateSets.

    Parameters
    ----------
    gateStrings : list of GateString or tuple objects
        The gate strings to estimate using MLEGST.  The elements of this list
        are the keys of the returned dictionary.

    dataset : DataSet
        The data to use for all LGST and LSGST estimates.

    specs : 2-tuple
        A (prepSpecs,effectSpecs) tuple usually generated by calling build_spam_specs(...)

    targetGateset : GateSet
        The target gate set used by LGST to extract gate labels and an initial gauge

    svdTruncateTo : int, optional
        The Hilbert space dimension to truncate the gate matrices to using
        a SVD to keep only the largest svdToTruncateTo singular values of
        the I_tildle LGST matrix. Defaults to no truncation.

    minProbClip : float, optional
        defines the minimum probability "patch point" used
        within the logl function.

    probClipInterval : 2-tuple, optional
        (min,max) to clip probabilities to within GateSet probability
        computation routines (see GateSet.bulk_fill_probs)

    verbosity : int, optional
        Verbosity value to send to do_lgst(...) and do_mlgst(...) calls.

    Returns
    -------
    dict
        A dictionary that relates each gate string of gateStrings to a
        GateSet containing the MLEGST estimate of that gate string stored under
        the gate label "GsigmaLbl", along with MLEGST estimates of the gates in
        targetGateset.
    """
    printer = _objs.VerbosityPrinter.build_printer(verbosity)
    directMLEGSTgatesets = {}
    printer.log("--- Direct MLEGST precomputation ---")
    with printer.progress_logging(1):
        for i,sigma in enumerate(gateStrings):
            printer.show_progress(i, len(gateStrings), prefix="--- Computing gateset for string ", suffix="---")
            directMLEGSTgatesets[sigma] = direct_mlgst_gateset( sigma, "GsigmaLbl", dataset, specs, targetGateset,
                                                            svdTruncateTo, minProbClip, probClipInterval, verbosity)
    return directMLEGSTgatesets


def focused_mc2gst_gateset( gateStringToEstimate, gateStringLabel, dataset, specs, startGateset,
                         minProbClipForWeighting=1e-4, probClipInterval=(-1e6,1e6), verbosity=0 ):
    """
    Constructs a gateset containing a single LSGST estimate of gateStringToEstimate.

    Starting with startGateset, run LSGST with the same gate strings that LGST
    would use to estimate gateStringToEstimate.  That is, LSGST is run with
    strings of the form:  prepStr + gateStringToEstimate + effectStr
    and return the resulting Gateset.

    Parameters
    ----------
    gateStringToEstimate : GateString or tuple
        The single gate string to estimate using LSGST

    gateStringLabel : string
        The label for the estimate of gateStringToEstimate.
        i.e. gate_matrix = returned_gateset[gate_label]

    dataset : DataSet
        The data to use for LGST

    specs : 2-tuple
        A (prepSpecs,effectSpecs) tuple usually generated by calling build_spam_specs(...)

    startGateset : GateSet
        The gate set to seed LSGST with. Often times obtained via LGST.

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight used
        within the chi^2 function (see chi2fn).

    probClipInterval : 2-tuple, optional
        (min,max) to clip probabilities to within GateSet probability
        computation routines (see GateSet.bulk_fill_probs)

    verbosity : int, optional
        Verbosity value to send do_mc2gst(...) call.

    Returns
    -------
    Gateset
        A gateset containing LSGST estimate of gateStringToEstimate.
    """
    prepStrs, effectStrs = _construction.get_spam_strs(specs) # LEXICOGRAPHICAL VS MATRIX ORDER
    gatestrings = [ prepStr + gateStringToEstimate + effectStr for prepStr in prepStrs for effectStr in effectStrs ]

    _, focused_lsgst = _alg.do_mc2gst(
        dataset, startGateset, gatestrings,
        minProbClipForWeighting=minProbClipForWeighting,
        probClipInterval=probClipInterval, verbosity=verbosity)

    focused_lsgst.gates[gateStringLabel] = _objs.FullyParameterizedGate(
            focused_lsgst.product(gateStringToEstimate)) #add desired string as a separate labeled gate
    return focused_lsgst


def focused_mc2gst_gatesets(gateStrings, dataset, specs, startGateset,
                            minProbClipForWeighting=1e-4,
                            probClipInterval=(-1e6,1e6), verbosity=0):
    """
    Constructs a dictionary with keys == gate strings and values == Focused-LSGST GateSets.

    Parameters
    ----------
    gateStrings : list of GateString or tuple objects
        The gate strings to estimate using LSGST.  The elements of this list
        are the keys of the returned dictionary.

    dataset : DataSet
        The data to use for all LGST and LSGST estimates.

    specs : 2-tuple
        A (prepSpecs,effectSpecs) tuple usually generated by calling build_spam_specs(...)

    startGateset : GateSet
        The gate set to seed LSGST with. Often times obtained via LGST.

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight used
        within the chi^2 function (see chi2fn).

    probClipInterval : 2-tuple, optional
        (min,max) to clip probabilities to within GateSet probability
        computation routines (see GateSet.bulk_fill_probs)

    verbosity : int, optional
        Verbosity value to send to do_mc2gst(...) call.

    Returns
    -------
    dict
        A dictionary that relates each gate string of gateStrings to a
        GateSet containing the LSGST estimate of that gate string stored under
        the gate label "GsigmaLbl".
    """

    printer = _objs.VerbosityPrinter.build_printer(verbosity)
    focusedLSGSTgatesets = {}
    printer.log("--- Focused LSGST precomputation ---")
    with printer.progress_logging(1):
        for i,sigma in enumerate(gateStrings):
            printer.show_progress(i, len(gateStrings), prefix="--- Computing gateset for string", suffix='---')
            focusedLSGSTgatesets[sigma] = focused_mc2gst_gateset( sigma, "GsigmaLbl", dataset, specs, startGateset,
                                                               minProbClipForWeighting, probClipInterval, verbosity)
    return focusedLSGSTgatesets


def direct_chi2_matrix(sigma, dataset, directGateset, strs,
                       minProbClipForWeighting=1e-4, fidpair_filter=None,
                       gatestring_filter=None):
    """
    Computes the Direct-X chi^2 matrix for a base gatestring sigma.

    Similar to chi2_matrix, except the probabilities used to compute
    chi^2 values come from using the "composite gate" of directGatesets[sigma],
    a GateSet assumed to contain some estimate of sigma stored under the
    gate label "GsigmaLbl".

    Parameters
    ----------
    sigma : GateString or tuple of gate labels
        The gate sequence that is sandwiched between each prepStr and effectStr

    dataset : DataSet
        The data used to specify frequencies and counts

    directGateset : GateSet
        GateSet which contains an estimate of sigma stored
        under the gate label "GsigmaLbl".

    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight (see chi2fn).

    fidpair_filter : list, optional
        A list of (iRhoStr,iEStr) tuples specifying a subset of all the prepStr,effectStr
        pairs to include in the matrix.

    gatestring_filter : list, optional
        If not None, a list of GateString objects specifying which elements
        of the matrix should be computed.  Any matrix entry corresponding to
        an gate string *not* in this list is set to NaN.


    Returns
    -------
    numpy array of shape ( len(effectStrs), len(prepStrs) )
        Direct-X chi^2 values corresponding to gate sequences where
        gateString is sandwiched between the each (effectStr,prepStr) pair.
    """
    spamlabels = dataset.get_spam_labels()
    gsmap = get_gatestring_map(sigma, dataset, strs, fidpair_filter,
                               gatestring_filter)
    gsmap_pr = get_gatestring_map(_objs.GateString( ("GsigmaLbl",) ), dataset,
                                  strs, fidpair_filter, gatestring_filter)
    cntMxs = total_count_matrix(gsmap, dataset)[None,:,:]
    probMxs = probability_matrices( gsmap_pr, directGateset, spamlabels ) # no probs_precomp_dict
    freqMxs = frequency_matrices( gsmap, dataset, spamlabels)
    chiSqMxs= _tools.chi2fn( cntMxs, probMxs, freqMxs,
                                     minProbClipForWeighting)
    return chiSqMxs.sum(axis=0) # sum over spam labels


def direct_chi2_boxplot( xvals, yvals, xy_gatestring_dict, dataset,
                         directGatesets, strs, xlabel="", ylabel="", scale=1.0,
                         prec='compact', title=r"Direct $\chi^2$", sumUp=False,
                         boxLabels=True, histogram=False, histBins=50,
                         minProbClipForWeighting=1e-4, save_to=None, ticSize=20,
                         invert=False, fidpair_filters=None,
                         gatestring_filters=None, linlg_pcntle=.05):
    """
    Create a color box plot of Direct-X chi^2 values.

    Parameters
    ----------
    xvals, yvals : list
        List of x and y values. Elements can be any hashable quantity, and will be converted
        into x and y tic labels.  Tuples of strings are converted specially for nice latex
        rendering of gate strings.

    xy_gatestring_dict : dict
        Dictionary with keys == (x_value,y_value) tuples and values == gate strings, where
        a gate string can either be a GateString object or a tuple of gate labels.  Provides
        the mapping between x,y pairs and gate strings.  None values are allowed, and
        indicate that there is not data for that x,y pair and nothing should be plotted.

    dataset : DataSet
        The data used to specify frequencies and counts

    directGatesets : dict
        Dictionary with keys == gate strings and values == GateSets.
        directGatesets[sigma] must be a GateSet which contains an estimate
        of sigma stored under the gate label "GsigmaLbl".

    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    xlabel, ylabel : str, optional
        X and Y axis labels

    scale : float, optional
        Scaling factor to adjust the size of the final figure.

    prec : int, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    title : string, optional
        Plot title (latex can be used)

    sumUp : bool, optional
        False displays each matrix element as it's own color box
        True sums the elements of each (x,y) matrix and displays
        a single color box for the sum.

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    histogram : bool, optional
        Whether a histogram of the matrix element values or summed matrix
        values (depending on sumUp) should also be computed and displayed.

    histBins : int, optional
        The number of bins to use in the histogram.

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight used
        within the chi^2 function (see chi2fn).

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    invert : bool, optional
        If True, invert the nesting order of the nested color box plot (applicable
        only when sumUp == False).  Use inner_x_labels and inner_y_labels to label
        the x and y axes.

    fidpair_filters : dict, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose values
        are lists of (iRhoStr,iEStr) tuples specifying a subset of all the
        prepStr,effectStr pairs to include in the plot for each particular
        (x,y) sub-block.

    gatestring_filters : dict, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose
        values are lists of GateString objects specifying which elements should
        be computed and displayed in the (x,y) sub-block of the plot.

    linlg_pcntle: float, optional
        Specifies the (1 - linlg_pcntle) percentile to compute for the boxplots


    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.  Extra figure
        info is a dict with keys:

        nUsedXs : int
            The number of used X-values, proportional to the overall final figure width
        nUsedYs : int
            The number of used Y-values, proportional to the overall final figure height
    """
    prepStrs, effectStrs = strs

    def mx_fn(gateStr,x,y):
        return direct_chi2_matrix( gateStr, dataset, directGatesets.get(gateStr,None),
                                   strs, minProbClipForWeighting,
                                   fidpair_filters[(x,y)] if (fidpair_filters is not None) else None,
                                   gatestring_filters[(x,y)] if (gatestring_filters is not None) else None)

    xvals,yvals,subMxs,n_boxes,dof = _computeSubMxs(xvals,yvals,xy_gatestring_dict,mx_fn,sumUp)
    stdcmap = StdColormapFactory('linlog', n_boxes=n_boxes, linlg_pcntle=linlg_pcntle, dof=dof)

    return generate_boxplot( xvals, yvals, xy_gatestring_dict, subMxs, stdcmap, xlabel, ylabel,
                            scale,prec,title,sumUp,boxLabels,histogram,histBins,save_to,ticSize,
                            invert, prepStrs, effectStrs, r"$\rho_i$", r"$E_i$",  )



def direct_logl_matrix(sigma, dataset, directGateset, strs,
                       minProbClip=1e-6, fidpair_filter=None,
                       gatestring_filter=None):
    """
    Computes the Direct-X log-likelihood matrix, containing the values
     of 2*( log(L)_upperbound - log(L) ) for a base gatestring sigma.

    Similar to logl_matrix, except the probabilities used to compute
    LogL values come from using the "composite gate" of directGatesets[sigma],
    a GateSet assumed to contain some estimate of sigma stored under the
    gate label "GsigmaLbl".

    Parameters
    ----------
    sigma : GateString or tuple of gate labels
        The gate sequence that is sandwiched between each prepStr and effectStr

    dataset : DataSet
        The data used to specify frequencies and counts

    directGateset : GateSet
        GateSet which contains an estimate of sigma stored
        under the gate label "GsigmaLbl".

    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    minProbClip : float, optional
        defines the minimum probability clipping.

    fidpair_filter : list, optional
        A list of (iRhoStr,iEStr) tuples specifying a subset of all the prepStr,effectStr
        pairs to include in the matrix.  Other elements are set to NaN.

    gatestring_filter : list, optional
        If not None, a list of GateString objects specifying which elements
        of the matrix should be computed.  Any matrix entry corresponding to
        an gate string *not* in this list is set to NaN.


    Returns
    -------
    numpy array of shape ( len(effectStrs), len(prepStrs) )
        Direct-X chi^2 values corresponding to gate sequences where
        gateString is sandwiched between the each (effectStr,prepStr) pair.
    """
    spamlabels = dataset.get_spam_labels()
    gsmap = get_gatestring_map(sigma, dataset, strs, fidpair_filter,
                               gatestring_filter)
    gsmap_pr = get_gatestring_map(_objs.GateString( ("GsigmaLbl",) ), dataset,
                                  strs, fidpair_filter, gatestring_filter)
    cntMxs = total_count_matrix(gsmap, dataset)[None,:,:]
    probMxs = probability_matrices( gsmap_pr, directGateset, spamlabels ) # no probs_precomp_dict
    freqMxs = frequency_matrices( gsmap, dataset, spamlabels)
    logLMxs = _tools.two_delta_loglfn( cntMxs, probMxs, freqMxs, minProbClip)
    return logLMxs.sum(axis=0) # sum over spam labels


def direct_logl_boxplot( xvals, yvals, xy_gatestring_dict, dataset,
                         directGatesets, strs, xlabel="", ylabel="", scale=1.0,
                         prec='compact', title=r"Direct $\log(\mathcal{L})$",
                         sumUp=False, boxLabels=True, histogram=False,
                         histBins=50, minProbClipForWeighting=1e-4, 
                         save_to=None, ticSize=20, invert=False,
                         fidpair_filters=None, gatestring_filters=None,
                         linlg_pcntle=.05):
    """
    Create a color box plot of Direct-X log-likelihood values.

    Parameters
    ----------
    xvals, yvals : list
        List of x and y values. Elements can be any hashable quantity, and will be converted
        into x and y tic labels.  Tuples of strings are converted specially for nice latex
        rendering of gate strings.

    xy_gatestring_dict : dict
        Dictionary with keys == (x_value,y_value) tuples and values == gate strings, where
        a gate string can either be a GateString object or a tuple of gate labels.  Provides
        the mapping between x,y pairs and gate strings.  None values are allowed, and
        indicate that there is not data for that x,y pair and nothing should be plotted.

    dataset : DataSet
        The data used to specify frequencies and counts

    directGatesets : dict
        Dictionary with keys == gate strings and values == GateSets.
        directGatesets[sigma] must be a GateSet which contains an estimate
        of sigma stored under the gate label "GsigmaLbl".

    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    xlabel, ylabel : str, optional
        X and Y axis labels

    scale : float, optional
        Scaling factor to adjust the size of the final figure.

    prec : int, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    title : string, optional
        Plot title (latex can be used)

    sumUp : bool, optional
        False displays each matrix element as it's own color box
        True sums the elements of each (x,y) matrix and displays
        a single color box for the sum.

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    histogram : bool, optional
        Whether a histogram of the matrix element values or summed matrix
        values (depending on sumUp) should also be computed and displayed.

    histBins : int, optional
        The number of bins to use in the histogram.

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight used
        within the logl function.

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    invert : bool, optional
        If True, invert the nesting order of the nested color box plot (applicable
        only when sumUp == False).  Use inner_x_labels and inner_y_labels to label
        the x and y axes.

    fidpair_filters : dict, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose values
        are lists of (iRhoStr,iEStr) tuples specifying a subset of all the
        prepStr,effectStr pairs to include in the plot for each particular
        (x,y) sub-block.

    gatestring_filters : dict, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose
        values are lists of GateString objects specifying which elements should
        be computed and displayed in the (x,y) sub-block of the plot.

    linlg_pcntle: float, optional
        Specifies the (1 - linlg_pcntle) percentile to compute for the boxplots


    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.  Extra figure
        info is a dict with keys:

        nUsedXs : int
            The number of used X-values, proportional to the overall final figure width
        nUsedYs : int
            The number of used Y-values, proportional to the overall final figure height
    """
    prepStrs, effectStrs = strs
    def mx_fn(gateStr,x,y):
        return direct_logl_matrix( gateStr, dataset, directGatesets.get(gateStr,None),
                                   strs, minProbClipForWeighting,
                                   fidpair_filters[(x,y)] if (fidpair_filters is not None) else None,
                                   gatestring_filters[(x,y)] if (gatestring_filters is not None) else None)

    xvals,yvals,subMxs,n_boxes,dof = _computeSubMxs(xvals,yvals,xy_gatestring_dict,mx_fn,sumUp)
    stdcmap = StdColormapFactory('linlog', n_boxes=n_boxes, linlg_pcntle=linlg_pcntle, dof=dof)

    return generate_boxplot( xvals, yvals, xy_gatestring_dict, subMxs, stdcmap, xlabel, ylabel,
                            scale,prec,title,sumUp,boxLabels,histogram,histBins,save_to,ticSize,
                            invert, prepStrs, effectStrs, r"$\rho_i$", r"$E_i$",  )


def direct2x_comp_boxplot( xvals, yvals, xy_gatestring_dict, dataset, directGatesets, strs, xlabel="", ylabel="",
                             m=None, M=None, scale=1.0, prec='compact', title="Direct 2x Chi^2 Comparison", sumUp=False,
                             boxLabels=True, histogram=False, histBins=50, minProbClipForWeighting=1e-4,
                             save_to=None, ticSize=20, invert=False):
    """
    Create a box plot indicating how well the Direct-X estimates of string s
    predict the data for 2s (the string repeated)

    Creates a color box plot whose boxes (or box, if sumUp == True) at
    position (x,y) display the chi^2 for the (x,y) base gate string
    **repeated twice** (if this data is available), where the probabilities
    used in the chi^2 calculation are obtained using the Direct-X gateset
    for the un-repeated (x,y) base gate string.  That is, the box(es) at
    coordinates x,y show how well the Direct-X estimates of xy_gatestring_dict[(x,y)]
    reproduce the observed frequencies of 2 * xy_gatestring_dict[(x,y)] (the
    gate string repeated twice).

    Parameters
    ----------
    xvals, yvals : list
        List of x and y values. Elements can be any hashable quantity, and will be converted
        into x and y tic labels.  Tuples of strings are converted specially for nice latex
        rendering of gate strings.

    xy_gatestring_dict : dict
        Dictionary with keys == (x_value,y_value) tuples and values == gate strings, where
        a gate string can either be a GateString object or a tuple of gate labels.  Provides
        the mapping between x,y pairs and gate strings.  None values are allowed, and
        indicate that there is not data for that x,y pair and nothing should be plotted.

    dataset : DataSet
        The data used to specify frequencies and counts

    directGatesets : dict
        Dictionary with keys == gate strings and values == GateSets.
        directGatesets[sigma] must be a GateSet which contains an estimate
        of sigma stored under the gate label "GsigmaLbl".

    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    xlabel, ylabel : str, optional
        X and Y axis labels

    m, M : float, optional
        Min and max values of the color scale.

    scale : float, optional
        Scaling factor to adjust the size of the final figure.

    prec : int, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    title : string, optional
        Plot title (latex can be used)

    sumUp : bool, optional
        False displays each matrix element as it's own color box
        True sums the elements of each (x,y) matrix and displays
        a single color box for the sum.

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    histogram : bool, optional
        Whether a histogram of the matrix element values or summed matrix
        values (depending on sumUp) should also be computed and displayed.

    histBins : int, optional
        The number of bins to use in the histogram.

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight used
        within the chi^2 function (see chi2fn).

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    invert : bool, optional
        If True, invert the nesting order of the nested color box plot (applicable
        only when sumUp == False).  Use inner_x_labels and inner_y_labels to label
        the x and y axes.

    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.   Extra figure
        info is a dict with keys:

        nUsedXs : int
            The number of used X-values, proportional to the overall final figure width
        nUsedYs : int
            The number of used Y-values, proportional to the overall final figure height
    """
    prepStrs, effectStrs = strs
    nanMx = _np.nan * _np.zeros( (len(strs[1]),len(strs[0])), 'd')
    gatestring_filter = None #don't use gatestring filters for now

    def mx_fn(gateStr,x,y):

        if gateStr is None: return nanMx
        directGateset = directGatesets[ gateStr ] #contains "GsigmaLbl" gate <=> gateStr
        spamlabels = directGateset.get_spam_labels()

        try:
            gsmap = get_gatestring_map(gateStr*2, dataset, strs, None,
                                       gatestring_filter)
            gsmap_pr = get_gatestring_map(_objs.GateString(
                    ("GsigmaLbl","GsigmaLbl") ), None,
                    strs, None, gatestring_filter) 
                   # Note: don't test for dataset membership here (dataset
                   #       doesn't contain "GsigmaLbl"!)
            cntMxs = total_count_matrix(gsmap, dataset)[None,:,:]
            probMxs = probability_matrices( gsmap_pr, directGateset, spamlabels ) # no probs_precomp_dict
            freqMxs = frequency_matrices( gsmap, dataset, spamlabels)
            chiSqMxs= _tools.chi2fn( cntMxs, probMxs, freqMxs,
                                     minProbClipForWeighting)
        except:
            return nanMx #if something fails, just punt

        return chiSqMxs.sum(axis=0) # sum over spam labels

    xvals,yvals,subMxs,n_boxes,dof = _computeSubMxs(xvals,yvals,xy_gatestring_dict,mx_fn,sumUp)
    max_abs = max([ _np.max(_np.abs(_np.nan_to_num(subMxs[iy][ix])))
                    for ix in range(len(xvals))
                    for iy in range(len(yvals)) ])
    if max_abs == 0: max_abs = 1e-6 # pick a nonzero value if all entries are zero or nan
    m = -max_abs if m is None else m
    M = +max_abs if M is None else M
    stdcmap = StdColormapFactory('div', n_boxes=n_boxes, vmin=m, vmax=M, dof=dof)

    return generate_boxplot( xvals, yvals, xy_gatestring_dict, subMxs, stdcmap, xlabel, ylabel,
                            scale,prec,title,sumUp,boxLabels,histogram,histBins,
                            save_to,ticSize,invert, prepStrs, effectStrs, r"$\rho_i$", r"$E_i$" )


def direct_deviation_boxplot( xvals, yvals, xy_gatestring_dict, dataset, gateset, directGatesets,
                                 xlabel="", ylabel="", m=None, M=None, scale=1.0, prec='compact', title="Direct Deviation",
                                 boxLabels=True, histogram=False, histBins=50, save_to=None, ticSize=20):
    """
    Create a box plot showing the difference in max-fidelity-with-unitary
    between gateset's estimate for each base gate string and the Direct-X estimate.

    Creates a color box plot whose box at position (x,y) shows the
    the difference between:

    1. the upper bound of the fidelity between the map corresponding to
       this base gate string using the Direct-X estimate of this map
       (i.e. by using only data relevant to this particular string) and
       a unitary map.

    2. the upper bound of the fidelity between the map corresponding to
       this base gate string using gateset (i.e. by multiplying together
       single gate estimates) and a unitary map.

    The plotted quantity indicates how much more "unitary", i.e. how
    much less "depolarized", the map corresponding to each base gate
    sequence is when considering only the data immediately relevant
    to predicting that map. Large absolute values indicate that
    the data used for fitting other gate sequences has made the estimate
    for the subject gate sequence more depolarized (~worse) than the
    data for the sequence alone would suggest.

    Parameters
    ----------
    xvals, yvals : list
        List of x and y values. Elements can be any hashable quantity, and will be converted
        into x and y tic labels.  Tuples of strings are converted specially for nice latex
        rendering of gate strings.

    xy_gatestring_dict : dict
        Dictionary with keys == (x_value,y_value) tuples and values == gate strings, where
        a gate string can either be a GateString object or a tuple of gate labels.  Provides
        the mapping between x,y pairs and gate strings.  None values are allowed, and
        indicate that there is not data for that x,y pair and nothing should be plotted.

    dataset : DataSet
        The data used to specify frequencies and counts

    directGatesets : dict
        Dictionary with keys == gate strings and values == GateSets.
        directGatesets[sigma] must be a GateSet which contains an estimate
        of sigma stored under the gate label "GsigmaLbl".

    xlabel, ylabel : str, optional
        X and Y axis labels

    m, M : float, optional
        Min and max values of the color scale.

    scale : float, optional
        Scaling factor to adjust the size of the final figure.

    prec : int, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    title : string, optional
        Plot title (latex can be used)

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    histogram : bool, optional
        Whether a histogram of the matrix element values or summed matrix
        values (depending on sumUp) should also be computed and displayed.

    histBins : int, optional
        The number of bins to use in the histogram.

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.  Extra figure
        info is a dict with keys:

        nUsedXs : int
            The number of used X-values, proportional to the overall final figure width
        nUsedYs : int
            The number of used Y-values, proportional to the overall final figure height
    """
    def mx_fn(gateStr,x,y):
        if gateStr is None: return _np.nan * _np.zeros( (1,1), 'd')
        gate = gateset.product( gateStr )
        gate_direct = directGatesets[ gateStr ].gates[ "GsigmaLbl" ]
        #evals = _np.linalg.eigvals(gate)
        #evals_direct = _np.linalg.eigvals(gate_direct)
        ubF, _ = _tools.fidelity_upper_bound(gate)
        ubF_direct, _ = _tools.fidelity_upper_bound(gate_direct)
        return _np.array( ubF_direct - ubF, dtype='float64' )

    xvals, yvals, subMxs, _, _ = _computeSubMxs(xvals,yvals,xy_gatestring_dict,mx_fn,True)
    max_abs = max([ _np.max(_np.abs(_np.nan_to_num(subMxs[iy][ix])))
                    for ix in range(len(xvals))
                    for iy in range(len(yvals)) ])
    if max_abs == 0: max_abs = 1e-6 # pick a nonzero value if all entries are zero or nan
    m = -max_abs if m is None else m
    M = +max_abs if M is None else M
    stdcmap = StdColormapFactory('div', vmin=m, vmax=M, midpoint=0)
    return generate_boxplot( xvals, yvals, xy_gatestring_dict, subMxs, stdcmap, xlabel, ylabel,
                            scale,prec,title,True,boxLabels,histogram,histBins,save_to,ticSize)



def whack_a_chi2_mole_boxplot( gatestringToWhack, allGatestringsUsedInChi2Opt,
                               xvals, yvals, xy_gatestring_dict, dataset,
                               gateset, strs, xlabel="", ylabel="", m=None,
                               M=None, scale=1.0, prec='compact',
                               title="Whack a Chi^2 Mole", sumUp=False,
                               boxLabels=True, histogram=False, histBins=50,
                               minProbClipForWeighting=1e-4, save_to=None, 
                               ticSize=20, whackWith=10.0, invert=False, 
                               fidpair_filters=None, gatestring_filters=None):
    """
    Create a box plot indicating how the chi^2 would change if the chi^2 of one
      base gate string blocks were forced to be smaller ("whacked").

    Creates a color box plot which displays the change in chi^2 caused by
      changing the gate set parameters such that the chi^2 of gatestringToWhack's
      (x,y) block decreases by whackWith.  This changes the gate set along
      the direction of parameter space given by the gradient of chi^2 restricted
      to only those gatestrings in gatestringToWhack's block, and the the
      displayed difference in chi^2 values are based on the linear interpolation
      of the full gradient of chi^2 after this change.

    Parameters
    ----------
    gatestringToWhack : GateString or tuple
        The **base** gate sequence for which chi^2 will be decreased.

    allGatestringsUsedInChi2Opt : list of GateStrings or tuples
        List of all the gate strings used to form the total chi^2 that is being decreased.

    xvals, yvals : list
        List of x and y values. Elements can be any hashable quantity, and will be converted
        into x and y tic labels.  Tuples of strings are converted specially for nice latex
        rendering of gate strings.

    xy_gatestring_dict : dict
        Dictionary with keys == (x_value,y_value) tuples and values == gate strings, where
        a gate string can either be a GateString object or a tuple of gate labels.  Provides
        the mapping between x,y pairs and gate strings.  None values are allowed, and
        indicate that there is not data for that x,y pair and nothing should be plotted.

    dataset : DataSet
        The data used to specify frequencies and counts

    gateset : GateSet
        The gate set used for computing probabilities

    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    xlabel, ylabel : str, optional
        X and Y axis labels

    m, M : float, optional
        Min and max values of the color scale.

    scale : float, optional
        Scaling factor to adjust the size of the final figure.

    prec : int, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    title : string, optional
        Plot title (latex can be used)

    sumUp : bool, optional
        False displays each matrix element as it's own color box
        True sums the elements of each (x,y) matrix and displays
        a single color box for the sum.

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    histogram : bool, optional
        Whether a histogram of the matrix element values or summed matrix
        values (depending on sumUp) should also be computed and displayed.

    histBins : int, optional
        The number of bins to use in the histogram.

    minProbClipForWeighting : float, optional
        defines the clipping interval for the statistical weight used
        within the chi^2 function (see chi2fn).

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    whackWith : float, optional
        the total amount to decrease chi^2 by.  This number just sets the
        overall scale of the numbers displayed, since the extrapolation is
        linear.

    invert : bool, optional
        If True, invert the nesting order of the nested color box plot (applicable
        only when sumUp == False).  Use inner_x_labels and inner_y_labels to label
        the x and y axes.

    fidpair_filters : dict, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose values
        are lists of (iRhoStr,iEStr) tuples specifying a subset of all the
        prepStr,effectStr pairs to include in the plot for each particular
        (x,y) sub-block.

    gatestring_filters : dict, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose
        values are lists of GateString objects specifying which elements should
        be computed and displayed in the (x,y) sub-block of the plot.


    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.  Extra figure
        info is a dict with keys:

        nUsedXs : int
            The number of used X-values, proportional to the overall final figure width
        nUsedYs : int
            The number of used Y-values, proportional to the overall final figure height
    """

    #We want the derivative of chi^2 = sum_i N_i*(p_i-f_i)^2 / p_i  (i over gatestrings & spam labels)
    # and the ability to separate the chi^2 of just "gatestringToWhack" (sandwiched with strs, optionally)
    # This latter derivative (w.r.t gateset params) gives the direction in gateset space to move to reduce
    #   the "whacked" string(s) chi2.  Applying this direction to the full chi^2 (or to other base strings
    #   sandwiched with strs) will give the relative change in the chi^2 for these strings if the whacked
    #   string(s) was in fact whacked.
    # D(chi^2) = sum_i N_i * [ 2(p_i-f_i)*dp_i / p_i - (p_i-f_i)^2 / p_i^2 * dp_i ]
    #          = sum_i N_i * (p_i-f_i) / p_i * [2 - (p_i-f_i)/p_i   ] * dp_i
    prepStrs, effectStrs = strs
    spamLabels = gateset.get_spam_labels() #this list fixes the ordering of the spam labels
    spam_lbl_rows = { sl:i for (i,sl) in enumerate(spamLabels) }
    vec_gs_len = gateset.num_params()

    N      = _np.empty( len(allGatestringsUsedInChi2Opt) )
    f      = _np.empty( (len(spamLabels),len(allGatestringsUsedInChi2Opt)) )
    probs  = _np.empty( (len(spamLabels),len(allGatestringsUsedInChi2Opt)) )
    dprobs = _np.empty( (len(spamLabels),len(allGatestringsUsedInChi2Opt),vec_gs_len) )

    for (i,gateStr) in enumerate(allGatestringsUsedInChi2Opt):
        N[i] = float(dataset[gateStr].total())
        for k,sl in enumerate(spamLabels):
            f[k,i] = dataset[gateStr].fraction(sl)

    evTree = gateset.bulk_evaltree(allGatestringsUsedInChi2Opt)
    gateset.bulk_fill_dprobs(dprobs, spam_lbl_rows, evTree, prMxToFill=probs)

    t = ((probs - f)/probs)[:,:,None]
    Dchi2 = N[None,:,None] * t * (2 - t) * dprobs  # (1,M,1) * (K,M,1) * (K,M,N)

    # LEXICOGRAPHICAL VS MATRIX ORDER
    
    #Since we don't know which (x,y) pair the gatestringToWhack is associated with,
    # we try to wack *all* possible fiducial pairs.
    gatestringsToWhack = [ (prepStr + gatestringToWhack + effectStr)
                           for prepStr in prepStrs for effectStr in effectStrs ]

    whacked_indices = []
    for s in gatestringsToWhack:
        try: whacked_indices.append( allGatestringsUsedInChi2Opt.index(s) )
        except ValueError: pass #just ignore gatestrings that aren't available
    whacked_Dchi2 = _np.take(Dchi2,whacked_indices,axis=1) # (K,m,N) where m == len(whacked_indices)

    grad = -1.0 * _np.sum(whacked_Dchi2,axis=(0,1)) # (N) after summing over gate strings and spam labels
    dx = whackWith * grad / _np.dot(grad,grad) # direction in gateset space of direction to move to *decrease* the chi2
                                               #  of the desired base string by whackWith
    delta = _np.sum( _np.dot(Dchi2,dx), axis=0 ) # sum{(K,M), axis=1} ==> (M); the change in chi2 for each gateString
                                                 #   as a result of a unit decrease in the chi2 of the base string

    def mx_fn(gateStr,x,y):
        # LEXICOGRAPHICAL VS MATRIX ORDER
        if gateStr is None: return _np.nan * _np.zeros( (len(effectStrs),len(prepStrs)), 'd')

        if gatestring_filters is not None:
            ret = _np.nan * _np.ones( (len(effectStrs),len(prepStrs)), 'd')
            for j,effectStr in enumerate(effectStrs):
                for i,prepStr in enumerate(prepStrs):
                    s = prepStr + gateStr + effectStr
                    if s in gatestring_filters[(x,y)] \
                            and s in allGatestringsUsedInChi2Opt:
                        ret[j,i] = delta[allGatestringsUsedInChi2Opt.index(s)]
            return ret

        elif fidpair_filters is not None:
            ret = _np.nan * _np.ones( (len(effectStrs),len(prepStrs)), 'd')
            for i,j in fidpair_filters[(x,y)]:
                s = prepStrs[i] + gateStr + effectStrs[j]
                if s in allGatestringsUsedInChi2Opt:
                    ret[j,i] = delta[ allGatestringsUsedInChi2Opt.index(s) ]
            return ret

        else:
            return _np.array( [ [ delta[ allGatestringsUsedInChi2Opt.index(prepStr + gateStr + effectStr) ]
                                  for prepStr in prepStrs  ] for effectStr in effectStrs ] )


    xvals,yvals,subMxs,n_boxes,dof = _computeSubMxs(xvals,yvals,xy_gatestring_dict,mx_fn,sumUp)
    max_abs = max([ _np.max(_np.abs(_np.nan_to_num(subMxs[iy][ix])))
                    for ix in range(len(xvals))
                    for iy in range(len(yvals)) ])
    if max_abs == 0: max_abs = 1e-6 # pick a nonzero value if all entries are zero or nan
    m = -max_abs if m is None else m
    M = +max_abs if M is None else M
    stdcmap = StdColormapFactory('div', n_boxes=n_boxes, vmin=m, vmax=M, dof=dof, midpoint=0)
    return generate_boxplot( xvals, yvals, xy_gatestring_dict, subMxs, stdcmap, xlabel, ylabel,
                            scale,prec,title,sumUp,boxLabels,histogram,histBins,save_to,ticSize,
                            invert, prepStrs, effectStrs, r"$\rho_i$", r"$E_i$" )



def whack_a_logl_mole_boxplot( gatestringToWhack, allGatestringsUsedInLogLOpt,
                               xvals, yvals, xy_gatestring_dict, dataset,
                               gateset, strs, xlabel="", ylabel="", m=None,
                               M=None, scale=1.0, prec='compact',
                               title="Whack a log(L) Mole", sumUp=False,
                               boxLabels=True, histogram=False, histBins=50,
                               minProbClipForWeighting=1e-4, save_to=None,
                               ticSize=20, whackWith=10.0, invert=False,
                               fidpair_filters=None, gatestring_filters=None):
    """
    Create a box plot indicating how the log-likelihood would change if the log(L)
      of one base gate string blocks were forced to be smaller ("whacked").

    Creates a color box plot which displays the change in log(L) caused by
      changing the gate set parameters such that the log(L) of gatestringToWhack's
      (x,y) block decreases by whackWith.  This changes the gate set along
      the direction of parameter space given by the gradient of log(L) restricted
      to only those gatestrings in gatestringToWhack's block, and the the
      displayed difference in log(L) values are based on the linear interpolation
      of the full gradient of log(L) after this change.

    Parameters
    ----------
    gatestringToWhack : GateString or tuple
        The **base** gate sequence for which log(L) will be decreased.

    allGatestringsUsedInLogLOpt : list of GateStrings or tuples
        List of all the gate strings used to form the total log(L) that is being decreased.

    xvals, yvals : list
        List of x and y values. Elements can be any hashable quantity, and will be converted
        into x and y tic labels.  Tuples of strings are converted specially for nice latex
        rendering of gate strings.

    xy_gatestring_dict : dict
        Dictionary with keys == (x_value,y_value) tuples and values == gate strings, where
        a gate string can either be a GateString object or a tuple of gate labels.  Provides
        the mapping between x,y pairs and gate strings.  None values are allowed, and
        indicate that there is not data for that x,y pair and nothing should be plotted.

    dataset : DataSet
        The data used to specify frequencies and counts

    gateset : GateSet
        The gate set used for computing probabilities

    strs : 2-tuple
        A (prepStrs,effectStrs) tuple usually generated by calling get_spam_strs(...)

    xlabel, ylabel : str, optional
        X and Y axis labels

    m, M : float, optional
        Min and max values of the color scale.

    scale : float, optional
        Scaling factor to adjust the size of the final figure.

    prec : int, optional
        Precision for box labels.  Allowed values are:
          'compact' = round to nearest whole number using at most 3 characters
          'compacthp' = show as much precision as possible using at most 3 characters
          int >= 0 = fixed precision given by int
          int <  0 = number of significant figures given by -int

    title : string, optional
        Plot title (latex can be used)

    sumUp : bool, optional
        False displays each matrix element as it's own color box
        True sums the elements of each (x,y) matrix and displays
        a single color box for the sum.

    boxLabels : bool, optional
        Whether box labels are displayed.  It takes much longer to
        generate the figure when this is set to True.

    histogram : bool, optional
        Whether a histogram of the matrix element values or summed matrix
        values (depending on sumUp) should also be computed and displayed.

    histBins : int, optional
        The number of bins to use in the histogram.

    minProbClipForWeighting : float, optional
        defines the minimum probability clipping for the log(L) function.

    save_to : str, optional
        save figure to this filename (usually ending in .pdf)

    ticSize : int, optional
        size of tic marks

    whackWith : float, optional
        the total amount to decrease chi^2 by.  This number just sets the
        overall scale of the numbers displayed, since the extrapolation is
        linear.

    invert : bool, optional
        If True, invert the nesting order of the nested color box plot (applicable
        only when sumUp == False).  Use inner_x_labels and inner_y_labels to label
        the x and y axes.

    fidpair_filters : dict, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose values
        are lists of (iRhoStr,iEStr) tuples specifying a subset of all the
        prepStr,effectStr pairs to include in the plot for each particular
        (x,y) sub-block.

    gatestring_filters : dict, optional
        If not None, a dictionary whose keys are (x,y) tuples and whose
        values are lists of GateString objects specifying which elements should
        be computed and displayed in the (x,y) sub-block of the plot.


    Returns
    -------
    rptFig : ReportFigure
        The encapsulated matplotlib figure that was generated.  Extra figure
        info is a dict with keys:

        nUsedXs : int
            The number of used X-values, proportional to the overall final figure width
        nUsedYs : int
            The number of used Y-values, proportional to the overall final figure height
    """

    #We want the derivative of 2*Delta_LogL = 2 * sum_i N_i*(f_i*log(f_i/p_i) + (p_i-f_i))  (i over gatestrings & spam labels)
    # and the ability to separate the 2*Delta_LogL of just "gatestringToWhack" (sandwiched with strs, optionally)
    # This latter derivative (w.r.t gateset params) gives the direction in gateset space to move to reduce
    #   the "whacked" string(s) 2*Delta_LogL.  Applying this direction to the full 2*Delta_LogL (or to other base strings
    #   sandwiched with strs) will give the relative change in the 2*Delta_LogL for these strings if the whacked
    #   string(s) was in fact whacked.
    # D(2*Delta_LogL) = sum_i 2* N_i * [ -f_i/p_i + 1.0 ] * dp_i

    prepStrs, effectStrs = strs
    spamLabels = gateset.get_spam_labels() #this list fixes the ordering of the spam labels
    spam_lbl_rows = { sl:i for (i,sl) in enumerate(spamLabels) }
    vec_gs_len = gateset.num_params()
      #Note: assumes *all* gateset params vary, which may not be what we always want (e.g. for TP-constrained analyses)

    N      = _np.empty( len(allGatestringsUsedInLogLOpt) )
    f      = _np.empty( (len(spamLabels),len(allGatestringsUsedInLogLOpt)) )
    probs  = _np.empty( (len(spamLabels),len(allGatestringsUsedInLogLOpt)) )
    dprobs = _np.empty( (len(spamLabels),len(allGatestringsUsedInLogLOpt),vec_gs_len) )

    for (i,gateStr) in enumerate(allGatestringsUsedInLogLOpt):
        N[i] = float(dataset[gateStr].total())
        for k,sl in enumerate(spamLabels):
            f[k,i] = dataset[gateStr].fraction(sl)

    evTree = gateset.bulk_evaltree(allGatestringsUsedInLogLOpt)
    gateset.bulk_fill_dprobs(dprobs, spam_lbl_rows, evTree, prMxToFill=probs) # spamlabel, gatestring, gsParam

    pos_probs = _np.maximum(probs, minProbClipForWeighting) #make sure all probs are positive? TODO: make this fn handle minProbClip like do_mlgst does...
    DlogL = 2 * (N[None,:] * (1.0 - f/pos_probs))[:,:,None] * dprobs # (K,M,1) * (K,M,N)

    # LEXICOGRAPHICAL VS MATRIX ORDER

    #Since we don't know which (x,y) pair the gatestringToWhack is associated with,
    # we try to wack *all* possible fiducial pairs.
    gatestringsToWhack = [ (prepStr + gatestringToWhack + effectStr) for prepStr in prepStrs for effectStr in effectStrs ]

    whacked_indices = []
    for s in gatestringsToWhack:
        try:  whacked_indices.append(allGatestringsUsedInLogLOpt.index(s))
        except ValueError: pass #just ignore gatestrings that aren't available
    whacked_DlogL = _np.take(DlogL,whacked_indices,axis=1) # (K,m,N) where m == len(whacked_indices)

    grad = -1.0 * _np.sum(whacked_DlogL,axis=(0,1)) # (N) after summing over gate strings and spam labels
    dx = whackWith * grad / _np.dot(grad,grad) # direction in gateset space of direction to move to *decrease* the 2*Delta_LogL
                                               #  of the desired base string by whackWith
    delta = _np.sum( _np.dot(DlogL,dx), axis=0 ) # sum{(K,M), axis=1} ==> (M); the change in 2*Delta_LogL for each gateString
                                                 #   as a result of a unit decrease in the 2*Delta_LogL of the base string

    def mx_fn(gateStr,x,y):
        # LEXICOGRAPHICAL VS MATRIX ORDER
        if gateStr is None: return _np.nan * _np.zeros( (len(effectStrs),len(prepStrs)), 'd')

        if gatestring_filters is not None:
            ret = _np.nan * _np.ones( (len(effectStrs),len(prepStrs)), 'd')
            for j,effectStr in enumerate(effectStrs):
                for i,prepStr in enumerate(prepStrs):
                    s = prepStr + gateStr + effectStr
                    if s in gatestring_filters[(x,y)] \
                            and s in allGatestringsUsedInLogLOpt:
                        ret[j,i] = delta[allGatestringsUsedInLogLOpt.index(s)]
            return ret

        elif fidpair_filters is not None:
            ret = _np.nan * _np.ones( (len(effectStrs),len(prepStrs)), 'd')
            for i,j in fidpairs_filters[(x,y)]:
                s = prepStrs[i] + gateStr + effectStrs[j]
                if s in allGatestringsUsedInLogLOpt:
                    ret[j,i] = delta[ allGatestringsUsedInLogLOpt.index(s) ]
            return ret

        else:
            return _np.array( [ [ delta[ allGatestringsUsedInLogLOpt.index(prepStr + gateStr + effectStr) ]
                                  for prepStr in prepStrs ] for effectStr in effectStrs ] )


    xvals,yvals,subMxs,n_boxes,dof = _computeSubMxs(xvals,yvals,xy_gatestring_dict,mx_fn,sumUp)
    max_abs = max([ _np.max(_np.abs(_np.nan_to_num(subMxs[iy][ix])))
                    for ix in range(len(xvals))
                    for iy in range(len(yvals)) ])
    if max_abs == 0: max_abs = 1e-6 # pick a nonzero value if all entries are zero or nan
    m = -max_abs if m is None else m
    M = +max_abs if M is None else M
    stdcmap = StdColormapFactory('div', n_boxes=n_boxes, vmin=m, vmax=M, dof=dof, midpoint=0)
    return generate_boxplot( xvals, yvals, xy_gatestring_dict, subMxs, stdcmap, xlabel, ylabel,
                            scale,prec,title,sumUp,boxLabels,histogram,histBins,save_to,ticSize,
                            invert, prepStrs, effectStrs, r"$\rho_i$", r"$E_i$" )



def gate_matrix_boxplot(gateMatrix, size=None, m=-1.0, M=1.0,
                        save_to=None, fontSize=20, mxBasis=None,
                        mxBasisDims=None, xlabel=None, ylabel=None,
                        title=None, boxLabels=False, prec=0, mxBasisDimsY=None):
    """
    Creates a color box plot of a gate matrix using a diverging color map.

    This can be a useful way to display large matrices which have so many
    entries that their entries cannot easily fit within the width of a page.

    Parameters
    ----------
    gateMatrix : ndarray
      The gate matrix data to display.

    size : tuple, optional
      The (width,height) figure size in inches.  None
      enables automatic calculation based on gateMatrix
      size.

    m, M : float, optional
      Min and max values of the color scale.

    save_to : str, optional
      save figure as this filename (usually ending in .pdf)

    fontSize : int, optional
      size of font for title

    mxBasis : str, optional
      The name abbreviation for the basis. Typically in {"pp","gm","std","qt"}.
      Used to label the rows & columns.  If you don't want labels, leave as
      None.

    mxBasisDims : int or list, optional
      The dimension of the density matrix space this basis spans, or a
      list specifying the dimensions of terms in a direct-sum
      decomposition of the density matrix space.  Used to label the
      rows & columns.  If you don't want labels, leave as None.

    xlabel : str, optional
      An x-axis label for the plot.

    ylabel : str, optional
      A y-axis label for the plot.

    title : str, optional
      A title for the plot.

    boxLabels : bool, optional
        Whether box labels are displayed.  If False, then a colorbar is
        displayed to the right of the box plot.

    prec : int or {'compact','compacthp'}, optional
        Precision for box labels.  Only relevant when boxLabels == True. Allowed
        values are:

        - 'compact' = round to nearest whole number using at most 3 characters
        - 'compacthp' = show as much precision as possible using at most 3 characters
        - int >= 0 = fixed precision given by int
        - int <  0 = number of significant figures given by -int

    mxBasisDimsY : int or list, optional
        Specifies the dimension of the basis along the Y-axis direction
        if and when this is *different* from the X-axis direction.  If
        the two are the same, this parameter can be set to None.


    Returns
    -------
    ReportFigure
    """
    fig, axes = _plt.subplots()
    if size is not None:
        fig.set_size_inches(size[0],size[1])
    else:
        fig.set_size_inches(gateMatrix.shape[1]*0.5,
                            gateMatrix.shape[0]*0.5)

    if title is not None:
        axes.set_title( title, fontsize=fontSize, y=1.3)
          # y argument specified b/c labels are *above* plot

    if xlabel is not None:
        axes.set_xlabel( xlabel, fontsize=fontSize )
    if ylabel is not None:
        axes.set_ylabel( ylabel, fontsize=fontSize )

    def one_sigfig(x):
        if abs(x) < 1e-9: return 0
        if x < 0: return -one_sigfig(-x)
        e = -int(_np.floor(_np.log10(abs(x)))) #exponent
        trunc_x = _np.floor(x * 10**e)/ 10**e #truncate decimal to make sure it gets *smaller*
        return round(trunc_x, e) #round to truncation point just to be sure

    cmapFactory = StdColormapFactory('div',vmin=m,vmax=M)
    cmap, norm = cmapFactory.get_cmap(), cmapFactory.get_norm()

    cax = axes.imshow(gateMatrix,interpolation='nearest',cmap=cmap, norm=norm)

    xlabels=[("$%s$" % x) if len(x) else "" \
                 for x in _tools.basis_element_labels(mxBasis,mxBasisDims)]
    ylabels=[("$%s$" % x) if len(x) else "" \
                 for x in _tools.basis_element_labels(mxBasis,mxBasisDimsY)] \
                 if (mxBasisDimsY is not None) else xlabels
    axes.set_xticklabels(xlabels,rotation='vertical')
    axes.set_yticklabels(ylabels)
    axes.set_xticks(_np.arange(gateMatrix.shape[1]))
    axes.set_yticks(_np.arange(gateMatrix.shape[0]))
    axes.set_xticks(_np.arange(gateMatrix.shape[1])+.5, minor=True)
    axes.set_yticks(_np.arange(gateMatrix.shape[0])+.5, minor=True)
    axes.tick_params(which='major', bottom='off', top='off', left='off', right='off', pad=5 )
    axes.xaxis.grid(True, which='minor', linestyle='-',linewidth=1.5)
    axes.yaxis.grid(True, which='minor', linestyle='-',linewidth=1.5)

    axes.xaxis.tick_top() # move xticks to top of plot
    axes.xaxis.set_label_position('top') #move xlabel to top also

    if mxBasis == "pp": #add darker lines at multiples of 4 boxes
        for i in _np.arange(0,gateMatrix.shape[1],4):
            axes.axvline(i-0.5, linestyle='-', linewidth=3, color='k')
        for i in _np.arange(0,gateMatrix.shape[0],4):
            axes.axhline(i-0.5, linestyle='-', linewidth=3, color='k')

    if boxLabels:
        for iy in range(gateMatrix.shape[0]):
            for ix in range(gateMatrix.shape[1]):
                axes.text(ix, iy, _eformat(gateMatrix[iy,ix],prec),
                          horizontalalignment='center',
                          verticalalignment='center',
                          color=besttxtcolor(gateMatrix[iy,ix], cmap, norm))

    else: #display a color bar
        tickVals = [one_sigfig(m), one_sigfig((m+M)/2), one_sigfig(M)]
        cbar = _plt.colorbar(cax,shrink=.75, pad=.1, aspect=18, ticks=tickVals) #pylint: disable=unused-variable

    rptFig = _ReportFigure(axes)

    if save_to is not None:
        if len(save_to) > 0: #So you can pass save_to="" and figure will be closed but not saved to a file
            _plt.savefig(save_to, bbox_extra_artists=(axes,), bbox_inches='tight')
        if fig is not None: _plt.close(fig) #close the figure if we're saving it to a file

    return rptFig


#Unused -- too little logic to warrant it's own function...
#def gate_matrix_errgen_boxplot(gateMatrix, targetMatrix, size=None,
#                               save_to=None, fontSize=20, mxBasis=None,
#                               mxBasisDims=None, xlabel=None, ylabel=None,
#                               title=None, boxLabels=False, prec=0):
#    """
#    Creates a color box plot of a the error generator of a gate matrix.
#
#    The error generator is given by log( inv(targetMatrix) * gateMatrix ).
#    This can be a useful way to display large matrices which have so many
#    entries that their entries cannot easily fit within the width of a page.
#
#    Parameters
#    ----------
#    gateMatrix : ndarray
#      The gate matrix data used when constructing the generator.
#
#    targetMatrix : ndarray
#      The target gate matrix data to use when constructing the the
#      generator.
#
#    size : tuple, optional
#      The (width,height) figure size in inches.  None
#      enables automatic calculation based on gateMatrix
#      size.
#
#    save_to : str, optional
#      save figure as this filename (usually ending in .pdf)
#
#    fontSize : int, optional
#        size of font for title
#
#    mxBasis : str, optional
#      The name abbreviation for the basis. Typically in {"pp","gm","std","qt"}.
#      Used to label the rows & columns.  If you don't want labels, leave as
#      None.
#
#    mxBasisDims : int or list, optional
#      The dimension of the density matrix space this basis spans, or a
#      list specifying the dimensions of terms in a direct-sum
#      decomposition of the density matrix space.  Used to label the
#      rows & columns.  If you don't want labels, leave as None.
#
#    xlabel : str, optional
#      An x-axis label for the plot.
#
#    ylabel : str, optional
#      A y-axis label for the plot.
#
#    title : str, optional
#      A title for the plot.
#
#    boxLabels : bool, optional
#        Whether box labels are displayed.  If False, then a colorbar is
#        displayed to the right of the box plot.
#
#    prec : int or {'compact','compacthp'}, optional
#        Precision for box labels.  Only relevant when boxLabels == True. Allowed
#        values are:
#
#        - 'compact' = round to nearest whole number using at most 3 characters
#        - 'compacthp' = show as much precision as possible using at most 3 characters
#        - int >= 0 = fixed precision given by int
#        - int <  0 = number of significant figures given by -int
#
#
#    Returns
#    -------
#    ReportFigure
#    """
#    errgen = _tools.error_generator(gateMatrix, targetMatrix)
#    absMax = _np.max(_np.abs(errgen))
#    m,M = -absMax, absMax
#    return gate_matrix_boxplot(errgen, size, m,M, save_to, fontSize,
#                               mxBasis, mxBasisDims, xlabel, ylabel, title,
#                               boxLabels, prec)


def polar_eigenval_plot(gate, targetGate, size=(4,4), title=None,
                        save_to=None, fontSize=20, showNormal=True,
                        showRelative=True):
    """
    Creates a color box plot of a the error generator of a gate matrix.

    The error generator is given by log( inv(targetMatrix) * gateMatrix ).
    This can be a useful way to display large matrices which have so many
    entries that their entries cannot easily fit within the width of a page.

    Parameters
    ----------
    gate : ndarray
      The gate matrix data used when constructing the generator.

    targetGate : ndarray
      The target gate matrix data to use when constructing the the
      generator.

    size : tuple, optional
      The (width,height) figure size in inches.

    title : str, optional
      A title for the plot.

    save_to : str, optional
      save figure as this filename (usually ending in .pdf)

    fontSize : int, optional
      size of font for title

    showNormal : bool, optional
      whether to display the actual eigenvalues of the gate
      and the target gate on the plot.

    showRelative : bool, optional
      whether to display the relative eigenvalues of the gate
      relative to the target gate on the plot.

    Returns
    -------
    ReportFigure
    """
    evals = _np.linalg.eigvals(gate)
    target_evals = _np.linalg.eigvals(targetGate)
    rel_gate = _np.dot(_np.linalg.inv(targetGate), gate)
    rel_evals = _np.linalg.eigvals(rel_gate)
    rel_evals10 = rel_evals**10

    fig = _plt.figure()
    axes = fig.add_axes([0,0,1,1], polar=True, axisbg='#F8F8F8')
    if size is not None:
        fig.set_size_inches(size[0],size[1])

    if title is not None:
        #axes.set_title( title, fontsize=fontSize )
        axes.text( _np.pi,0.0, title, fontsize=fontSize, ha='center' )

    if showNormal:
        r = _np.absolute(target_evals)
        theta = _np.angle(target_evals)
        axes.plot(theta, r, linestyle='None', marker='o', color='k', markersize=8)

        r = _np.absolute(evals)
        theta = _np.angle(evals)
        axes.plot(theta, r, linestyle='None', marker='o', color='c', markersize=8)

    if showRelative:
        r = _np.absolute(rel_evals10)
        theta = _np.angle(rel_evals10)
        axes.plot(theta, r, linestyle='None', marker='o', color='g', markersize=5)

        r = _np.absolute(rel_evals)
        theta = _np.angle(rel_evals)
        axes.plot(theta, r, linestyle='None', marker='o', color='r', markersize=5)

    axes.grid(True)
    axes.set_rmax(1.25)
    axes.set_yticks([0.5,1.0])
    axes.set_theta_zero_location('N')
    axes.set_rlabel_position(135)

    rptFig = _ReportFigure(axes)

    if save_to is not None:
        if len(save_to) > 0: #So you can pass save_to="" and figure will be closed but not saved to a file
            _plt.savefig(save_to, bbox_extra_artists=(axes,), bbox_inches='tight')
        if fig is not None: _plt.close(fig) #close the figure if we're saving it to a file

    return rptFig



def errgen_projection_boxplot(projections, projection_basis, m=None, M=None,
                              size=None, title=None, save_to=None, fontSize=15,
                              boxLabels=False, prec="compacthp"):
    """
    Creates a color box plot displaying the projections of a gate error
    generator onto generators corresponding to a set of standard error
    generators constructed from the given basis.  Typically `projections`
    is obtained by calling :func:`std_errgen_projections`.

    Parameters
    ----------
    projections : ndarray
      A 1-dimensional array of length equal to the numer of elements in
      the given basis (usually equal to the gate dimension).  Ordering of
      the values is assumed to correspond to the ordering given by the 
      routines in `pygsti.tools`, (e.g. :func:`pp_matrices` when 
      `projection_basis` equals "pp").

    projection_basis : {'std', 'gm', 'pp', 'qt'}
      The basis is used to construct the error generators onto which
      the gate  error generator is projected.  Allowed values are
      Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp) and Qutrit (qt).

    m,M : float, optional
      Color scale min and max values, respectivey.  If None, then computed
      automatically from the data range.

    size : tuple, optional
      The (width,height) figure size in inches.  None
      enables automatic calculation based on gateMatrix
      size.

    title : str, optional
      A title for the plot.

    save_to : str, optional
      save figure as this filename (usually ending in .pdf)

    fontSize : int, optional
        size of font for title

    boxLabels : bool, optional
        Whether box labels are displayed.  If False, then a colorbar is
        displayed to the right of the box plot.

    prec : int or {'compact','compacthp'}, optional
        Precision for box labels.  Only relevant when boxLabels == True. Allowed
        values are:

        - 'compact' = round to nearest whole number using at most 3 characters
        - 'compacthp' = show as much precision as possible using at most 3 characters
        - int >= 0 = fixed precision given by int
        - int <  0 = number of significant figures given by -int


    Returns
    -------
    ReportFigure
    """
    absMax = _np.max(_np.abs(projections))
    if m is None: m = -absMax
    if M is None: M =  absMax

    d2 = len(projections) # number of projections == dim of gate
    d = int(_np.sqrt(d2)) # dim of density matrix
    nQubits = _np.log2(d)

    if not _np.isclose(round(nQubits),nQubits):
        #Non-integral # of qubits, so just show as a single row
        projections = projections.reshape( (1,projections.size) )
        xlabel = ""; ylabel = ""        
    elif nQubits == 1:
        projections = projections.reshape( (1,4) )
        xlabel = "Q1"; ylabel = ""
    elif nQubits == 2:
        projections = projections.reshape( (4,4) )
        xlabel = "Q2"; ylabel="Q1"
    else:
        projections = projections.reshape( (4,projections.size/4) )
        xlabel = "Q*"; ylabel="Q1"

    xd = int(round(_np.sqrt(projections.shape[1]))) #x-basis-dim
    yd = int(round(_np.sqrt(projections.shape[0]))) #y-basis-dim
    return gate_matrix_boxplot(projections, size, m,M, save_to, fontSize,
                               projection_basis, xd, xlabel, ylabel, title, boxLabels,
                               prec, yd)



def choi_eigenvalue_barplot(evals, errbars=None, size=(8,5), barWidth=1,
                            save_to=None, fontSize=15, xlabel="index",
                            ylabel="Re[eigenvalue]", title=None):
    """
    Creates a bar plot showing the real parts of each of the eigenvalues
    given.  This is useful for plotting the eigenvalues of Choi matrices,
    since all elements are positive for a CPTP map.

    Parameters
    ----------
    evals : ndarray
       An array containing the eigenvalues to plot.

    errbars : ndarray, optional
       An array containing the lengths of the error bars
       to place on each bar of the plot.

    size : tuple, optional
      The (width,height) figure size in inches.

    barWidth : float, optional
      The width of the bars in the plot.

    save_to : str, optional
      save figure as this filename (usually ending in .pdf)

    fontSize : int, optional
      size of font for title

    xlabel : str, optional
      An x-axis label for the plot.

    ylabel : str, optional
      A y-axis label for the plot.

    title : str, optional
      A title for the plot.

    Returns
    -------
    ReportFigure
    """
    fig, axes = _plt.subplots()
    fig.set_size_inches(size[0],size[1])

    if title is not None:
        axes.set_title( title, fontsize=fontSize)
    if xlabel is not None:
        axes.set_xlabel( xlabel, fontsize=fontSize )
    if ylabel is not None:
        axes.set_ylabel( ylabel, fontsize=fontSize )

    evals = _np.asarray(evals)
    ind = _np.arange(evals.size)

    if errbars is None:
        pos_evals = _np.maximum(evals.flatten().real,0.0)
        neg_evals = _np.abs(_np.minimum(evals.flatten().real,0.0))
        rects = axes.bar(ind, pos_evals, barWidth, color=(0.5,0.5,0.5)) #pylint: disable=unused-variable
        rects = axes.bar(ind, neg_evals, barWidth, color='r') #pylint: disable=unused-variable
    else:
        evalsEB = _np.asarray(errbars)
        pos_evals = []; pos_err = []
        neg_evals = []; neg_err = []
        for val,eb in zip(evals.flatten().real, evalsEB.flatten().real):
            if (val+eb) < 0.0: #if entire error interval is less than zero
                neg_evals.append(abs(val)); neg_err.append(eb)
                pos_evals.append(0);   pos_err.append(0)
            else:
                pos_evals.append(abs(val)); pos_err.append(eb)
                neg_evals.append(0);   neg_err.append(0)
        rects = axes.bar(ind, pos_evals, barWidth, color=(0.5,0.5,0.5),
                         yerr=pos_err)
        rects = axes.bar(ind, neg_evals, barWidth, color='r',yerr=neg_err)

    axes.set_yscale("log")
    axes.set_xticks(ind + barWidth/2.0)
    axes.set_xticklabels(list(map(str,list(range(len(ind))))))

    rptFig = _ReportFigure(axes)

    if save_to is not None:
        if len(save_to) > 0: #So you can pass save_to="" and figure will be closed but not saved to a file
            _plt.savefig(save_to, bbox_extra_artists=(axes,), bbox_inches='tight')
        if fig is not None: _plt.close(fig) #close the figure if we're saving it to a file

    return rptFig



def _makeHistFilename(mainFilename):
    #Insert "_hist" before extension, e.g. /one/two.txt ==> /one/two_hist.txt
    if len(mainFilename) > 0:
        return "_hist".join(_os.path.splitext(mainFilename))
    else: return "" #keep empty string empty, as this signals not actually saving any files
