def diagonal(a, offset=0, axis1=0, axis2=1): # pylint: disable=missing-docstring a = asarray(a).data maybe_rank = a.shape.rank if maybe_rank is not None and offset == 0 and ( axis1 == maybe_rank - 2 or axis1 == -2) and (axis2 == maybe_rank - 1 or axis2 == -1): return utils.tensor_to_ndarray(tf.linalg.diag_part(a)) a = moveaxis(utils.tensor_to_ndarray(a), (axis1, axis2), (-2, -1)).data a_shape = tf.shape(a) def _zeros(): # pylint: disable=missing-docstring return (tf.zeros(tf.concat([a_shape[:-1], [0]], 0), dtype=a.dtype), 0) # All zeros since diag_part doesn't handle all possible k (aka offset). # Written this way since cond will run shape inference on both branches, # and diag_part shape inference will fail when offset is out of bounds. a, offset = utils.cond( utils.logical_or( utils.less_equal(offset, -1 * utils.getitem(a_shape, -2)), utils.greater_equal(offset, utils.getitem(a_shape, -1)), ), _zeros, lambda: (a, offset)) a = utils.tensor_to_ndarray(tf.linalg.diag_part(a, k=offset)) return a
def ix_(*args): # pylint: disable=missing-docstring n = len(args) output = [] for i, a in enumerate(args): a = asarray(a).data a_rank = tf.rank(a) a_rank_temp = utils.get_static_value(a_rank) if a_rank_temp is not None: a_rank = a_rank_temp if a_rank != 1: raise ValueError( 'Arguments must be 1-d, got arg {} of rank {}'.format(i, a_rank)) else: tf.debugging.Assert(a_rank == 1, [a_rank]) new_shape = [1] * n new_shape[i] = -1 dtype = a.dtype if dtype == tf.bool: output.append( utils.tensor_to_ndarray(tf.reshape(nonzero(a)[0].data, new_shape))) elif dtype.is_integer: output.append(utils.tensor_to_ndarray(tf.reshape(a, new_shape))) else: raise ValueError( 'Only integer and bool dtypes are supported, got {}'.format(dtype)) return output
def flip(m, axis=None): # pylint: disable=missing-docstring m = asarray(m).data if axis is None: return utils.tensor_to_ndarray(tf.reverse(m, tf.range(tf.rank(m)))) axis = utils._canonicalize_axis(axis, tf.rank(m)) # pylint: disable=protected-access return utils.tensor_to_ndarray(tf.reverse(m, [axis]))
def _reduce(tf_fn, a, axis=None, dtype=None, keepdims=None, promote_int=_TO_INT64, tf_bool_fn=None, preserve_bool=False): """A general reduction function. Args: tf_fn: the TF reduction function. a: the array to be reduced. axis: (optional) the axis along which to do the reduction. If None, all dimensions are reduced. dtype: (optional) the dtype of the result. keepdims: (optional) whether to keep the reduced dimension(s). promote_int: how to promote integer and bool inputs. There are three choices: (1) _TO_INT64: always promote them to int64 or uint64; (2) _TO_FLOAT: always promote them to a float type (determined by dtypes.default_float_type); (3) None: don't promote. tf_bool_fn: (optional) the TF reduction function for bool inputs. It will only be used if `dtype` is explicitly set to `np.bool_` or if `a`'s dtype is `np.bool_` and `preserve_bool` is True. preserve_bool: a flag to control whether to use `tf_bool_fn` if `a`'s dtype is `np.bool_` (some reductions such as np.sum convert bools to integers, while others such as np.max preserve bools. Returns: An ndarray. """ if dtype: dtype = utils.result_type(dtype) if keepdims is None: keepdims = False a = array_creation.asarray(a, dtype=dtype) if ((dtype == np.bool_ or preserve_bool and a.dtype == np.bool_) and tf_bool_fn is not None): return utils.tensor_to_ndarray( tf_bool_fn(input_tensor=a.data, axis=axis, keepdims=keepdims)) if dtype is None: dtype = a.dtype if np.issubdtype(dtype, np.integer) or dtype == np.bool_: if promote_int == _TO_INT64: # If a is an integer/bool type and whose bit width is less than 64, # numpy up-casts it to 64-bit. if dtype == np.bool_: is_signed = True width = 8 # We can use any number here that is less than 64 else: is_signed = np.issubdtype(dtype, np.signedinteger) width = np.iinfo(dtype).bits if width < 64: if is_signed: dtype = np.int64 else: dtype = np.uint64 a = a.astype(dtype) elif promote_int == _TO_FLOAT: a = a.astype(dtypes.default_float_type()) return utils.tensor_to_ndarray( tf_fn(input_tensor=a.data, axis=axis, keepdims=keepdims))
def roll(a, shift, axis=None): # pylint: disable=missing-docstring a = asarray(a).data if axis is not None: return utils.tensor_to_ndarray(tf.roll(a, shift, axis)) # If axis is None, the roll happens as a 1-d tensor. original_shape = tf.shape(a) a = tf.roll(tf.reshape(a, [-1]), shift, 0) return utils.tensor_to_ndarray(tf.reshape(a, original_shape))
def sort(a, axis=-1, kind='quicksort', order=None): # pylint: disable=missing-docstring if kind != 'quicksort': raise ValueError("Only 'quicksort' is supported.") if order is not None: raise ValueError("'order' argument to sort is not supported.") a = array_ops.array(a) if axis is None: result_t = tf.sort(tf.reshape(a.data, [-1]), 0) return utils.tensor_to_ndarray(result_t) else: return utils.tensor_to_ndarray(tf.sort(a.data, axis))
def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=float): """Returns `num` uniformly spread values in a range. Args: start: Start of the interval. Always included in the output. stop: If `endpoint` is true and num > 1, this is included in the output. If `endpoint` is false, `num` + 1 values are sampled in [start, stop] both inclusive and the last value is ignored. num: Number of values to sample. Defaults to 50. endpoint: When to include `stop` in the output. Defaults to true. retstep: Whether to return the step size alongside the output samples. dtype: Optional, defaults to float. The type of the resulting ndarray. Could be a python type, a NumPy type or a TensorFlow `DType`. Returns: An ndarray of output sequence if retstep is False else a 2-tuple of (array, step_size). Raises: ValueError: if `num` is negative. """ # TODO(srbs): Check whether dtype is handled properly. if dtype: dtype = utils.to_tf_type(dtype) start = array(start, copy=False, dtype=dtype) stop = array(stop, copy=False, dtype=dtype) if num == 0: return empty(dtype) if num < 0: raise ValueError( 'Number of samples {} must be non-negative.'.format(num)) step = np_nan if endpoint: result = tf.linspace(start.data, stop.data, num) if num > 1: step = (stop - start) / (num - 1) else: # tf.linspace does not support endpoint=False so we manually handle it # here. if num > 1: step = (stop - start) / num result = tf.linspace(start.data, (stop - step).data, num) else: result = tf.linspace(start.data, stop.data, num) result = utils.maybe_cast(result, dtype) if retstep: return utils.tensor_to_ndarray(result), step else: return utils.tensor_to_ndarray(result)
def sum(a, axis=None, dtype=None, keepdims=None): # pylint: disable=redefined-builtin """Computes sum of all array elements or along specified axes. Args: a: array_like. Could be an ndarray, a Tensor or any object that can be converted to a Tensor using `tf.convert_to_tensor`. axis: Optional 0-d or 1-d array_like. Axes along which to compute sum. If None, returns sum of all elements in array. dtype: Optional. The type of the output array. If None, defaults to the dtype of `a` unless `a` is an integer type with precision less than `int` in which case the output type is `int.` keepdims: If true, retains reduced dimensions with length 1. Returns: An ndarray. """ # TODO(wangpeng): check that we fully match numpy behavior. a = array_creation.asarray(a, dtype=dtype) if dtype is None and tf.as_dtype(a.dtype).is_integer: # If a is an integer type and its precision is less than that of `int`, # the output type will be `int`. output_type = np.promote_types(a.dtype, int) if output_type != a.dtype: a = array_creation.asarray(a, dtype=output_type) return utils.tensor_to_ndarray( tf.reduce_sum(input_tensor=a.data, axis=axis, keepdims=keepdims))
def repeat(a, repeats, axis=None): # pylint: disable=missing-docstring a = asarray(a).data original_shape = a._shape_as_list() # pylint: disable=protected-access # Best effort recovery of the shape. if original_shape is not None and None not in original_shape: if not original_shape: original_shape = (repeats,) else: repeats_np = np.ravel(np.array(repeats)) if repeats_np.size == 1: repeats_np = repeats_np.item() if axis is None: original_shape = (repeats_np * np.prod(original_shape),) else: original_shape[axis] = repeats_np * original_shape[axis] else: if axis is None: original_shape = (repeats_np.sum(),) else: original_shape[axis] = repeats_np.sum() repeats = asarray(repeats).data result = tf.repeat(a, repeats, axis) result.set_shape(original_shape) return utils.tensor_to_ndarray(result)
def _bin_op(tf_fun, a, b, promote=True): if promote: a, b = array_creation._promote_dtype(a, b) else: a = array_creation.asarray(a) b = array_creation.asarray(b) return utils.tensor_to_ndarray(tf_fun(a.data, b.data))
def split(ary, indices_or_sections, axis=0): ary = asarray(ary) if not isinstance(indices_or_sections, six.integer_types): indices_or_sections = _boundaries_to_sizes(ary, indices_or_sections, axis) result = tf.split(ary.data, indices_or_sections, axis=axis) return [utils.tensor_to_ndarray(a) for a in result]
def _bin_op(tf_fun, a, b, promote=True): if promote: a, b = array_ops._promote_dtype(a, b) # pylint: disable=protected-access else: a = array_ops.array(a) b = array_ops.array(b) return utils.tensor_to_ndarray(tf_fun(a.data, b.data))
def vander(x, N=None, increasing=False): # pylint: disable=missing-docstring,invalid-name x = asarray(x).data x_shape = tf.shape(x) N = N or x_shape[0] N_temp = utils.get_static_value(N) # pylint: disable=invalid-name if N_temp is not None: N = N_temp if N < 0: raise ValueError('N must be nonnegative') else: tf.debugging.Assert(N >= 0, [N]) rank = tf.rank(x) rank_temp = utils.get_static_value(rank) if rank_temp is not None: rank = rank_temp if rank != 1: raise ValueError('x must be a one-dimensional array') else: tf.debugging.Assert(rank == 1, [rank]) if increasing: start = 0 limit = N delta = 1 else: start = N - 1 limit = -1 delta = -1 x = tf.expand_dims(x, -1) return utils.tensor_to_ndarray( tf.math.pow(x, tf.cast(tf.range(start, limit, delta), dtype=x.dtype)))
def diag(v, k=0): # pylint: disable=missing-docstring """Raises an error if input is not 1- or 2-d.""" v = asarray(v).data v_rank = tf.rank(v) v.shape.with_rank_at_most(2) # TODO(nareshmodi): Consider a utils.Assert version that will fail during # tracing time if the shape is known. tf.debugging.Assert( utils.logical_or(tf.equal(v_rank, 1), tf.equal(v_rank, 2)), [v_rank]) def _diag(v, k): return utils.cond( tf.equal(tf.size(v), 0), lambda: tf.zeros([abs(k), abs(k)], dtype=v.dtype), lambda: tf.linalg.diag(v, k=k)) def _diag_part(v, k): v_shape = tf.shape(v) v, k = utils.cond( utils.logical_or( utils.less_equal(k, -1 * utils.getitem(v_shape, 0)), utils.greater_equal(k, utils.getitem(v_shape, 1)), ), lambda: (tf.zeros([0, 0], dtype=v.dtype), 0), lambda: (v, k)) result = tf.linalg.diag_part(v, k=k) return result result = utils.cond( tf.equal(v_rank, 1), lambda: _diag(v, k), lambda: _diag_part(v, k)) return utils.tensor_to_ndarray(result)
def clip(a, a_min=None, a_max=None): """Clips array values to lie within a given range. Uses `tf.clip_by_value`. Args: a: array_like. Could be an ndarray, a Tensor or any object that can be converted to a Tensor using `tf.convert_to_tensor`. a_min: array_like. Must be a scalar or a shape that can be broadcast to `a.shape`. At least one of `a_min` or `a_max` should be non-None. a_max: array_like. Must be a scalar or a shape that can be broadcast to `a.shape`. At least one of `a_min` or `a_max` should be non-None. Returns: An ndarray with trimmed values with the same shape and dtype as `a`. Raises: ValueError: if both a_min and a_max are None. """ if a_min is None and a_max is None: raise ValueError('Both a_min and a_max cannot be None.') a = array_creation.asarray(a) # Unlike np.clip, tf.clip_by_value requires both min and max values to be # specified so we set them to the smallest/largest values of the array dtype. if a_min is None: a_min = np_iinfo(a.dtype).min if a_max is None: a_max = np_iinfo(a.dtype).max a_min = array_creation.asarray(a_min, dtype=a.dtype) a_max = array_creation.asarray(a_max, dtype=a.dtype) return utils.tensor_to_ndarray( tf.clip_by_value(a.data, a_min.data, a_max.data))
def prod(a, axis=None, dtype=None, keepdims=None): """Computes the product of elements across dimensions of a tensor. Uses `tf.reduce_prod`. Args: a: array_like. Could be an ndarray, a Tensor or any object that can be converted to a Tensor using `tf.convert_to_tensor`. axis: Optional 0-d or 1-d array_like. Axes along which to compute products. If None, returns product of all elements in array. dtype: Optional. The type of the output array. If None, defaults to the dtype of `a` unless `a` is an integer type with precision less than `int` in which case the output type is `int.` keepdims: If true, retains reduced dimensions with length 1. Returns: An ndarray. """ a = array_creation.asarray(a, dtype=dtype) if dtype is None and tf.as_dtype(a.dtype).is_integer: # If a is an integer type and its precision is less than that of `int`, # the output type will be `int`. output_type = np_promote_types(a.dtype, int) if output_type != a.dtype: a = array_creation.asarray(a, dtype=output_type) return utils.tensor_to_ndarray( tf.reduce_prod(input_tensor=a.data, axis=axis, keepdims=keepdims))
def cumsum(a, axis=None, dtype=None): """Returns cumulative sum of `a` along an axis or the flattened array. Uses `tf.cumsum`. Args: a: array_like. Could be an ndarray, a Tensor or any object that can be converted to a Tensor using `tf.convert_to_tensor`. axis: Optional. Axis along which to compute sums. If None, operation is performed on the flattened array. dtype: Optional. The type of the output array. If None, defaults to the dtype of `a` unless `a` is an integer type with precision less than `int` in which case the output type is `int.` Returns: An ndarray with the same number of elements as `a`. If `axis` is None, the output is a 1-d array, else it has the same shape as `a`. """ a = array_creation.asarray(a, dtype=dtype) if dtype is None and tf.as_dtype(a.dtype).is_integer: # If a is an integer type and its precision is less than that of `int`, # the output type will be `int`. output_type = np.promote_types(a.dtype, int) if output_type != a.dtype: a = array_creation.asarray(a, dtype=output_type) # If axis is None, the input is flattened. if axis is None: a = ravel(a) axis = 0 if axis < 0: axis += a.ndim assert axis >= 0 and axis < a.ndim return utils.tensor_to_ndarray(tf.cumsum(a.data, axis))
def logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None): """Returns `num` values sampled evenly on a log scale. Equivalent to `base ** linspace(start, stop, num, endpoint)`. Args: start: base**start is the start of the output sequence. stop: If `endpoint` is true and num > 1, base ** stop is included in the output. If `endpoint` is false, `num` + 1 values are linearly sampled in [start, stop] both inclusive and the last value is ignored before raising to power of `base`. num: Number of values to sample. Defaults to 50. endpoint: When to include `base ** stop` in the output. Defaults to true. base: Base of the log space. dtype: Optional. Type of the resulting ndarray. Could be a python type, a NumPy type or a TensorFlow `DType`. If not provided, it is figured from input args. """ # TODO(srbs): Check whether dtype is handled properly. if dtype: dtype = utils.to_tf_type(dtype) result = linspace(start, stop, num=num, endpoint=endpoint) result = tf.pow(base, result.data) if dtype: result = utils.maybe_cast(result, dtype) return utils.tensor_to_ndarray(result)
def around(a, decimals=0): a = asarray(a) factor = math.pow(10, decimals) factor = tf.cast(factor, a.dtype) a_t = tf.multiply(a.data, factor) a_t = tf.round(a_t) a_t = tf.math.divide(a_t, factor) return utils.tensor_to_ndarray(a_t)
def _argminmax(fn, a, axis=None): a = array_ops.array(a) if axis is None: # When axis is None numpy flattens the array. a_t = tf.reshape(a.data, [-1]) else: a_t = array_ops.atleast_1d(a).data return utils.tensor_to_ndarray(fn(input=a_t, axis=axis))
def argmin(a, axis=None): a = array_creation.asarray(a) a = atleast_1d(a) if axis is None: # When axis is None numpy flattens the array. a_t = tf.reshape(a.data, [-1]) else: a_t = a.data return utils.tensor_to_ndarray(tf.argmin(input=a_t, axis=axis))
def where(condition, x=None, y=None): """Raises ValueError if exactly one of x or y is not None.""" condition = asarray(condition, dtype=np.bool_) if x is None and y is None: return nonzero(condition) elif x is not None and y is not None: x, y = _promote_dtype(x, y) return utils.tensor_to_ndarray(tf.where(condition.data, x.data, y.data)) raise ValueError('Both x and y must be ndarrays, or both must be None.')
def moveaxis(a, source, destination): # pylint: disable=missing-docstring """Raises ValueError if source, destination not in (-ndim(a), ndim(a)).""" if not source and not destination: return a a = asarray(a).data if isinstance(source, int): source = (source, ) if isinstance(destination, int): destination = (destination, ) a_rank = utils._maybe_static(tf.rank(a)) # pylint: disable=protected-access def _correct_axis(axis, rank): if axis < 0: return axis + rank return axis source = tuple(_correct_axis(axis, a_rank) for axis in source) destination = tuple(_correct_axis(axis, a_rank) for axis in destination) if a.shape.rank is not None: perm = [i for i in range(a_rank) if i not in source] for dest, src in sorted(zip(destination, source)): assert dest <= len(perm) perm.insert(dest, src) else: r = tf.range(a_rank) def _remove_indices(a, b): """Remove indices (`b`) from `a`.""" items = tf.unstack(tf.sort(tf.stack(b)), num=len(b)) i = 0 result = [] for item in items: result.append(a[i:item]) i = item + 1 result.append(a[i:]) return tf.concat(result, 0) minus_sources = _remove_indices(r, source) minus_dest = _remove_indices(r, destination) perm = tf.scatter_nd(tf.expand_dims(minus_dest, 1), minus_sources, [a_rank]) perm = tf.tensor_scatter_nd_update(perm, tf.expand_dims(destination, 1), source) a = tf.transpose(a, perm) return utils.tensor_to_ndarray(a)
def trace(a, offset=0, axis1=0, axis2=1, dtype=None): # pylint: disable=missing-docstring a = array_ops.asarray(a).data if offset == 0: a_shape = a.shape if a_shape.rank is not None: rank = len(a_shape) if (axis1 == -2 or axis1 == rank - 2) and (axis2 == -1 or axis2 == rank - 1): return utils.tensor_to_ndarray(tf.linalg.trace(a)) a_rank = tf.rank(a) if axis1 < 0: axis1 += a_rank if axis2 < 0: axis2 += a_rank minaxis = tf.minimum(axis1, axis2) maxaxis = tf.maximum(axis1, axis2) # Move axes of interest to the end. range_rank = tf.range(a_rank) perm = tf.concat([ range_rank[0:minaxis], range_rank[minaxis + 1:maxaxis], range_rank[maxaxis + 1:], [axis1, axis2] ], axis=0) a = tf.transpose(a, perm) a_shape = tf.shape(a) # All zeros since diag_part doesn't handle all possible k (aka offset). # Written this way since cond will run shape inference on both branches, # and diag_part shape inference will fail when offset is out of bounds. a, offset = utils.cond( utils.logical_or( utils.less_equal(offset, -1 * utils.getitem(a_shape, -2)), utils.greater_equal(offset, utils.getitem(a_shape, -1)), ), lambda: (tf.zeros_like(a), 0), lambda: (a, offset)) a = utils.tensor_to_ndarray(tf.linalg.diag_part(a, k=offset)) return array_ops.sum(a, -1, dtype)
def _comparison(tf_fun, x1, x2, cast_bool_to_int=False): dtype = utils.result_type(x1, x2) # Cast x1 and x2 to the result_type if needed. x1 = array_creation.asarray(x1, dtype=dtype) x2 = array_creation.asarray(x2, dtype=dtype) x1 = x1.data x2 = x2.data if cast_bool_to_int and x1.dtype == tf.bool: x1 = tf.cast(x1, tf.int32) x2 = tf.cast(x2, tf.int32) return utils.tensor_to_ndarray(tf_fun(x1, x2))
def clip(a, a_min, a_max): # pylint: disable=missing-docstring if a_min is None and a_max is None: raise ValueError('Not more than one of `a_min` and `a_max` may be `None`.') if a_min is None: return minimum(a, a_max) elif a_max is None: return maximum(a, a_min) else: a, a_min, a_max = array_ops._promote_dtype(a, a_min, a_max) # pylint: disable=protected-access return utils.tensor_to_ndarray( tf.clip_by_value(*utils.tf_broadcast(a.data, a_min.data, a_max.data)))
def around(a, decimals=0): # pylint: disable=missing-docstring a = asarray(a) dtype = a.dtype factor = math.pow(10, decimals) # Use float as the working dtype instead of a.dtype, because a.dtype can be # integer and `decimals` can be negative. float_dtype = dtypes.default_float_type() a = a.astype(float_dtype).data factor = tf.cast(factor, float_dtype) a = tf.multiply(a, factor) a = tf.round(a) a = tf.math.divide(a, factor) return utils.tensor_to_ndarray(a).astype(dtype)
def cumsum(a, axis=None, dtype=None): # pylint: disable=missing-docstring a = asarray(a, dtype=dtype) if dtype is None: a = _maybe_promote_to_int(a) # If axis is None, the input is flattened. if axis is None: a = ravel(a) axis = 0 elif axis < 0: axis += tf.rank(a.data) return utils.tensor_to_ndarray(tf.cumsum(a.data, axis))
def expand_dims(a, axis): """Expand the shape of an array. Args: a: array_like. Could be an ndarray, a Tensor or any object that can be converted to a Tensor using `tf.convert_to_tensor`. axis: int. axis on which to expand the shape. Returns: An ndarray with the contents and dtype of `a` and shape expanded on axis. """ a = array_creation.asarray(a) return utils.tensor_to_ndarray(tf.expand_dims(a.data, axis=axis))
def array(val, dtype=None, copy=True, ndmin=0): """Creates an ndarray with the contents of val. Args: val: array_like. Could be an ndarray, a Tensor or any object that can be converted to a Tensor using `tf.convert_to_tensor`. dtype: Optional, defaults to dtype of the `val`. The type of the resulting ndarray. Could be a python type, a NumPy type or a TensorFlow `DType`. copy: Determines whether to create a copy of the backing buffer. Since Tensors are immutable, a copy is made only if val is placed on a different device than the current one. Even if `copy` is False, a new Tensor may need to be built to satisfy `dtype` and `ndim`. This is used only if `val` is an ndarray or a Tensor. ndmin: The minimum rank of the returned array. Returns: An ndarray. """ if dtype: dtype = utils.to_tf_type(dtype) if isinstance(val, arrays.ndarray): result_t = val.data else: result_t = val if isinstance(result_t, tf.Tensor): # Copy only if copy=True and a copy would not otherwise be made to satisfy # dtype or ndmin. if (copy and (dtype is None or dtype == utils.array_dtype(val)) and val.ndim >= ndmin): # Note: In eager mode, a copy of `result_t` is made only if it is not on # the context device. result_t = tf.identity(result_t) if not isinstance(result_t, tf.Tensor): # Note: We don't just call tf.convert_to_tensor because, unlike NumPy, # TensorFlow prefers int32 and float32 over int64 and float64. So we compute # the NumPy type of `result_t` and create a tensor of that type instead. if not dtype: dtype = utils.array_dtype(result_t) result_t = tf.convert_to_tensor(result_t) result_t = tf.cast(result_t, dtype=dtype) elif dtype: result_t = utils.maybe_cast(result_t, dtype) ndims = len(result_t.shape) if ndmin > ndims: old_shape = list(result_t.shape) new_shape = [1 for _ in range(ndmin - ndims)] + old_shape result_t = tf.reshape(result_t, new_shape) return utils.tensor_to_ndarray(result_t)