Example #1
0
 def func(vlist,
          func,
          v0=None,
          verb=False,
          name=DEF_VECTOR_NAME,
          inv=False):
     '''
     Apply the given function (func) to a list of Vectors (vlist).
     Initial guess (v0) may be set (if is None, then v0=vlist[0])
     For MODE_NP and MODE_SP function should expects input in the form
         vlist[0].x, vlist[1].x,..., vlist[-1].x,
         where args are float or numpy arrays, 
     For MODE_TT function should expects input in the form
         r, where r is a 2-d array 
         (r[0, :] - is corresponding to vlist[0],
          r[1, :] - is corresponding to vlist[1], ...) 
     '''
     if not isinstance(vlist, list) or not isinstance(vlist[0], Vector):
         raise ValueError('Incorrect input.')
     res = vlist[0].copy(copy_x=False)
     res.name = name
     for v in vlist:
         if not isinstance(v, Vector) or res.d != v.d or res.mode != v.mode:
             raise ValueError('Incorrect input.')
     res.tau = _max_tau([v.tau for v in vlist])
     if res.mode == MODE_NP or res.mode == MODE_SP:
         if not inv:
             res.x = func(*[v.x for v in vlist])
         else:
             res.x = 1. / func(*[v.x for v in vlist])
     if res.mode == MODE_TT:
         if verb:
             print '  Construction of %s' % res.name
         if v0 is None:
             v0 = vlist[0].copy()
         if not inv:
             res.x = tt.multifuncrs2([v.x for v in vlist],
                                     func,
                                     res.tau,
                                     verb=verb,
                                     y0=v0.x)
         else:
             res.x = tt.multifuncrs2([v.x for v in vlist],
                                     lambda x: 1. / func(x),
                                     res.tau,
                                     verb=verb,
                                     y0=v0.x)
         res = res.round()
     return res
Example #2
0
def mean_dimension_tensor(t, eps=1e-6, verbose=False, **kwargs):
    """
    Given a TT set t, return another that maps each tuple to the mean dimension of t restricted to the tuple

    :param t: a 2^N TT
    :return:

    """

    N = t.d

    ct = tr.core.to_superset(t)
    ct = tt.vector.from_list(
        [core[:, [0, 0, 1], :] for core in tt.vector.to_list(ct)])

    t = tt.vector.from_list(
        [core[:, [0, 1, 1], :] for core in tt.vector.to_list(t)])

    w = tr.core.hamming_weight(N)
    w = tt.vector.from_list(
        [core[:, [0, 1, 1], :] for core in tt.vector.to_list(w)])

    def fun(Xs):
        result = np.zeros(len(Xs))
        idx = np.where(Xs[:, 2] != 0)[0]
        result[idx] = Xs[idx, 0] * Xs[idx, 1] / Xs[idx, 2]
        return result

    t = tt.multifuncrs2([t, w, ct], fun, eps=eps, verb=verbose, **kwargs)
    t = tt.vector.from_list([
        np.concatenate(
            [np.sum(core[:, 0:2, :], axis=1, keepdims=True), core[:, 2:3, :]],
            axis=1) for core in tt.vector.to_list(t)
    ])
    return t.round(eps=0)
Example #3
0
def banzhaf_power_indices(st,
                          threshold=0.5,
                          eps=1e-6,
                          verbose=False,
                          **kwargs):
    """
    Compute all N Banzhaf values for a 2^N tensor: for each n-th variable, it is the proportion of all swing votes in which it is the key vote (i.e. tuples whose closed value exceeds a given threshold). The value of a coalition is its closed value

    :param st: a Sobol TT
    :param threshold: real between 0 and 1 that defines the majority (default: 0.5)
    :param eps: default is 1e-6
    :return: a vector with the N Banzhaf values

    """

    N = st.d
    threshold *= tr.core.sum(st)
    cst = tr.core.to_lower(st)
    cst_masked = tt.multifuncrs2([cst],
                                 lambda x: x >= threshold,
                                 eps=eps,
                                 verb=verbose,
                                 **kwargs)

    result = np.empty(N)
    for i in range(N):
        idx1 = [slice(None)] * N
        idx1[i] = 1
        idx2 = [slice(None)] * N
        idx2[i] = 0
        result[i] = tr.core.sum(
            cst_masked[idx1] *
            (tt.ones(cst_masked[idx2].n) - cst_masked[idx2]))
    return result / np.sum(result)
