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
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)
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)
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
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)
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()
#%% 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])
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
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)
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)
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
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)])
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
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