Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into tj/core/op/descri…
Browse files Browse the repository at this point in the history
…ptor_Tensor-mutex
  • Loading branch information
t-jankowski committed Apr 25, 2024
2 parents 2af5c41 + 3777479 commit 41e977b
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 56 deletions.
2 changes: 2 additions & 0 deletions src/bindings/python/src/openvino/runtime/opset15/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
# Inlcudes new operators added in Opset15

# TODO (ticket 138273): Add previous opset operators at the end of opset15 development
from openvino.runtime.opset1.ops import parameter
from openvino.runtime.opset15.ops import scatter_nd_update
29 changes: 29 additions & 0 deletions src/bindings/python/src/openvino/runtime/opset15/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,38 @@

"""Factory functions for ops added to openvino opset15."""
from functools import partial
from typing import Optional, Literal

from openvino.runtime import Node, Type
from openvino.runtime.opset_utils import _get_node_factory
from openvino.runtime.utils.decorators import nameable_op
from openvino.runtime.utils.types import NodeInput, as_nodes

_get_node_factory_opset15 = partial(_get_node_factory, "opset15")

# -------------------------------------------- ops ------------------------------------------------


@nameable_op
def scatter_nd_update(
data: NodeInput,
indices: NodeInput,
updates: NodeInput,
reduction: Optional[Literal["none", "sum", "sub", "prod", "min", "max"]] = None,
name: Optional[str] = None,
) -> Node:
"""Return a node which performs ScatterNDUpdate.
:param data: Node input representing the tensor to be updated.
:param indices: Node input representing the indices at which updates will be applied.
:param updates: Node input representing the updates to be applied.
:param reduction: The type of operation to perform on the inputs. One of "none", "sum",
"sub", "prod", "min", "max".
:param name: Optional name for the output node.
:return: New node performing the ScatterNDUpdate.
"""
inputs = as_nodes(data, indices, updates, name=name)
attributes = {}
if reduction:
attributes["reduction"] = reduction
return _get_node_factory_opset15().create("ScatterNDUpdate", inputs, attributes)
179 changes: 123 additions & 56 deletions src/bindings/python/tests/test_graph/test_ops_scatter_nd_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,159 +6,226 @@
import pytest

from openvino import PartialShape, Type
from openvino.runtime import opset4, opset15

import openvino.runtime.opset13 as ov
scatter_version_opset = pytest.mark.parametrize("opset", [opset4, opset15])


def test_scatter_nd_update():
@scatter_version_opset
def test_scatter_nd_update(opset):
data_shape = [4, 4, 4]
indices_shape = [2, 1]
updates_shape = [2, 4, 4]

data_param = ov.parameter(shape=data_shape, dtype=Type.f32, name="data")
indices_param = ov.parameter(shape=indices_shape, dtype=Type.i32, name="indices")
updates_param = ov.parameter(shape=updates_shape, dtype=Type.f32, name="updates")
data_param = opset.parameter(shape=data_shape, dtype=Type.f32, name="data")
indices_param = opset.parameter(shape=indices_shape, dtype=Type.i32, name="indices")
updates_param = opset.parameter(shape=updates_shape, dtype=Type.f32, name="updates")

scatter_nd_node = ov.scatter_nd_update(data_param, indices_param, updates_param)
scatter_nd_node = opset.scatter_nd_update(data_param, indices_param, updates_param)

assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data_shape))
assert scatter_nd_node.get_output_element_type(0) == Type.f32


def test_scatter_nd_update_basic():
@scatter_version_opset
def test_scatter_nd_update_basic(opset):
data = np.array([1, 2, 3, 4, 5])
indices = np.array([[0], [2]])
updates = np.array([9, 10])

result = ov.scatter_nd_update(data, indices, updates)
expected = np.array([9, 2, 10, 4, 5])
np.testing.assert_array_equal(result, expected)
scatter_nd_node = opset.scatter_nd_update(data, indices, updates)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)


def test_scatter_nd_update_multidimensional():
@scatter_version_opset
def test_scatter_nd_update_multidimensional(opset):
data = np.array([[1, 2], [3, 4]])
indices = np.array([[0, 1], [1, 0]])
updates = np.array([9, 10])

result = ov.scatter_nd_update(data, indices, updates)
expected = np.array([[1, 9], [10, 4]])
np.testing.assert_array_equal(result, expected)
scatter_nd_node = opset.scatter_nd_update(data, indices, updates)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)


def test_scatter_nd_update_mismatched_updates_shape():
@scatter_version_opset
def test_scatter_nd_update_mismatched_updates_shape(opset):
data = np.array([1, 2, 3])
indices = np.array([[0], [1]])
updates = np.array([4])

with pytest.raises(RuntimeError):
ov.scatter_nd_update(data, indices, updates)
opset.scatter_nd_update(data, indices, updates)


def test_scatter_nd_update_non_integer_indices():
@scatter_version_opset
def test_scatter_nd_update_non_integer_indices(opset):
data = np.array([1, 2, 3])
indices = np.array([[0.5]])
updates = np.array([4])

with pytest.raises(RuntimeError):
ov.scatter_nd_update(data, indices, updates)
opset.scatter_nd_update(data, indices, updates)


def test_scatter_nd_update_negative_indices():
@scatter_version_opset
def test_scatter_nd_update_negative_indices(opset):
data = np.array([1, 2, 3, 4])
indices = np.array([[-1]])
updates = np.array([5])

result = ov.scatter_nd_update(data, indices, updates)
expected = np.array([1, 2, 3, 5])
np.testing.assert_array_equal(result, expected)
scatter_nd_node = opset.scatter_nd_update(data, indices, updates)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)