Example #4
0
def windowed_mean_dimension(wst, mode='cross', eps=1e-6, verbose=False, **kwargs):
    """
    Given a windowed Sobol TT, return a TT with the mean dimension of every window

    :param wst:
    :return:

    """

    assert mode in ('matvec', 'cross')
    N = wst.tt.d

    if verbose:
        print('Computing windowed mean dimension tensor...')
    if mode == 'matvec':
        return tt.matvec(wst, tr.core.hamming_weight(N))
    else:
        wst = wst.tt

        cores = tt.vector.to_list(tr.core.hamming_weight(N))
        for n in range(N):
            cores[n] = cores[n][:, np.concatenate([np.zeros(wst.n[n]//2, dtype=np.int), np.ones(wst.n[n]//2, dtype=np.int)]), :]
        h = tt.vector.from_list(cores)

        wmd = tt.multifuncrs2([wst, h], lambda x: x[:, 0] * x[:, 1], eps=eps, verb=verbose, **kwargs)
        wmd = tt.vector.from_list([core[:, :core.shape[1]//2, :] + core[:, core.shape[1]//2:, :] for core in tt.vector.to_list(wmd)])
        return wmd
Example #5
0
def group_shapley(game, eps=1e-6, verbose=False, **kwargs):
    """
    Given a game, builds a tensor that maps each tuple to its "group Shapley value", i.e. the Shapley value resulting from calling that tuple a single player and leaving everyone else unchanged ("externality-free")

    References:
    - Flores et al., "The Shapley Group Value"
    - Skibski et al., "A Graphical Representation for Games in Partition Function Form"

    """

    N = game.d

    def fun(Xs):
        tcard = np.sum(Xs == 1, axis=1)
        ccard = np.sum(Xs == 2, axis=1)
        return sp.misc.factorial(N - tcard - ccard) * sp.misc.factorial(
            tcard) / sp.misc.factorial(N - ccard + 1)

    # TODO use direct handcrafted automaton
    ws = tr.core.cross(ticks_list=[np.arange(3)] * N,
                       fun=fun,
                       eps=eps,
                       verbose=verbose,
                       **kwargs)
    ws = tt.vector.from_list(
        [core[:, [0, 1, 2, 2], :] for core in tt.vector.to_list(ws)])

    cores = [core[:, [0, 1, 0, 1], :] for core in tt.vector.to_list(game)]
    game = tt.vector.from_list(cores)

    t = tt.multifuncrs2([game, ws],
                        lambda x: x[:, 0] * x[:, 1],
                        eps=eps,
                        verb=verbose,
                        **kwargs)

    add = tt.vector.from_list(
        [core[:, [0, 1, 3], :] for core in tt.vector.to_list(t)])
    sub = tt.vector.from_list(
        [core[:, [0, 1, 2], :] for core in tt.vector.to_list(t)])
    t = add - sub

    t = tt.vector.from_list([
        np.concatenate(
            [np.sum(core[:, 0:2, :], axis=1, keepdims=True), core[:, 2:3, :]],
            axis=1) for core in tt.vector.to_list(t)
    ])
    return t.round(eps=0)
Example #6
0
import sys
sys.path.append("../")
import tt
#import ctypes as ct
#ct.CDLL("libblas.so.3", ct.RTLD_GLOBAL)
#ct.CDLL("libcblas.so.1", ct.RTLD_GLOBAL)
#ct.CDLL("liblapacke.so", ct.RTLD_GLOBAL)
import numpy as np

a = tt.rand([3, 5, 7, 11], 4, [1, 4, 6, 5, 1])
b = tt.rand([3, 5, 7, 11], 4, [1, 2, 4, 3, 1])

c = tt.multifuncrs2([a, b], lambda x: np.sum(x, axis=1), eps=1E-6)

print "Relative error norm:", (c - (a + b)).norm() / (a + b).norm()









Example #7
0
#%%
from __future__ import print_function, absolute_import, division
import numpy as np
import tt
a = tt.rand([3, 5, 7, 11], 4, [1, 4, 6, 5, 1])
b = tt.rand([3, 5, 7, 11], 4, [1, 2, 4, 3, 1])
c = tt.multifuncrs2([a, b], lambda x: np.sum(x, axis=1), eps=1E-6)

print("Relative error norm:", (c - (a + b)).norm() / (a + b).norm())

# %%
a
# %%
a.full().shape
# %%
c
# %%
a = tt.vector(np.array([[[-1,2],[-5,4]],[[-1,2],[-5,4]]]))
def iamafunc(x):
    print("in",x)
    y=np.maximum(x,0)
    print("out",y)
    return y
c = tt.multifuncrs2([a], np.vectorize(iamafunc), eps=1E-6)
# %%
c.full().shape
# %%
def testf(x):
    #print("A",x)
    #print("0",x[:,0])
    #print("1",x[:,1])
Example #8
0
def best_subspace(t,
                  ndim=1,
                  target='max',
                  mode='cross',
                  eps=1e-6,
                  verbose=False,
                  **kwargs):
    """
    Find an axis-aligned subspace of a certain dimensionality that has the highest/lowest variance. Example applications:
    - In visualization, to find interesting subspaces
    - In factor fixing (sensitivity analysis), to find the set of parameters (and their values) that will minimize the uncertainty of a model

    TODO: only uniform independently distributed inputs are supported now

    :param t: a TT
    :param ndim: dimensionality of the subspace sought (default is 2)
    :param target: if 'max' (default), the highest variance will be sought; if 'min', the lowest one
    :param mode: 'cross' (default) or 'kronecker'
    :param verbose:
    :param kwargs: arguments for the cross-approximation
    :return: (a) a list of indices, with slice(None) in the free subspace's dimensions, and (b) the variance of that subspace

    """

    assert mode in ('cross', 'kronecker')
    assert target in ('max', 'min')

    # Build up a tensor that contains variances of all possible subspaces of any dimensionality, using the formula E(X^2) - E(X)^2
    if mode == 'cross':
        cores = tt.vector.to_list(
            tt.multifuncrs2([t],
                            lambda x: x**2,
                            eps=eps,
                            verb=verbose,
                            **kwargs))
    else:
        cores = tt.vector.to_list((t * t).round(0))
    cores = [
        np.concatenate([np.mean(core, axis=1, keepdims=True), core], axis=1)
        for core in cores
    ]
    part1 = tt.vector.from_list(cores)  # E(X^2)

    cores = tt.vector.to_list(t)
    cores = [
        np.concatenate([np.mean(core, axis=1, keepdims=True), core], axis=1)
        for core in cores
    ]
    part2 = tt.vector.from_list(cores)
    if mode == 'cross':
        part2 = tt.multifuncrs2([part2],
                                lambda x: x**2,
                                eps=eps,
                                verb=verbose,
                                **kwargs)
    else:
        part2 = (part2 * part2).round(0)  # E(X)^2

    variances = (part1 - part2).round(0)

    # Filter out encoded subspaces that do not have the target dimensionality
    mask = tt.vector.to_list(tr.core.hamming_eq_mask(t.d, t.d - ndim))
    mask = [
        np.concatenate(
            [core[:, 0:1, :],
             np.repeat(core[:, 1:, :], sh, axis=1)], axis=1)
        for core, sh in zip(mask, t.n)
    ]
    mask = tt.vector.from_list(mask)

    # Find and return the best candidate
    if target == 'max':
        prod = tt.vector.round(variances * mask, eps=eps)
        val, point = tt_min.min_tens(-prod, verb=verbose)
        val = -val
    else:
        shift = -1e3 * tt_min.min_tens(-variances, verb=False, rmax=1)[0]
        variances_shifted = variances - tt.vector.from_list(
            [np.ones([1, sh + 1, 1]) for sh in t.n]) * shift
        val, point = tt_min.min_tens(variances_shifted * mask, verb=verbose)
        val += shift
    nones = np.where(np.array(point) == 0)[0]
    point = [p - 1 for p in point]
    for i in nones:
        point[i] = slice(None)
    return point, val
Example #9
0
def moments(t,
            modes,
            order,
            centered=False,
            normalized=False,
            eps=1e-3,
            verbose=False,
            **kwargs):
    """
    Given an N-dimensional TT and a list of M modes, returns a TT of dimension N - M that contains the k-th order moments along these modes

    :param t: a TT
    :param modes: a list of M integers
    :param order: an integer
    :param centered: if True the moments will be computed about their mean. Default is False
    :param normalized: if True the moments will be divided by sigma^order. Default is False
    :param eps: accuracy for cross-approximation (default is 1e-3)
    :return: a TT of dimension N - M

    """

    N = t.d
    assert np.all(0 <= np.array(modes))
    assert np.all(np.array(modes) < N)
    if not hasattr(modes, '__len__'):
        modes = [modes]
    assert len(modes) == len(set(modes))  # Modes may not be repeated
    assert 1 <= len(modes) <= N

    if centered or normalized:
        central_cores = []
        cores = tt.vector.to_list(t)
        for n in range(N):
            if n in modes:
                central_cores.append(
                    np.repeat(np.mean(cores[n], axis=1, keepdims=True),
                              cores[n].shape[1],
                              axis=1))
            else:
                central_cores.append(cores[n])
        central = t - tt.vector.from_list(central_cores)
    if centered:
        if order == 1:
            moments = copy.deepcopy(central)
        else:
            moments = tt.multifuncrs2([central],
                                      lambda x: x**order,
                                      eps=eps,
                                      verb=verbose,
                                      **kwargs)
    else:
        if order == 1:
            moments = copy.deepcopy(t)
        else:
            moments = tt.multifuncrs2([t],
                                      lambda x: x**order,
                                      eps=eps,
                                      verb=verbose,
                                      **kwargs)
    cores = tt.vector.to_list(moments)
    for mode in modes:
        cores[mode] = np.mean(cores[mode], axis=1, keepdims=True)
    moments = tt.vector.from_list(cores)
    if normalized:
        central = tt.multifuncrs2([central],
                                  lambda x: x**2,
                                  eps=eps,
                                  verb=verbose,
                                  **kwargs)
        cores = tt.vector.to_list(central)
        for mode in modes:
            cores[mode] = np.mean(cores[mode], axis=1, keepdims=True)
        variances = tt.vector.from_list(cores)
        moments = tt.multifuncrs2([moments, variances],
                                  lambda x: x[:, 0] / (x[:, 1]**(order / 2.)),
                                  eps=eps,
                                  verb=verbose,
                                  **kwargs)
    return tr.core.squeeze(moments)
Example #10
0
def sobol_tt(t,
             pdf=None,
             premultiplied=False,
             eps=1e-6,
             verbose=False,
             **kwargs):
    """
    Create a Sobol tensor train, i.e. a 2^N TT tensor that compactly encodes all Sobol' indices of a surrogate according to a pdf. For example, element S_{2} is encoded by
     [0, 1, 0, ..., 0], element S_{12} is encoded by [1, 1, 0, ..., 0], and so on

    :param t: a TT
    :param pdf: a TT containing the joint PDF of the N input variables. It does not need to sum 1. If None (default), independent uniformly distributed variables will be assumed. If a list of marginals (vectors), independent variables will be assumed (i.e. separable PDF -> rank-1 TT)
    :param premultiplied: if False (default), `t` is assumed to encode a TT surrogate or analytical function. If True, it is assumed to be the surrogate times the PDF.
    :param eps: default is 1e-6
    :param verbose:
    :param kwargs: these will be used for the cross-approximation
    :return: a 2^N TT with all Sobol indices (if the PDF is not separable, some may be negative)

    """

    N = t.d
    if hasattr(pdf, '__len__'):
        pdf = tr.core.separable_tt(pdf)
    if pdf is None:
        pdf = tr.core.constant_tt(
            shape=t.n,
            fill=1. / np.prod(t.n))  # A constant function that sums 1

    if premultiplied:
        tpdf = t
    else:
        if np.max(pdf.r) == 1:
            tpdf = t * pdf
        else:
            tpdf = tt.multifuncrs2([t, pdf],
                                   lambda x: x[:, 0] * x[:, 1],
                                   y0=t,
                                   eps=eps,
                                   verb=verbose,
                                   **kwargs)
    t2 = tt.vector.from_list([
        np.concatenate([np.sum(core, axis=1, keepdims=True), core], axis=1)
        for core in tt.vector.to_list(tpdf)
    ])

    pdf2 = tt.vector.from_list([
        np.concatenate([np.sum(core, axis=1, keepdims=True), core], axis=1)
        for core in tt.vector.to_list(pdf)
    ])

    def fun(x):
        x[x[:, 1] == 0, 1] = float('inf')
        result = (x[:, 0]**2 / x[:, 1])
        return result

    start = time.time()
    t_normalized_sq = tt.multifuncrs2([t2, pdf2],
                                      fun,
                                      y0=t2,
                                      eps=eps,
                                      verb=verbose,
                                      **kwargs)
    if verbose:
        print('Squaring completed in {} seconds -- resulting ranks: {}'.format(
            time.time() - start, t_normalized_sq.r))

    sobol = tt.vector.from_list([
        np.concatenate([
            core[:, 0:1, :],
            np.sum(core[:, 1:, :], axis=1, keepdims=True) - core[:, 0:1, :]
        ],
                       axis=1) for core in tt.vector.to_list(t_normalized_sq)
    ])
    sobol *= (1. / (tr.core.sum(sobol) - sobol[[
        0,
    ] * N]))
    correction = tt.vector.from_list([
        np.array([1, 0])[np.newaxis, :, np.newaxis],
    ] * N)  # Set first index to 0 for convenience
    return (sobol - correction * sobol[[
        0,
    ] * N]).round(eps=0)
Example #11
0
def cross(ticks_list,
          fun,
          mode="array",
          qtt=False,
          callback=None,
          return_n_samples=False,
          stats=False,
          eps=1e-3,
          verbose=False,
          **kwargs):
    """
    Create a TT from a function and a list of discretized axes (the ticks). This function is mostly a convenience
    wrapper for ttpy's multifuncrs2

    :param ticks_list: a list of vectors
    :param fun: the black-box procedure
    :param mode: if "parameters", :param: `fun` takes its N inputs as N parameters. If "array" (default), :param: `fun` takes a single input, namely a P x N array, and returns an iterable with P elements. Mode "array" has *much* less overhead, which makes a difference especially with many function evaluations
    :param qtt: if True, QTT indexing is used, i.e. each axis is reshaped to 2 x ... x 2 and then all dimensions interleaved (all axes must have the same number of ticks, a power of 2). Default is False
    :param callback: if not None, this function will be regularly called with a value in [0, 1] that estimates the fraction of the cross-approximation that has been completed. Default is None
    :param return_n_samples: if True, return also the number of samples taken
    :param stats: if True, display an error summary over the acquired samples. Default is False
    :param eps:
    :param verbose:
    :param kwargs: these will be passed to ttpy's multifuncrs2
    :return: a TT, or (TT, n_samples) if return_n_samples is True

    """

    assert mode in ("array", "parameters")

    N = len(ticks_list)
    if qtt:
        I = len(ticks_list[0])
        L = int(np.log2(I))
        if 2**L != int(I):
            raise ValueError(
                'For QTT cross-approximation, the number of ticks must be a power of two along all axes'
            )
        if not all([len(ticks_list[n]) == I for n in range(N)]):
            raise ValueError(
                'For QTT cross-approximation, all axes must have the same number of ticks'
            )
        shape = [2] * (N * L)
    else:
        shape = [len(ticks) for ticks in ticks_list]

    if 'nswp' not in kwargs:
        nswp = 10  # ttpy's default
    else:
        nswp = kwargs['nswp']
    total_calls = nswp * 2 * (N * 3 - 2)
    global n_calls
    n_calls = 0

    def indices_to_coordinates(Xs):
        """
        Map integer indices (tensor entries) to coordinates via a given ticks_list

        :param Xs: a P x N matrix of integers with ndim columns
        :return coordinates: a P x N matrix

        """

        global n_calls
        n_calls += 1
        if callback is not None:
            callback(n_calls / float(total_calls))

        Xs = Xs.astype(int)
        if qtt:
            Xs = tr.core.idx_from_qtt(Xs, I=I)
        result = np.empty(Xs.shape)
        for j in range(N):
            result[:, j] = np.asarray(ticks_list[j])[Xs[:, j]]
        return result

    def check_values(Xs, coordinates, values):
        where = np.where(np.isnan(values))[0]
        if len(where) > 0:
            raise ValueError(
                'NaN detected in cross-approximation: indices = {}, coords = {}'
                .format(Xs[where[0], :], coordinates[where[0], :]))

        where = np.where(np.isinf(values))[0]
        if len(where) > 0:
            raise ValueError(
                'Infinite detected in cross-approximation: indices = {}, coords = {}'
                .format(Xs[where[0], :], coordinates[where[0], :]))

    global n_samples
    n_samples = 0
    if stats:
        all_Xs = []
        all_values = []

    if mode == "parameters":

        def f(Xs):
            global n_samples
            values = []
            coordinates = indices_to_coordinates(Xs)
            for x in coordinates:
                values.append(fun(*x))
            values = np.array(values)
            check_values(Xs, coordinates, values)
            n_samples += len(Xs)
            if stats:
                all_Xs.extend(list(Xs))
                all_values.extend(list(values))
            return values
    elif mode == "array":

        def f(Xs):
            global n_samples
            coordinates = indices_to_coordinates(Xs)
            values = fun(coordinates)
            check_values(Xs, coordinates, values)
            n_samples += len(Xs)
            if stats:
                all_Xs.extend(list(Xs))
                all_values.extend(list(values))
            return values

    grids = tr.core.meshgrid(shape)
    if verbose:
        print("Cross-approximating a {}D function with target error {}...".
              format(N, eps))
        start = time.time()
    result = tt.multifuncrs2(grids, f, eps=eps, verb=verbose, **kwargs)
    if verbose:
        total_time = time.time() - start
        print('Function evaluations: {} in {} seconds (time/evaluation: {})'.
              format(n_samples, total_time, total_time / n_samples))
        print('The resulting tensor has ranks {} and {} elements'.format(
            [r for r in result.r], len(result.core)))
    if stats:
        import matplotlib.pyplot as plt
        all_Xs = np.array(all_Xs)
        all_values = np.array(all_values)
        if len(all_values) > 10000:  # To keep things light
            idx = np.random.choice(len(all_values), 10000, replace=False)
            all_Xs = all_Xs[idx, ...]
            all_values = all_values[idx]
        reco = tr.core.sparse_reco(result, all_Xs)
        n = all_values.size
        norm_diff = np.linalg.norm(all_values - reco)
        eps = norm_diff / np.linalg.norm(all_values)
        rmse = norm_diff / np.sqrt(n)
        psnr = 20 * np.log10(
            (all_values.max() - all_values.min()) / (2 * rmse))
        rsquared = 1 - norm_diff**2 / np.var(all_values)
        fig = plt.figure()
        plt.suptitle('eps = {}, '.format(eps) + 'rmse = {}\n'.format(rmse) +
                     'PSNR = {}, '.format(psnr) + 'R^2 = {}'.format(rsquared))
        fig.add_subplot(121)
        plt.scatter(all_values, reco)
        plt.xlabel('Groundtruth')
        plt.ylabel('Learned')
        line = np.linspace(all_values.min(), all_values.max(), 100)
        plt.plot(line, line, color='black')
        fig.add_subplot(122)
        plt.hist(reco - all_values, 25, facecolor='green', alpha=0.75)
        plt.xlabel('Error')
        plt.ylabel('Count')
        plt.show()
    if return_n_samples:
        return result, n_samples
    return result
Example #12
0
def windowed_sobol_tt(t, ws, mode='same', normalize=True, non_scalar_outputs=(), eps=1e-6, verbose=False, **kwargs):
    """
    Compute a windowed Sobol tensor out of a TT tensor, i.e. a tensor that stores 2^N Sobol tensors for all possible
    rectangular subwindows of a given size. For each axis, entries 0, ... I-1 encode '0's in a regular Sobol tensor, while entries I, ..., 2*I-1 encode the '1's.

    TODO: non-uniform pdfs

    Warning: normalized Sobol indices may become inaccurate over regions of low variance!

    :param t:
    :param ws: window sizes (integer or list of integers). For even window sizes and mode 'same', the center is rounded
    up. For example, the first window of size 2 is [0], the second is [0, 1], the third [1, 2], etc.
    :param mode: 'same' (default) or 'valid'. See scipy.signal.convolve
    :param normalize: if True (default) the indices of each region will sum 1 (i.e. the true Sobol indices). This will
    likely be problematic if the tensor contains windows with little or no variance. If False, the indices will sum
    to the variance in that region
    :param non_scalar_outputs: list of modes (default: empty). Sobol indices get averaged along these modes
    :param eps:
    :param verbose:
    :param kwargs: other args for the cross-approximation
    :return: (wst, normalization):
        - wst: a windowed Sobol tensor, represented as a TT matrix: rows represent window positions, columns Sobol
        indices. Each row sums 1. If `mode` is 'same', it has size 2*I for each dimension. If 'valid', 2*(I-w+1)
        - normalization: a TT with the variance for each window

    """

    N = t.d
    non_scalar_outputs = np.asarray(non_scalar_outputs, dtype=np.int)
    if not hasattr(ws, '__len__'):
        ws = [ws]*N
    ws = np.array(ws)
    ws[non_scalar_outputs] = 1
    assert np.all(ws >= 1)
    assert np.all(ws <= t.n)
    assert mode in ('same', 'valid')

    if verbose:
        print('Computing windowed Sobol tensor with window sizes {}...'.format(ws))
    cores = tt.vector.to_list(t)
    boxes = [None]*N
    weights = [None]*N
    for n in range(N):
        boxes[n] = np.ones(ws[n])[np.newaxis, :, np.newaxis]
        # To normalize the box convolutions
        weights[n] = 1 / scipy.signal.convolve(np.ones([1, cores[n].shape[1], 1]), boxes[n], mode=mode)
        cores[n] = np.concatenate([sp.signal.convolve(cores[n], boxes[n], mode=mode) * weights[n], cores[n]], axis=1)
    t2 = tt.vector.from_list(cores)
    t2 = tt.multifuncrs2([t2], lambda x: x**2, eps=eps, verb=verbose, **kwargs)

    cores = tt.vector.to_list(t2)
    normalization_cores = []
    mean_cores = []
    meancorner_cores = []
    for n in range(N):
        split = t.n[n]
        if mode == 'valid':
            split = split-ws[n]+1
        idx = np.concatenate([np.arange(split), np.arange(split)])
        mean_core = cores[n][:, :split, :]
        mean_core = mean_core[:, idx, :]
        mean_cores.append(mean_core)
        meancorner_core = np.concatenate([cores[n][:, :split, :], np.zeros([cores[n].shape[0], split, cores[n].shape[
            2]])], axis=1)
        meancorner_cores.append(meancorner_core)
        cores[n] = np.concatenate([cores[n][:, :split, :], sp.signal.convolve(cores[n][:, split:, :], boxes[n], mode=mode) * weights[n] - cores[n][:, :split, :]], axis=1)
        normalization_core = cores[n][:, :split, :] + cores[n][:, split:, :]
        normalization_core = normalization_core[:, idx, :]
        normalization_cores.append(normalization_core)
    normalization = tt.vector.from_list(normalization_cores)
    mean = tt.vector.from_list(mean_cores)
    meancorner = tt.vector.from_list(meancorner_cores)
    normalization = tt.vector.round(normalization - mean, eps=1e-14)
    t3 = tt.vector.from_list(cores)
    t3 = (t3-meancorner).round(1e-14)  # Make 0 all indices corresponding to the empty set {}

    if len(non_scalar_outputs) > 0:
        cores = tt.vector.to_list(t3)
        normalization_cores = tt.vector.to_list(normalization)
        for nso in non_scalar_outputs:
            cores[nso] = np.sum(cores[nso][:, :cores[nso].shape[1]//2, :], axis=1, keepdims=True)
            normalization_cores[nso] = np.sum(normalization_cores[nso][:, :normalization_cores[nso].shape[1]//2, :], axis=1, keepdims=True)
        t3 = tt.vector.from_list(cores)
        t3 = tr.core.squeeze(t3, modes=non_scalar_outputs)
        normalization = tt.vector.from_list(normalization_cores)
        normalization = tr.core.squeeze(normalization, modes=non_scalar_outputs)

    if normalize:
        def fun(Xs):  # "Safer" division, but can still run into trouble over flat regions
            result = Xs[:, 0] / (Xs[:, 1] + eps)
            result[result < 0] = 0
            result[result > 1] = 1
            return result
        result = tt.multifuncrs2([t3, normalization], fun, eps=eps, verb=verbose, **kwargs)
    else:
        result = t3

    result = tt.vector.round(result, eps=1e-14)
    result = tt.matrix.from_list([np.transpose(np.reshape(core, [core.shape[0], 2, core.shape[1]//2, core.shape[2]]),
                                               [0, 2, 1, 3]) for core in tt.vector.to_list(result)])
    return result, tt.vector.from_list([c[:, slice(0, c.shape[1]//2), :] for c in tt.vector.to_list(normalization)])
Example #13
0
def query_sobol(st,
                include=(),
                exclude=(),
                min_order=1,
                max_order=None,
                mode='highest',
                index_type='standard',
                eps=1e-6,
                verbose=False,
                **kwargs):
    """Interface to query sensitivity metrics: find a tuple of variables satisfying certain criteria.

    Args:
        st (object): A Sobol TT or a 'metrics' dictionary,
            typically obtained from: metrics = var_metrics(...))
        include (list): List of variables that must appear in the result.
            When `st` is a ttpy vector, it has to be a list of integers with the
            indices of the variables. When `st` is a 'metrics' dictionary, it may
            also be a list of strings with the names of the variables.
        exclude (list): List of variables that must NOT appear in the result.
            When `st` is a ttpy vector, it has to be a list of integers with the
            indices of the variables. When `st` is a 'metrics' dictionary, it may
            also be a list of strings with the names of the variables.
        min_order (int, optional): consider only tuples of this order or above.
            Defaults to 1
        max_order (int, optional): consider only tuples up to this order.
            If None (default), no bound
        mode (str, optional): must be 'highest' or 'lowest'.
            Defaults to 'highest'.
        index_type (str, optional): which Sobol indices or related indices to consider.
            Must be 'standard', 'closed', 'total' or 'superset'. Defaults to 'standard'.
        eps (float, optional): Tolerated relative error. Defaults to 1e-6.
        verbose (bool, optional: Activate verbose mode. Defaults to False.

    Returns:
        (tuple, value): the best variables, and their index

    """
    if isinstance(st, tt.core.vector.vector):
        st = st
        names = None
    elif isinstance(
            st, dict) and '_tr_info' in st and 'stt' in st['_tr_info']['tags']:
        names = [axis[0] for axis in st['_tr_info']['axes']]
        names_idx = dict([(name, i) for i, name in enumerate(names)])
        include = [i if isinstance(i, int) else names_idx[i] for i in include]
        exclude = [i if isinstance(i, int) else names_idx[i] for i in exclude]
        st = st['_tr_info']['st']
    else:
        raise ValueError("'STT' must be a ttpy vector or a 'metrics' object")

    N = st.d
    if max_order is None:
        max_order = N
    if index_type == 'closed':
        st = tr.core.to_lower(st)
    elif index_type == 'total':
        st = tr.core.to_upper(st)
    elif index_type == 'superset':
        st = tr.core.to_superset(st)
    elif index_type != 'standard':
        raise ValueError(
            "index_type must be 'standard', 'closed', 'total' or 'superset'")

    # First mask: tuples that must be included
    mask1 = [np.array([1, 1])[np.newaxis, :, np.newaxis] for n in range(N)]
    for n in include:
        mask1[n] = np.array([0, 1])[np.newaxis, :, np.newaxis]
    mask1 = tt.vector.from_list(mask1)

    # Second mask: tuples that must be excluded
    mask2 = [np.array([1, 1])[np.newaxis, :, np.newaxis] for n in range(N)]
    for n in exclude:
        mask2[n] = np.array([1, 0])[np.newaxis, :, np.newaxis]
    mask2 = tt.vector.from_list(mask2)

    mask12 = mask1 * mask2
    mask12 = mask12 - tr.core.hamming_weight(
        N) * eps  # Tiny gradient to follow

    # Last mask: order bounds
    hws = tr.core.hamming_weight_state(N)
    cores = tt.vector.to_list(hws)
    cores[-1][:, :min_order, :] = 0
    cores[-1][:, max_order + 1:, :] = 0
    cores[-1] = np.sum(cores[-1], axis=1, keepdims=True)
    mask3 = tr.core.squeeze(tt.vector.from_list(cores))
    mask3 = tt.vector.round(mask3, eps=0)

    if mode == 'highest':
        st = tt.multifuncrs2([st, mask3],
                             lambda x: x[:, 0] * x[:, 1],
                             eps=eps,
                             verb=verbose,
                             **kwargs) * mask12
        val, point = tr.core.maximize(st)
    elif mode == 'lowest':  # Shift down by one so that the masks' zeroing-out works as intended
        st = tt.multifuncrs2([st - tr.core.constant_tt(st.n), mask3],
                             lambda x: x[:, 0] * x[:, 1],
                             eps=eps,
                             verb=verbose,
                             **kwargs) * mask12
        val, point = tr.core.minimize(st)
        val += 1  # Shift the value back up
    else:
        raise ValueError("Mode must be either 'highest' or 'lowest'")

    result = list(np.where(point)[0]), val
    if names is not None:
        result = ([names[i] for i in result[0]], val)
    return result
Example #14
0
def search_subspace(t,
                    Xs,
                    Ys,
                    tsq=None,
                    tmin=None,
                    tmax=None,
                    hinv=True,
                    vinv=True,
                    dims=None,
                    eps=1e-3):
    """
    Find a region that bests match a given template (both have to be axis-aligned).

    :param Xs: a list of M ndarrays (each of dimension M) encoding the positions to be searched. Note: non-rectangular regions will be extended to their axis-aligned bounding box
    :param Ys: an ndarray of dimension M containing the values to be searched. Must have the same size as the elements of Xs
    :param hinv: whether to search with invariance to horizontal translation (default: True). If True, the values in Xs must lie between 0 and 1
    :param vinv: whether to search with invariance to vertical translation (default: True). If True, the values in Ys must lie between 0 and 1
    :param dims: the dimensions along which the subspace should be searched. Must be a list with M elements. If None (default), the best tuple of M dimensions will be chosen
    :return: match: the center of the best-matching window; dims: the dimensions along which the best subspace was found; borders: a list of M pairs
     (start, end) with the coordinates for the region

    TODO: support non-invariances
    TODO: support search in 2D and more
    TODO: use masks

    """

    N = t.d
    M = Ys.ndim
    if M != 1:
        raise NotImplementedError("Only 1D search is supported for now")
    if hinv == False or vinv == False:
        raise NotImplementedError
    assert len(Xs) == M
    assert np.all([X.ndim == M for X in Xs])
    if hinv:
        assert np.all([np.all(X >= 0) for X in Xs])
        assert np.all([np.all(X <= 1) for X in Xs])
    if vinv:
        assert np.all(Ys >= 0)
        assert np.all(Ys <= 1)
    if dims is not None:
        assert len(dims) == M

    if dims == None:
        dims = np.arange(N)
    inds = np.argsort(Xs[0])  # Sort the values, in case they are reversed
    Xs[0] = Xs[0][inds]
    Ys = Ys[inds]
    record_value = float('inf')

    for dim in dims:
        Xs_adj = (Xs[0] - np.min(Xs[0]) - 1e-9
                  ) * t.n[dim]  # Xs adjusted to this dimension's grid ticks
        Ys_adj = scipy.interpolate.interp1d(Xs_adj, Ys)(
            np.arange(np.floor(Xs_adj[-1]) + 1))  # Adjusted Ys
        Ys_adj = Ys_adj * (
            tmax -
            tmin) + tmin  # Ys are now mapped to the tensor output's range
        Ys_adj -= np.mean(Ys_adj)  # Vertical invariance

        # Compute the loss tensor: for each position, the squared norm (dot product of itself) of a windowed fiber
        # consisting of the difference between the original fiber (x minus its mean t, for vertical invariance) and
        # the vertically-centered template fiber y (with zero mean). We use the expansion:
        # (x - t - y)*(x - t - y) = x*x - 2*x*t - 2*x*y + 2*t*y + t*t + y*y = x*x - t*t - 2*x*y + y*y
        # where we have used that mean(y) = 0

        if tsq is None:  # This part may be reused by subsequent queries, so we memoize it
            tsq = tt.multifuncrs2([t], lambda x: x**2, eps=eps, verb=False)

        box_filter = np.ones([
            1, Ys_adj.size, 1
        ]) / Ys_adj.size  # To average TT cores along their 2nd dimension

        cores = tt.vector.to_list(tsq)
        cores[dim] = scipy.signal.convolve(cores[dim],
                                           box_filter,
                                           mode='valid')
        tsquarebox = tt.vector.from_list(cores)

        cores = tt.vector.to_list(t)
        cores[dim] = scipy.signal.convolve(cores[dim],
                                           box_filter,
                                           mode='valid')
        tbox = tt.vector.from_list(cores)
        tboxsquare = tt.multifuncrs2([tbox],
                                     lambda x: x**2,
                                     eps=eps,
                                     verb=False)

        cores = tt.vector.to_list(t)
        cores[dim] = scipy.signal.convolve(cores[dim],
                                           np.reshape(Ys_adj[::-1],
                                                      [1, Ys_adj.size, 1]),
                                           mode='valid')
        tconv = tt.vector.from_list(cores)

        tfinal = Ys_adj.size * tsquarebox - Ys_adj.size * tboxsquare - 2 * tconv + np.dot(
            Ys_adj, Ys_adj) * tt.ones(tsquarebox.n)

        # Find an approximate minimum
        value, match = tt_min.min_tens(tfinal, rmax=5, nswp=5, verb=False)

        if value < record_value:
            record_value = value
            record_match = [int(m) for m in match]
            record_match[dim] += len(
                Ys_adj
            ) / 2  # Sum half the window size to get the final focus point
            record_dim = [dim]
            record_borders = [[
                record_match[dim] - len(Ys_adj) / 2,
                record_match[dim] + len(Ys_adj) / 2 - 1
            ]]

    return record_match, record_dim, record_borders