Exemple #1
0
    def __init__(self, evaluate, defaultArg, swpInds, domain, nTrials=1):
        '''
            Args:
                evaluate (function): called at each point with array args/returns of equal length
                defaultArg (ndarray): default value that will be sent to the evaluate function
                swpIndeces (tuple, int): which channels to sweep
                domain (tuple, iterable): the values over which the sweep channels will be swept
        '''
        super().__init__()
        self.evaluate = evaluate
        self.isScalar = np.isscalar(defaultArg)
        if self.isScalar:
            defaultArg = [defaultArg]
        self.defaultArg = np.array(defaultArg, dtype=float)
        self.allDims = len(self.defaultArg)

        self.swpInds = argFlatten(swpInds, typs=tuple)
        self.swpDims = len(self.swpInds)
        self.domain = argFlatten(domain, typs=tuple)
        self.swpShape = tuple(len(dom) for dom in self.domain)
        if len(self.domain) != self.swpDims:
            raise ValueError('domain and swpInds must have the same dimension.' +
                             'Got {} and {}'.format(len(self.domain), len(self.swpInds)))

        self.plotOptions = {'plType': 'curves'}
        self.monitorOptions = {'livePlot': False, 'plotEvery': 1,
                               'stdoutPrint': True, 'runServer': False, 'cmdCtrlPrint': True}

        # Generate actuation sweep grid
        self.cmdGrid = np.array(np.meshgrid(*self.domain)).T
        assert(self.cmdGrid.shape == self.swpShape + (self.swpDims,))

        self.nTrials = nTrials
Exemple #2
0
    def _reparse(self, parseKeys=None):
        ''' Reprocess measured data into parsed data.
            If there is not enough data present, it does nothing.
            If the parser depends on

            Args:
                parseKeys (tuple, str, None): which parsers to recalculate. If None, does all.
                    Execution order depends on addParser calls, not parseKeys

            Returns:
                None
        '''
        if self.data is None:
            return
        if parseKeys is None:
            parseKeys = tuple(self.parse.keys())
        else:
            parseKeys = argFlatten(parseKeys, typs=tuple)

        for pk, pFun in self.parse.items(
        ):  # We're indexing this way to make sure parsing is done in the order of parse attribute, not the order of parseKeys
            if pk not in parseKeys:
                continue
            tempDataMat = np.zeros(self.swpShape)
            for index in np.ndindex(self.swpShape):
                dataOfPt = OrderedDict()
                for datKey, datVal in self.data.items():
                    if np.any(datVal.shape != self.swpShape):
                        logger.warning(
                            'Data member %s is wrong size for reparsing %s. Skipping.',
                            datKey, pk)
                    else:
                        dataOfPt[datKey] = datVal[index]
                try:
                    tempDataMat[index] = pFun(dataOfPt)
                except KeyError:
                    logger.warning(
                        'Parser %s depends on unpresent data. Skipping.', pk)
                    break
            else:
                self.data[pk] = tempDataMat
