Source code for coremltools.converters.mil.mil.ops.defs.iOS17.scatter_gather

#  Copyright (c) 2023, Apple Inc. All rights reserved.
#
#  Use of this source code is governed by a BSD-3-clause license that can be
#  found in the LICENSE.txt file or at https://opensource.org/licenses/BSD-3-Clause

import numpy as np

from coremltools.converters.mil.mil import types
from coremltools.converters.mil.mil.input_type import DefaultInputs, InputSpec, TensorInputType
from coremltools.converters.mil.mil.ops.defs._op_reqs import register_op
from coremltools.converters.mil.mil.ops.defs.iOS15.scatter_gather import scatter as _scatter_iOS15
from coremltools.converters.mil.mil.ops.defs.iOS15.scatter_gather import (
    scatter_along_axis as _scatter_along_axis_iOS15,
)
from coremltools.converters.mil.mil.ops.defs.iOS15.scatter_gather import (
    scatter_nd as _scatter_nd_iOS15,
)
from coremltools.converters.mil.mil.ops.defs.iOS16.scatter_gather import gather as _gather_iOS16
from coremltools.converters.mil.mil.ops.defs.iOS16.scatter_gather import (
    gather_along_axis as _gather_along_axis_iOS16,
)
from coremltools.converters.mil.mil.ops.defs.iOS16.scatter_gather import (
    gather_nd as _gather_nd_iOS16,
)
from coremltools.converters.mil.mil.ops.defs.iOS17 import _IOS17_TARGET


