Skip to content
4 changes: 0 additions & 4 deletions keras/src/backend/openvino/excluded_concrete_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ NumpyDtypeTest::test_isnan
NumpyDtypeTest::test_isposinf
NumpyDtypeTest::test_kron
NumpyDtypeTest::test_lcm
NumpyDtypeTest::test_linspace
NumpyDtypeTest::test_logaddexp2
NumpyDtypeTest::test_logspace
NumpyDtypeTest::test_matmul_
NumpyDtypeTest::test_max
NumpyDtypeTest::test_mean
Expand Down Expand Up @@ -142,8 +140,6 @@ NumpyTwoInputOpsCorrectnessTest::test_inner
NumpyTwoInputOpsCorrectnessTest::test_isin
NumpyTwoInputOpsCorrectnessTest::test_kron
NumpyTwoInputOpsCorrectnessTest::test_lcm
NumpyTwoInputOpsCorrectnessTest::test_linspace
NumpyTwoInputOpsCorrectnessTest::test_logspace
NumpyTwoInputOpsCorrectnessTest::test_quantile
NumpyTwoInputOpsCorrectnessTest::test_tensordot
NumpyTwoInputOpsCorrectnessTest::test_vdot
Expand Down
150 changes: 146 additions & 4 deletions keras/src/backend/openvino/numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1038,9 +1038,131 @@ 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.

Supports axis=0 (prepend) and axis=-1 (append). Intermediate axis values are
treated as axis=-1.

If `retstep` is True, also returns the step size between values.

"""

start = get_ov_output(start)
stop = get_ov_output(stop)

if hasattr(num, "output") or isinstance(num, OpenVINOKerasTensor):
num_tensor = get_ov_output(num)
try:
if num_tensor.get_node().get_type_name() == "Constant":
num_value = num_tensor.get_node().get_vector()[0]
num = int(num_value)
else:
raise NotImplementedError(
"Dynamic num values not fully supported"
)
except Exception as e:
raise NotImplementedError(
"Could not extract num value from tensor"
) from e
else:
num = int(num)

if dtype is None:
output_type = OPENVINO_DTYPES[config.floatx()]
else:
output_type = OPENVINO_DTYPES[dtype]

start = ov_opset.convert(start, output_type).output(0)
stop = ov_opset.convert(stop, output_type).output(0)

if num < 0:
raise ValueError("Number of samples, `num`, must be non-negative.")

if num == 0:
empty_shape = ov_opset.constant([0], Type.i32).output(0)
result = ov_opset.broadcast(
ov_opset.constant(0.0, output_type).output(0), empty_shape
).output(0)
if retstep:
nan_step = ov_opset.constant(np.nan, output_type).output(0)
return OpenVINOKerasTensor(result), OpenVINOKerasTensor(nan_step)
return OpenVINOKerasTensor(result)

if num == 1:
result_val = start
axis_const = ov_opset.constant([axis], Type.i32).output(0)
result = ov_opset.unsqueeze(result_val, axis_const).output(0)
if retstep:
if endpoint:
step = ov_opset.constant(np.nan, output_type).output(0)
else:
step = ov_opset.subtract(stop, start).output(0)
return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step)
zero_i32 = ov_opset.constant(0, Type.i32).output(0)
one_i32 = ov_opset.constant(1, Type.i32).output(0)
one_i32_array = ov_opset.constant([1], Type.i32).output(0)

num_const = ov_opset.constant(num, output_type).output(0)

if endpoint:
divisor = ov_opset.subtract(
num_const, ov_opset.constant(1, output_type).output(0)
).output(0)
else:
divisor = num_const

step = ov_opset.divide(
ov_opset.subtract(stop, start).output(0), divisor
).output(0)

indices = ov_opset.range(
zero_i32,
ov_opset.constant(num, Type.i32).output(0),
one_i32,
output_type,
).output(0)

start_shape = ov_opset.convert(
ov_opset.shape_of(start).output(0), Type.i32
).output(0)
indices_shape = ov_opset.convert(
ov_opset.shape_of(indices).output(0), Type.i32
).output(0)

start_rank = ov_opset.shape_of(start_shape).output(0)
ones_for_start = ov_opset.broadcast(one_i32, start_rank).output(0)

if axis == 0:
indices_target_shape = ov_opset.concat(
[indices_shape, ones_for_start], 0
).output(0)
start_target_shape = ov_opset.concat(
[one_i32_array, start_shape], 0
).output(0)
else:
indices_target_shape = ov_opset.concat(
[ones_for_start, indices_shape], 0
).output(0)
start_target_shape = ov_opset.concat(
[start_shape, one_i32_array], 0
).output(0)

indices_reshaped = ov_opset.reshape(
indices, indices_target_shape, False
).output(0)
start_reshaped = ov_opset.reshape(start, start_target_shape, False).output(
0
)
step_reshaped = ov_opset.reshape(step, start_target_shape, False).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):
Expand Down Expand Up @@ -1171,10 +1293,30 @@ 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"
linear_samples = linspace(
start=start,
stop=stop,
num=num,
endpoint=endpoint,
retstep=False,
dtype=dtype,
axis=axis,
)

if dtype is None:
output_type = OPENVINO_DTYPES[config.floatx()]
else:
output_type = OPENVINO_DTYPES[dtype]

linear_output = get_ov_output(linear_samples)
base_tensor = get_ov_output(base)

base_tensor = ov_opset.convert(base_tensor, output_type).output(0)

result = ov_opset.power(base_tensor, linear_output).output(0)

return OpenVINOKerasTensor(result)


def maximum(x1, x2):
x1 = get_ov_output(x1)
Expand Down