Exemple #3
0
    def plot(self, slicer=None, tempData=None, index=None, axArr=None, pltKwargs=None):
        ''' Plots

            Much of the behavior to figure out labels and numbers for axes comes from the plotOptions attribute.

            The xKeys and yKeys are keys within this objects **data** dictionary (actuation, measurement, and parsers)
                The total number of plots will be the product of len('xKey') and len('yKey').
                xKeys can be anything, including parsed data members. By default it is the minor actuation variable
                yKeys can also be anything that has scalar elements.
                By default it is everything that is currently present, except xKeys and non-scalars

            When doing line plots in 2D sweeps, the legend does automatic labelling.
                Each line must correspond to an actuation dimension, otherwise it doesn't make sense.
                    This is despite the fact that the xKeys can still be anything.
                Usually, each line corresponds to a particular domain value of the major sweep axis;
                    however, if that is specified as an xKey, the lines will correspond to the minor axis.

            Surface plotting:
                Ignores whatever is in xKeys. The plotting domain is locked to the actuation domain in order to keep a rectangular grid.
                The values indicated in yKeys will become color data.

            Args:
                slicer (tuple, slice): domain slices
                axArr (ndarray), plt.axis): axes to plot on. Equivalent to what is returned by this method
                pltKwargs: passed through to plotting function

            Todo:
                * Graphics caching for 2D line plots
        '''
        global hCurves  # pylint: disable=global-statement
        if index is None or np.all(np.array(index) == 0):
            hCurves = None

        if pltKwargs is None:
            pltKwargs = {}

        # Which data dict to use and its dimensionality
        if tempData is None:
            fullData = self.data
        else:
            fullData = tempData
        if fullData is not None:
            plotDims = list(fullData.values())[0].ndim  # Instead of self.actuDims
        else:
            plotDims = self.actuDims
        assertValidPlotType(self.plotOptions['plType'], plotDims, type(self))
        # Cuts down the domain to the region of interest
        if slicer is None:
            slicer = (slice(None),) * plotDims
        else:
            slicer = argFlatten(slicer, typs=tuple)

        # Figure out what the keys of data are
        actuationKeys = list(self.actuate.keys())
        xKeys = argFlatten(self.plotOptions['xKey'], typs=tuple)
        yKeys = argFlatten(self.plotOptions['yKey'], typs=tuple)
        if len(xKeys) == 0:
            # default is the most minor sweep domain
            xKeys = (actuationKeys[-1], )
        if len(yKeys) == 0:
            # default is all scalar ranges
            for datKey, datVal in fullData.items():
                if (datKey not in xKeys and
                        datKey not in actuationKeys and
                        np.isscalar(datVal.item(0))):
                    yKeys += (datKey, )
        # Check it
        if (len(xKeys) == 0 or len(yKeys) == 0):
            raise ValueError('No axis key specified explicitly or found in self.actuate')
        for k in xKeys + yKeys:
            if k not in fullData.keys():
                raise KeyError(k + ' not found in data keys. ' +
                               'Available data are ' + ', '.join(fullData.keys()))

        # Make grid of axes based on number of pairs of variables
        plotArrShape = np.array([len(yKeys), len(xKeys)])
        if axArr is not None:
            pass
        elif self.plotOptions['axArr'] is not None:
            axArr = self.plotOptions['axArr']
        else:
            _, axArr = plt.subplots(nrows=plotArrShape[0], ncols=plotArrShape[1],
                                    sharex='col',
                                    figsize=(10, plotArrShape[0] * 2.5))  # pylint: disable=unused-variable

        axArr = np.array(axArr)
        # Force into a two dimensional array
        if axArr.ndim == 2:
            pass
        elif axArr.ndim == 1:
            if np.all(plotArrShape == 1):
                axArr = np.expand_dims(axArr, 0)
            elif plotArrShape[0] == 1:
                axArr = np.expand_dims(axArr, 0)
            elif plotArrShape[1] == 1:
                axArr = np.expand_dims(axArr, 1)
        elif axArr.ndim == 0:
            if np.all(plotArrShape == 1):
                axArr = np.expand_dims(np.expand_dims(axArr, 0), 0)
        # Check it
        if np.any(axArr.shape != plotArrShape):
            raise ValueError('Shape of axArray does not match plotArrShape')

        # Prepare options for plotting that do not depend on index or line no.
        sample_xK = xKeys[0]
        sample_xData = fullData[sample_xK][slicer]
        if self.plotOptions['plType'] == 'curves':
            pltArgs = ('.-', )
            if plotDims == 1:
                if hCurves is None:
                    hCurves = np.empty(axArr.shape, dtype=object)
            elif plotDims == 2:
                invertDomainPriority = False
                autoLabeling = (plotDims == self.actuDims)
                if autoLabeling:
                    if actuationKeys[0] != sample_xK:
                        curveKey = actuationKeys[0]
                    else:
                        curveKey = actuationKeys[1]
                        if index is not None:
                            index = index[::-1]
                        invertDomainPriority = True
                nLines = sample_xData.shape[0 if not invertDomainPriority else 1]
                colors = self.plotOptions['cmap-curves'](np.linspace(0, 1, nLines))

        # Loop over axes (i.e. axis key variables) and plot
        for iAx, ax in np.ndenumerate(axArr):
            xK = xKeys[iAx[1]]
            yK = yKeys[iAx[0]]
            # dereference and slice
            xData = fullData[xK][slicer]
            yData = fullData[yK][slicer]

            if self.plotOptions['plType'] == 'curves':
                if plotDims == 1:
                    # slice it
                    if index is not None:
                        xData = xData[:index[0] + 1]
                        yData = yData[:index[0] + 1]
                        ax.cla()
                    curv = ax.plot(xData, yData, *pltArgs, **pltKwargs)
                    # caching the part of the line that has already been drawn
                    if hCurves[iAx] is not None:  # pylint:disable=unsubscriptable-object
                        try:
                            hCurves[iAx][0].remove()
                        except ValueError:
                            # it was probably an old one
                            pass
                    hCurves[iAx] = curv
                elif plotDims == 2:
                    ax.cla()  # no caching, just clear
                    if invertDomainPriority:
                        xData = xData.T
                        yData = yData.T

                    for iLine in range(nLines):
                        # slicing data based on what the line and index are
                        xLine = xData[iLine, :]
                        yLine = yData[iLine, :]
                        if index is None:
                            pass
                        elif iLine < index[-2]:  # these lines are complete
                            pass
                        elif iLine == index[-2]:  # these lines are in-progress
                            xLine = xLine[slice(index[-1] + 1)]
                            yLine = yLine[slice(index[-1] + 1)]
                        elif iLine > index[-2]:  # these have not been started
                            break
                        # line options
                        pltKwargs['color'] = colors[iLine][:3]
                        if autoLabeling:
                            curveValue = self.actuate[curveKey].domain[iLine]
                            pltKwargs['label'] = '{} = {:.2f}'.format(curveKey, curveValue)
                        ax.plot(xLine, yLine, *pltArgs, **pltKwargs)
                    # legend
                    if autoLabeling and iAx[0] == 0 and iAx[1] == plotArrShape[1] - 1:  # AND it is the top right
                        ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
                else:
                    raise ValueError('Too many dimensions in sweep to plot. '
                                     'This should have been caught by assertValidPlotType.')

                if iAx[0] == plotArrShape[0] - 1:
                    ax.set_xlabel(xK)
                else:
                    ax.tick_params(labelbottom=False)
                if iAx[1] == 0:
                    ax.set_ylabel(yK)
                else:
                    ax.tick_params(labelleft=False)
            elif self.plotOptions['plType'] == 'surf':
                # xKeys we treat as meaningless. just use the actuation domains
                # We treat yData as color data
                doms = [None] * 2
                for iDim, actuObj in enumerate(self.actuate.values()):
                    doms[iDim] = actuObj.domain[slicer[iDim]]
                domainGrids = np.meshgrid(*doms[::-1], indexing='xy')
                pltKwargs['cmap'] = pltKwargs.pop('cmap', self.plotOptions['cmap-surf'])
                pltKwargs['shading'] = pltKwargs.pop('shading', 'gouraud')
                cax = ax.pcolormesh(*domainGrids, yData, **pltKwargs)
                plt.gcf().colorbar(cax, ax=ax)
                ax.autoscale(tight=True)
                ax.set_title(yK)
                if iAx[0] == plotArrShape[0] - 1:
                    ax.set_xlabel(actuationKeys[1])
                else:
                    ax.tick_params(labelbottom=False)
                ax.set_ylabel(actuationKeys[0])
        return axArr