[docs]@register_op(opset_version=_IOS17_TARGET) class scatter(_scatter_iOS15): """ Scatter ``updates`` to ``data`` at locations ``indices`` at dimension ``axis`` by the operation ``mode``. This section documents only the differences between this version and the iOS 15 :py:class:`~.iOS15.scatter_gather.scatter`. The major differences are as follows: - Input parameter ``indices`` now supports only positive values -- negative values are considered out-of-bound. If support for negative indices is required, they must be explicitly converted to positive values using the following:: index = iOS17.select(index >= 0, index, index + max_index) - New input parameter called ``validate_indices`` has been added to all scatter ops. Its behavior is as follows: - If ``True``, it raises a runtime (possibly also a compile-time) exception for out-of-bound values of the ``indices`` parameter. - If ``False``, absolutely no checking is performed for out-of-bound values of ``indices`` either at compile or runtime. Behavior for out-of-bound indices is undefined but memory safe. Parameters ---------- data: tensor<\*D, T> (Required) indices: tensor<[C], i32> (Required) * 1-D tensor. updates: tensor<\*K, T> (Required) * ``K = data.shape[:axis] + [len(indices)] + data.shape[axis+1:]``. axis: const i32 (Optional) * Default to ``0``. mode: const string (Optional) * Can be the following modes: ``add``, ``div``, ``max``, ``min``, ``mul``, ``sub``, ``update``. * Default value is ``update``. validate_indices: const bool (Optional) * If ``True``, it raises a runtime (possibly also a compile-time) exception for out-of-bound values of the ``indices`` parameter. * If ``False``, absolutely no checking is performed for out-of-bound values of ``indices`` either at compile or runtime. Behavior for out-of-bound indices is undefined but memory safe. * Default value is ``False``. Returns ------- tensor<\*D, T> * With the same type and shape as input ``x``. Attributes ---------- T: fp16, fp32, i32 """ input_spec = InputSpec( data=TensorInputType(type_domain="T"), indices=TensorInputType(type_domain=types.int32), updates=TensorInputType(type_domain="T"), axis=TensorInputType(const=True, optional=True, type_domain=types.int32), mode=TensorInputType(const=True, optional=True, type_domain=types.str), validate_indices=TensorInputType(const=True, optional=True, type_domain=types.bool), ) def default_inputs(self): return DefaultInputs( axis=0, mode="add", validate_indices=False, ) def type_inference(self): result = super().type_inference() if self.validate_indices.val: indices = self.indices.val if indices is not None: if np.count_nonzero( np.logical_or(indices < 0, indices >= self.data.shape[self.axis.val]) ): raise IndexError( f"Indices is out of bounds for `{self.op_type}` node {self.name}. " f"Expected indices between [0, {self.data.shape[self.axis.val]}), but got {indices}." ) return result
[docs]@register_op(opset_version=_IOS17_TARGET) class scatter_along_axis(_scatter_along_axis_iOS15): """ Scatter ``updates`` to ``data`` at locations ``indices`` along ``axis`` dimension using the ``mode`` operation. The major differences from the previous version are illustrated in :py:class:`scatter`. For more information, see the iOS 15 :py:class:`~.iOS15.scatter_gather.scatter_along_axis`. Parameters ---------- data: tensor<\*D, T> (Required) indices: tensor<\*K, i32> (Required) * ``rank(indices) == rank(data)``. updates: tensor<\*K, T> (Required) * Must be the same shape as ``indices``. axis: const i32 (Optional) * Default to ``0``. mode: const string (Optional) * Default to ``add``. * Can be the following modes: ``add``, ``div``, ``max``, ``min``, ``mul``, ``sub``, ``update``. validate_indices: const bool (Optional) * If ``True``, it raises a runtime (possibly also a compile-time) exception for out-of-bound values of the ``indices`` parameter. * If ``False``, absolutely no checking is performed for out-of-bound values of ``indices`` either at compile or runtime. Behavior for out-of-bound indices is undefined but memory safe. * Default value is ``False``. Returns ------- tensor<\*D, T> * With the same type and shape as input ``x``. Attributes ---------- T: fp16, fp32, i32 """ input_spec = InputSpec( data=TensorInputType(type_domain="T"), indices=TensorInputType(type_domain=types.int32), updates=TensorInputType(type_domain="T"), axis=TensorInputType(const=True, optional=True, type_domain=types.int32), mode=TensorInputType(const=True, optional=True, type_domain=types.str), validate_indices=TensorInputType(const=True, optional=True, type_domain=types.bool), ) def default_inputs(self): return DefaultInputs( axis=0, mode="add", validate_indices=False, ) def type_inference(self): result = super().type_inference() if self.validate_indices.val: indices = self.indices.val if indices is not None: if np.count_nonzero( np.logical_or(indices < 0, indices >= self.data.shape[self.axis.val]) ): raise IndexError( f"Indices is out of bounds for `{self.op_type}` node {self.name}. " f"Expected indices between [0, {self.data.shape[self.axis.val]}), but got {indices}." ) return result
[docs]@register_op(opset_version=_IOS17_TARGET) class scatter_nd(_scatter_nd_iOS15): """ Scatter ``updates`` to ``data`` at locations ``indices``. The major differences from the previous version are illustrated in :py:class:`scatter`. For more information, see the iOS 15 :py:class:`~.iOS15.scatter_gather.scatter_nd`. Parameters ---------- data: tensor<\*D, T> (Required) indices: tensor<\*K, i32> (Required) updates: tensor<\*K, T> (Required) * Must be the shape as ``K[:-1]+data.shape[K[-1]:]``. mode: const string (Optional) * Default to ``add``. * Can be the following modes: ``add``, ``div``, ``max``, ``min``, ``mul``, ``sub``, ``update``. validate_indices: const bool (Optional) * If ``True``, it raises a runtime (possibly also a compile-time) exception for out-of-bound values of the ``indices`` parameter. * If ``False``, absolutely no checking is performed for out-of-bound values of ``indices`` either at compile or runtime. Behavior for out-of-bound indices is undefined but memory safe. * Default value is ``False``. Returns ------- tensor<\*D, T> * A tensor with the same shape and type as ``data``. Attributes ---------- T: fp16, fp32, i32 """ input_spec = InputSpec( data=TensorInputType(type_domain="T"), indices=TensorInputType(type_domain=types.int32), updates=TensorInputType(type_domain="T"), mode=TensorInputType(const=True, optional=True, type_domain=types.str), validate_indices=TensorInputType(const=True, optional=True, type_domain=types.bool), ) def default_inputs(self): return DefaultInputs( mode="add", validate_indices=False, ) def type_inference(self): result = super().type_inference() if self.validate_indices.val: indices = self.indices.val upper_bound = self.data.shape if indices is not None: if np.count_nonzero(np.logical_or(indices < 0, indices >= upper_bound)): raise IndexError( f"Indices is out of bounds for `{self.op_type}` node {self.name}. " f"Expected indices between [0, {upper_bound}), but got {indices}." ) return result
[docs]@register_op(opset_version=_IOS17_TARGET) class gather(_gather_iOS16): """ Gather slices from input ``x`` along dimension ``axis`` according to ``indices``, similar to `tf.gather_nd <https://www.tensorflow.org/api_docs/python/tf/gather_nd>`_. This section documents only the differences between this version and the iOS 16 :py:class:`~.iOS16.scatter_gather.gather`. The major differences are as follows: - Input parameter ``x`` adds support for ``int16``, ``uint16``, ``int8``, and ``uint8``. - Input parameter ``indices`` adds support for ``int8`` and ``uint8``. - Input parameter ``indices`` now supports only positive values -- negative values are considered out-of-bound. If support for negative indices is required, they must be explicitly converted to positive values, using the following:: index = iOS17.select(index >= 0, index, index + max_index) - New input parameter called ``validate_indices`` has been added to all gather ops. Its behavior is as follows: - If ``True``, it raises a runtime (possibly also a compile-time) exception for out-of-bound values of the ``indices`` parameter. - If ``False``, absolutely no checking is performed for out-of-bound values of ``indices`` either at compile or runtime. Behavior for out-of-bound indices is undefined but memory safe. Parameters ---------- x: tensor<\*D, T> (Required) indices: tensor<\*N, I> (Required) * Indices values may be negative. More precisely, ``-D[axis]<= v < D[axis]`` for ``v`` in ``indices``. axis: const i32 (Optional. Default=``0``) * Negative axis is supported. batch_dims: const i32 (Optional. Default=``0``) * The number of batch dimensions. validate_indices: const bool (Optional) * If ``True``, it raises a runtime (possibly also a compile-time) exception for out-of-bound values of the ``indices`` parameter. * If ``False``, absolutely no checking is performed for out-of-bound values of ``indices`` either at compile or runtime. Behavior for out-of-bound indices is undefined but memory safe. * Default value is ``False``. Returns ------- tensor<\*K, T> * Where ``K = D[:axis] + N[batch_dims:] + D[axis+1:]``. Attributes ---------- T: fp16, fp32, int32, int16, uint16, int8, uint8 I: int32, int16, uint16, int8, uint8 """ input_spec = InputSpec( x=TensorInputType(type_domain="T"), indices=TensorInputType(type_domain="I"), axis=TensorInputType(const=True, optional=True, type_domain=types.int32), batch_dims=TensorInputType(const=True, optional=True, type_domain=types.int32), validate_indices=TensorInputType(const=True, optional=True, type_domain=types.bool), ) type_domains = { "T": ( types.fp16, types.fp32, types.int32, types.int16, types.uint16, types.int8, types.uint8, ), "I": (types.int32, types.int16, types.uint16, types.int8, types.uint8), } def default_inputs(self): return DefaultInputs(axis=0, batch_dims=0, validate_indices=False) def type_inference(self): result = super().type_inference() if self.validate_indices.val: indices = self.indices.val if indices is not None: if np.count_nonzero( np.logical_or(indices < 0, indices >= self.x.shape[self.axis.val]) ): raise IndexError( f"Indices is out of bounds for `{self.op_type}` node {self.name}. " f"Expected indices between [0, {self.x.shape[self.axis.val]}), but got {indices}." ) return result
[docs]@register_op(opset_version=_IOS17_TARGET) class gather_along_axis(_gather_along_axis_iOS16): """ Take the values along ``axis`` at locations ``indices``. The major differences from the previous version are illustrated in :py:class:`gather`. For more information, see the iOS 16 :py:class:`~.iOS16.scatter_gather.gather_along_axis`. Parameters ---------- x: tensor<\*D, T> (Required) indices: tensor<\*K, I> (Required) * ``rank(indices) == rank(x)``. axis: const i32 (Optional): * Default to ``0``. validate_indices: const bool (Optional) * If ``True``, it raises a runtime (possibly also a compile-time) exception for out-of-bound values of the ``indices`` parameter. * If ``False``, absolutely no checking is performed for out-of-bound values of ``indices`` either at compile or runtime. Behavior for out-of-bound indices is undefined but memory safe. * Default value is ``False``. Returns ------- tensor<\*D, T>: * Output tensor has the same shape as ``indices``. Attributes ---------- T: fp16, fp32, int32, int16, uint16, int8, uint8 I: int32, int16, uint16, int8, uint8 """ input_spec = InputSpec( x=TensorInputType(type_domain="T"), indices=TensorInputType(type_domain="I"), axis=TensorInputType(const=True, optional=True, type_domain=types.int32), validate_indices=TensorInputType(const=True, optional=True, type_domain=types.bool), ) type_domains = { "T": ( types.fp16, types.fp32, types.int32, types.int16, types.uint16, types.int8, types.uint8, ), "I": (types.int32, types.int16, types.uint16, types.int8, types.uint8), } def default_inputs(self): return DefaultInputs( axis=0, validate_indices=False, ) def type_inference(self): result = super().type_inference() if self.validate_indices.val: indices = self.indices.val if indices is not None: upper_bound = self.x.shape[self.axis.val] if np.count_nonzero(np.logical_or(indices < 0, indices >= upper_bound)): raise IndexError( f"Indices is out of bounds for `{self.op_type}` node {self.name}. " f"Expected indices between [0, {upper_bound}), but got {indices}." ) return result
[docs]@register_op(opset_version=_IOS17_TARGET) class gather_nd(_gather_nd_iOS16): """ Gather slices from ``x`` according to ``indices``, similar to `tf.gather_nd`. The major differences from the previous version are illustrated in :py:class:`gather`. For more information, see the iOS 16 :py:class:`~.iOS16.scatter_gather.gather_nd`. Parameters ---------- x: tensor<\*D, T> (Required) indices: tensor<\*K, I> (Required) batch_dims: const i32 (Optional. Default=``0``) * The number of batch dimensions. validate_indices: const bool (Optional) * If ``True``, it raises a runtime (possibly also a compile-time) exception for out-of-bound values of the ``indices`` parameter. * If ``False``, absolutely no checking is performed for out-of-bound values of ``indices`` either at compile or runtime. Behavior for out-of-bound indices is undefined but memory safe. * Default value is ``False``. Returns ------- tensor<\*V, T> * ``V = K[:-1] + D[batch_dims + K[-1]:]``, where ``D = x.shape`` and ``K = indices.shape``. Attributes ---------- T: fp16, fp32, int32, int16, uint16, int8, uint8 I: int32, int16, uint16, int8, uint8 """ input_spec = InputSpec( x=TensorInputType(type_domain="T"), indices=TensorInputType(type_domain="I"), batch_dims=TensorInputType(const=True, optional=True, type_domain=types.int32), validate_indices=TensorInputType(const=True, optional=True, type_domain=types.bool), ) type_domains = { "T": ( types.fp16, types.fp32, types.int32, types.int16, types.uint16, types.int8, types.uint8, ), "I": (types.int32, types.int16, types.uint16, types.int8, types.uint8), } def default_inputs(self): return DefaultInputs( batch_dims=0, validate_indices=False, ) def type_inference(self): result = super().type_inference() if self.validate_indices.val: indices = self.indices.val upper_bound = self.x.shape if indices is not None: if np.count_nonzero(np.logical_or(indices < 0, indices >= upper_bound)): raise IndexError( f"Indices is out of bounds for `{self.op_type}` node {self.name}. " f"Expected indices between [0, {upper_bound}), but got {indices}." ) return result