def test_scatter_nd_update_multi_index_per_update():
@scatter_version_opset
def test_scatter_nd_update_multi_index_per_update(opset):
data = np.array([[1, 2], [3, 4]])
indices = np.array([[0, 0], [0, 1]])
updates = np.array([5, 6])

result = ov.scatter_nd_update(data, indices, updates)
expected = np.array([[5, 6], [3, 4]])
np.testing.assert_array_equal(result, expected)
scatter_nd_node = opset.scatter_nd_update(data, indices, updates)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)


def test_scatter_nd_update_non_contiguous_indices():
@scatter_version_opset
def test_scatter_nd_update_non_contiguous_indices(opset):
data = np.array([10, 20, 30, 40, 50])
indices = np.array([[0], [3]])
updates = np.array([100, 400])

result = ov.scatter_nd_update(data, indices, updates)
expected = np.array([100, 20, 30, 400, 50])
np.testing.assert_array_equal(result, expected)
scatter_nd_node = opset.scatter_nd_update(data, indices, updates)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)


def test_scatter_nd_update_large_updates():
@scatter_version_opset
def test_scatter_nd_update_large_updates(opset):
data = np.zeros(1000, dtype=np.float64)
indices = np.reshape(np.arange(1000), (-1, 1))
updates = np.arange(1000, dtype=np.float64)

result = ov.scatter_nd_update(data, indices, updates)
expected = np.arange(1000, dtype=np.float64)
np.testing.assert_array_equal(result, expected)
scatter_nd_node = opset.scatter_nd_update(data, indices, updates)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)


def test_scatter_nd_update_overlapping_indices():
@scatter_version_opset
def test_scatter_nd_update_opseterlapping_indices(opset):
data = np.array([1, 2, 3, 4, 5])
indices = np.array([[1], [1], [3]])
updates = np.array([10, 20, 30])

result = ov.scatter_nd_update(data, indices, updates)
expected = np.array([1, 20, 3, 30, 5])
np.testing.assert_array_equal(result, expected)
scatter_nd_node = opset.scatter_nd_update(data, indices, updates)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)


def test_scatter_nd_update_3d_data():
@scatter_version_opset
def test_scatter_nd_update_3d_data(opset):
data = np.zeros((2, 2, 2), dtype=np.float64)
indices = np.array([[0, 0, 1], [1, 1, 0]])
updates = np.array([1, 2], dtype=np.float64)

result = ov.scatter_nd_update(data, indices, updates)
expected = np.array([[[0, 1], [0, 0]], [[0, 0], [2, 0]]], dtype=np.float64)
np.testing.assert_array_equal(result, expected)
scatter_nd_node = opset.scatter_nd_update(data, indices, updates)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)


def test_scatter_nd_update_all_indices():
@scatter_version_opset
def test_scatter_nd_update_all_indices(opset):
data = np.ones((2, 3), dtype=np.float64)
indices = np.array([[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]])
updates = np.array([10, 20, 30, 40, 50, 60], dtype=np.float64)

result = ov.scatter_nd_update(data, indices, updates)
expected = np.array([[10, 20, 30], [40, 50, 60]], dtype=np.float64)
np.testing.assert_array_equal(result, expected)
scatter_nd_node = opset.scatter_nd_update(data, indices, updates)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)


def test_scatter_nd_update_invalid_updates_shape():
@scatter_version_opset
def test_scatter_nd_update_invalid_updates_shape(opset):
data = np.array([1, 2, 3, 4])
indices = np.array([[1], [2]])
updates = np.array([5])

with pytest.raises(RuntimeError):
ov.scatter_nd_update(data, indices, updates)
opset.scatter_nd_update(data, indices, updates)


def test_scatter_nd_update_negative_updates():
@scatter_version_opset
def test_scatter_nd_update_negative_updates(opset):
data = np.array([1, 2, 3, 4, 5])
indices = np.array([[1], [3]])
updates = np.array([-1, -2])

result = ov.scatter_nd_update(data, indices, updates)
expected = np.array([1, -1, 3, -2, 5])
np.testing.assert_array_equal(result, expected)
scatter_nd_node = opset.scatter_nd_update(data, indices, updates)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)


def test_scatter_nd_update_empty_indices_and_updates():
@scatter_version_opset
def test_scatter_nd_update_empty_indices_and_updates(opset):
data = np.array([1, 2, 3], dtype=np.float64)
indices = np.array([], dtype=np.int64).reshape(0, 1)
updates = np.array([], dtype=np.float64)

result = ov.scatter_nd_update(data, indices, updates)
expected = np.array([1, 2, 3], dtype=np.float64)
np.testing.assert_array_equal(result, expected)
scatter_nd_node = opset.scatter_nd_update(data, indices, updates)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)


@pytest.mark.parametrize(
"reduction",
[
None,
"none",
"sUm",
"SUB",
"pRod",
"miN",
"Max",
],
)
def test_scatter_nd_update_reduction(reduction):
data = np.array([1, 2, 3, 4, 5])
indices = np.array([[0], [2]])
updates = np.array([9, 10])

scatter_nd_node = opset15.scatter_nd_update(data, indices, updates, reduction)
assert scatter_nd_node.get_type_name() == "ScatterNDUpdate"
op_attrs = scatter_nd_node.get_attributes()
if reduction is None:
assert op_attrs["reduction"] == "none"
else:
assert op_attrs["reduction"] == reduction.lower()
assert scatter_nd_node.get_output_size() == 1
assert scatter_nd_node.get_output_partial_shape(0).same_scheme(PartialShape(data.shape))
assert scatter_nd_node.get_output_element_type(0) == Type(data.dtype)

0 comments on commit 41e977b

Please sign in to comment.