diff --git a/keras/src/backend/openvino/excluded_concrete_tests.txt b/keras/src/backend/openvino/excluded_concrete_tests.txt index 34c1d5ff795..2443f23ee57 100644 --- a/keras/src/backend/openvino/excluded_concrete_tests.txt +++ b/keras/src/backend/openvino/excluded_concrete_tests.txt @@ -26,7 +26,7 @@ NumpyDtypeTest::test_inner NumpyDtypeTest::test_isfinite NumpyDtypeTest::test_isinf NumpyDtypeTest::test_isnan -NumpyDtypeTest::test_linspace +NumpyDtypeTest::test_log1p NumpyDtypeTest::test_logaddexp NumpyDtypeTest::test_logspace NumpyDtypeTest::test_matmul_ @@ -143,7 +143,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 diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 753c7773e11..8a6c421ee12 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -907,12 +907,115 @@ def less_equal(x1, x2): return OpenVINOKerasTensor(ov_opset.less_equal(x1, x2).output(0)) -def linspace( - start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0 -): - raise NotImplementedError( - "`linspace` is not supported with openvino backend" - ) +def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0): + if not isinstance(num, int): + raise TypeError("num must be an integer") + if num < 0: + raise ValueError("num must be non-negative") + + start_ov = get_ov_output(start) + stop_ov = get_ov_output(stop) + + if dtype is None: + ov_dtype = OPENVINO_DTYPES[config.floatx()] + numpy_dtype = np.dtype(config.floatx()) + else: + if dtype == "bfloat16": + ov_dtype = OPENVINO_DTYPES["float32"] + numpy_dtype = np.dtype("float32") + else: + ov_dtype = OPENVINO_DTYPES[dtype] + numpy_dtype = np.dtype(dtype) + + start_ov = ov_opset.convert(start_ov, ov_dtype).output(0) + stop_ov = ov_opset.convert(stop_ov, ov_dtype).output(0) + + start_shape = start_ov.get_shape() + stop_shape = stop_ov.get_shape() + + if num == 0: + if len(start_shape) == 0 and len(stop_shape) == 0: + empty_array = np.array([], dtype=numpy_dtype) + result = ov_opset.constant(empty_array).output(0) + else: + broadcast_shape = list(np.broadcast( + np.empty(start_shape, dtype=bool), + np.empty(stop_shape, dtype=bool) + ).shape) + broadcast_shape.insert(axis, 0) + + empty_array = np.empty(broadcast_shape, dtype=numpy_dtype) + result = ov_opset.constant(empty_array).output(0) + + if retstep: + delta = ov_opset.subtract(stop_ov, start_ov).output(0) + return OpenVINOKerasTensor(result), OpenVINOKerasTensor(delta) + return OpenVINOKerasTensor(result) + + if num == 1: + if endpoint: + result = start_ov + else: + result = start_ov + + if len(start_shape) > 0 or len(stop_shape) > 0: + broadcast_shape = list(np.broadcast( + np.empty(start_shape, dtype=bool), + np.empty(stop_shape, dtype=bool) + ).shape) + broadcast_shape.insert(axis, 1) + + shape_const = ov_opset.constant(np.array(broadcast_shape, dtype=np.int64)).output(0) + result = ov_opset.reshape(result, shape_const).output(0) + + if retstep: + delta = ov_opset.subtract(stop_ov, start_ov).output(0) + return OpenVINOKerasTensor(result), OpenVINOKerasTensor(delta) + return OpenVINOKerasTensor(result) + + div = num - 1 if endpoint else num + div_const = ov_opset.constant(float(div)).output(0) + delta = ov_opset.subtract(stop_ov, start_ov).output(0) + step = ov_opset.divide(delta, div_const).output(0) + + indices_np = np.arange(num, dtype=numpy_dtype) + indices = ov_opset.constant(indices_np).output(0) + + is_scalar_input = len(start_shape) == 0 and len(stop_shape) == 0 + + if is_scalar_input: + scaled_indices = ov_opset.multiply(indices, step).output(0) + result = ov_opset.add(start_ov, scaled_indices).output(0) + + if axis != 0: + out_shape = [1] * (axis + 1) + out_shape[axis] = num + shape_const = ov_opset.constant(np.array(out_shape, dtype=np.int64)).output(0) + result = ov_opset.reshape(result, shape_const).output(0) + else: + broadcast_shape = list(np.broadcast( + np.empty(start_shape, dtype=bool), + np.empty(stop_shape, dtype=bool) + ).shape) + + expanded_shape = broadcast_shape.copy() + expanded_shape.insert(axis, 1) + + shape_const = ov_opset.constant(np.array(expanded_shape, dtype=np.int64)).output(0) + start_reshaped = ov_opset.reshape(start_ov, shape_const).output(0) + step_reshaped = ov_opset.reshape(step, shape_const).output(0) + + indices_shape = [1] * len(expanded_shape) + indices_shape[axis] = num + indices_shape_const = ov_opset.constant(np.array(indices_shape, dtype=np.int64)).output(0) + indices_reshaped = ov_opset.reshape(indices, indices_shape_const).output(0) + + scaled_indices = ov_opset.multiply(indices_reshaped, step_reshaped).output(0) + result = ov_opset.add(start_reshaped, scaled_indices).output(0) + + if retstep: + return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) + return OpenVINOKerasTensor(result) def log(x):