diff --git a/keras/src/backend/openvino/excluded_concrete_tests.txt b/keras/src/backend/openvino/excluded_concrete_tests.txt index e8a4bc693555..ae7a3e926462 100644 --- a/keras/src/backend/openvino/excluded_concrete_tests.txt +++ b/keras/src/backend/openvino/excluded_concrete_tests.txt @@ -26,9 +26,7 @@ NumpyDtypeTest::test_inner NumpyDtypeTest::test_isfinite NumpyDtypeTest::test_isinf NumpyDtypeTest::test_isnan -NumpyDtypeTest::test_linspace NumpyDtypeTest::test_logaddexp -NumpyDtypeTest::test_logspace NumpyDtypeTest::test_matmul_ NumpyDtypeTest::test_max NumpyDtypeTest::test_mean @@ -142,8 +140,6 @@ NumpyTwoInputOpsCorrectnessTest::test_digitize NumpyTwoInputOpsCorrectnessTest::test_divide_no_nan NumpyTwoInputOpsCorrectnessTest::test_einsum NumpyTwoInputOpsCorrectnessTest::test_inner -NumpyTwoInputOpsCorrectnessTest::test_linspace -NumpyTwoInputOpsCorrectnessTest::test_logspace NumpyTwoInputOpsCorrectnessTest::test_outer NumpyTwoInputOpsCorrectnessTest::test_quantile NumpyTwoInputOpsCorrectnessTest::test_take_along_axis diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index eead553c29b8..be0ac205c6dc 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -910,9 +910,151 @@ def less_equal(x1, x2): def linspace( start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0 ): - raise NotImplementedError( - "`linspace` is not supported with openvino backend" - ) + """ + Return evenly spaced numbers over a specified interval. + + Returns `num` evenly spaced samples, calculated over the + interval [`start`, `stop`]. + + The endpoint of the interval can optionally be excluded. + + Parameters + ---------- + start : array_like + The starting value of the sequence. + stop : array_like + The end value of the sequence, unless `endpoint` is set to False. + In that case, the sequence consists of all but the last of ``num + 1`` + evenly spaced samples, so that `stop` is excluded. Note that the step + size changes when `endpoint` is False. + num : int, optional + Number of samples to generate. Default is 50. Must be non-negative. + endpoint : bool, optional + If True, `stop` is the last sample. Otherwise, it is not included. + Default is True. + retstep : bool, optional + If True, return (`samples`, `step`), where `step` is the spacing + between samples. + dtype : dtype, optional + The type of the output array. If `dtype` is not given, the data type + is inferred from `start` and `stop`. The inferred dtype will never be + an integer; `float` is chosen even if the arguments would produce an + array of integers. + axis : int, optional + The axis in the result to store the samples. Relevant only if start + or stop are array-like. By default (0), the samples will be along a + new axis inserted at the beginning. Use -1 to get an axis at the end. + + Returns + ------- + samples : ndarray + There are `num` equally spaced samples in the closed interval + ``[start, stop]`` or the half-open interval ``[start, stop)`` + (depending on whether `endpoint` is True or False). + step : float, optional + Only returned if `retstep` is True + + Size of spacing between samples. + """ + + dtype = standardize_dtype(dtype) or config.floatx() + out_dtype = OPENVINO_DTYPES[dtype] + dtype = OPENVINO_DTYPES[config.floatx()] + + start = get_ov_output(start, dtype) + stop = get_ov_output(stop, dtype) + start = ov_opset.convert(start, dtype).output(0) + stop = ov_opset.convert(stop, dtype).output(0) + + num = get_ov_output(num, Type.i32) + num = ov_opset.convert(num, Type.i32).output(0) + + zero_i = ov_opset.constant(0, Type.i32).output(0) + one_i = ov_opset.constant(1, Type.i32).output(0) + axis_i = ov_opset.constant(axis, Type.i32).output(0) + + div = ov_opset.subtract(num, one_i).output(0) if endpoint else num + div = ov_opset.convert(div, dtype).output(0) + + zero = ov_opset.constant(0.0, dtype).output(0) + one = ov_opset.constant(1.0, dtype).output(0) + num_f = ov_opset.convert(num, dtype).output(0) + seq = ov_opset.range(zero, num_f, one, dtype).output(0) + + ndim = len(start.shape) + dims = ov_opset.concat( + [ + ov_opset.constant([-1], Type.i32), + ov_opset.constant([1] * ndim, Type.i32), + ], + 0, + ).output(0) + seq = ov_opset.reshape(seq, dims, False).output(0) + + delta = ov_opset.subtract(stop, start).output(0) + + cond = ov_opset.greater(div, zero).output(0) + nan_const = ov_opset.constant(float("nan"), dtype).output(0) + step_val = ov_opset.divide(delta, div).output(0) + step = ov_opset.select(cond, step_val, nan_const).output(0) + + target_shape = ov_opset.concat( + [ + ov_opset.constant([1], Type.i64), + ov_opset.shape_of(start).output(0), + ], + 0, + ).output(0) + step = ov_opset.reshape(step, target_shape, False).output(0) + + eq_zero = ov_opset.equal(step, zero).output(0) + any_zero = ov_opset.reduce_logical_or( + eq_zero, ov_opset.constant([0], Type.i32), False + ).output(0) + + y_norm = ov_opset.multiply(seq, step).output(0) + y_denorm = ov_opset.multiply( + ov_opset.divide(seq, div).output(0), + delta, + ).output(0) + y_pos = ov_opset.convert( + ov_opset.select(any_zero, y_denorm, y_norm).output(0), dtype + ).output(0) + + y_zero = ov_opset.convert( + ov_opset.multiply(seq, delta).output(0), dtype + ).output(0) + y = ov_opset.add( + ov_opset.convert(ov_opset.select(cond, y_pos, y_zero).output(0), dtype), + start, + ).output(0) + + if endpoint: + idx = ov_opset.subtract(num, one_i).output(0) + idx = ov_opset.convert(idx, Type.i32).output(0) + idx_tensor = ov_opset.broadcast(idx, target_shape).output(0) + stop_tensor = ov_opset.broadcast(stop, target_shape).output(0) + y = ov_opset.scatter_elements_update( + y, idx_tensor, stop_tensor, 0 + ).output(0) + + if axis != 0: + rank = ov_opset.rank(y).output(0) + axis_p1 = ov_opset.add(axis_i, one_i).output(0) + pre = ov_opset.range(one_i, axis_p1, one_i).output(0) + post = ov_opset.range(axis_p1, rank, one_i).output(0) + zero_i = ov_opset.reshape( + zero_i, ov_opset.constant([1], Type.i32), False + ).output(0) + perm = ov_opset.concat([pre, zero_i, post], 0).output(0) + y = ov_opset.transpose(y, perm).output(0) + + y = ov_opset.convert(y, out_dtype).output(0) + + return_step = ov_opset.convert(step, out_dtype).output(0) + if retstep: + return (OpenVINOKerasTensor(y), OpenVINOKerasTensor(return_step)) + return OpenVINOKerasTensor(y) def log(x): @@ -993,9 +1135,152 @@ def logical_or(x1, x2): def logspace(start, stop, num=50, endpoint=True, base=10, dtype=None, axis=0): - raise NotImplementedError( - "`logspace` is not supported with openvino backend" + """ + Return numbers spaced evenly on a log scale. + + In linear space, the sequence starts at ``base ** start`` + (`base` to the power of `start`) and ends with ``base ** stop`` + (see `endpoint` below). + + Parameters + ---------- + start : array_like + ``base ** start`` is the starting value of the sequence. + stop : array_like + ``base ** stop`` is the final value of the sequence, unless `endpoint` + is False. In that case, ``num + 1`` values are spaced over the + interval in log-space, of which all but the last (a sequence of + length `num`) are returned. + num : integer, optional + Number of samples to generate. Default is 50. + endpoint : boolean, optional + If true, `stop` is the last sample. Otherwise, it is not included. + Default is True. + base : array_like, optional + The base of the log space. The step size between the elements in + ``ln(samples) / ln(base)`` (or ``log_base(samples)``) is uniform. + Default is 10.0. + dtype : dtype + The type of the output array. If `dtype` is not given, the data type + is inferred from `start` and `stop`. The inferred type will never be + an integer; `float` is chosen even if the arguments would produce an + array of integers. + axis : int, optional + The axis in the result to store the samples. Relevant only if start, + stop, or base are array-like. By default (0), the samples will be + along a new axis inserted at the beginning. Use -1 to get an axis at + the end. + + Returns + ------- + samples : ndarray + `num` samples, equally spaced on a log scale. + """ + if not (isinstance(num, int) or hasattr(num, "get_element_type")): + raise ValueError( + f"Expected 'num' to be a non-negative integer or OpenVINO scalar, " + f"got {type(num)}" + ) + if isinstance(num, int) and num < 0: + raise ValueError(f"Number of samples must be non-negative, got {num}") + if not isinstance(axis, int): + raise TypeError(f"'axis' must be an integer, got {type(axis)}") + + if dtype is None: + start_type = ( + ov_to_keras_type(start.get_element_type()) + if hasattr(start, "get_element_type") + else np.asarray(start).dtype + ) + stop_type = ( + ov_to_keras_type(stop.get_element_type()) + if hasattr(stop, "get_element_type") + else np.asarray(stop).dtype + ) + + dtype = dtypes.result_type(start_type, stop_type) + + out_dtype = standardize_dtype(dtype) or np.float64 # config.floatx() + out_dtype = OPENVINO_DTYPES[out_dtype] + + if num == 0: + return OpenVINOKerasTensor( + ov_opset.constant( + [], dtype=OPENVINO_DTYPES[config.floatx()] + ).output(0) + ) + + if ( + not hasattr(start, "get_element_type") + and not hasattr(stop, "get_element_type") + and not hasattr(base, "get_element_type") + ): + y = np.logspace( + start, + stop, + num=num, + endpoint=endpoint, + base=base, + dtype=dtype, + ) + + return OpenVINOKerasTensor( + ov_opset.convert(get_ov_output(y, out_dtype), out_dtype).output(0) + ) + + ######################################################### + # dtype = OPENVINO_DTYPES[config.floatx()] + + start, stop, base = ( + get_ov_output(start), + get_ov_output(stop), + get_ov_output(base), ) + start, stop, base = ( + ov_opset.convert(start, dtype).output(0), + ov_opset.convert(stop, dtype).output(0), + ov_opset.convert(base, dtype).output(0), + ) + + start = ov_opset.reshape( + start, + ov_opset.concat( + [ + ov_opset.constant([1], Type.i64), + ov_opset.shape_of(start).output(0), + ], + axis=0, + ).output(0), + False, + ).output(0) + stop = ov_opset.reshape( + stop, + ov_opset.concat( + [ + ov_opset.constant([1], Type.i64), + ov_opset.shape_of(stop).output(0), + ], + axis=0, + ).output(0), + False, + ).output(0) + + target_shape = ov_opset.shape_of(start).output(0) + target_shape = ov_opset.concat( + [ov_opset.constant([num], Type.i64), target_shape[1:]], axis=0 + ).output(0) + + start = ov_opset.broadcast(start, target_shape).output(0) + stop = ov_opset.broadcast(stop, target_shape).output(0) + + lin_output = linspace( + start, stop, num=num, endpoint=endpoint, dtype=dtype, axis=axis + ) + + y = ov_opset.power(base, lin_output).output(0) + y = ov_opset.convert(y, out_dtype).output(0) + + return OpenVINOKerasTensor(y) def maximum(x1, x2):