keras-nightly 3.12.0.dev2025083103__py3-none-any.whl → 3.14.0.dev2026011604__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. keras/__init__.py +1 -0
  2. keras/_tf_keras/keras/__init__.py +1 -0
  3. keras/_tf_keras/keras/callbacks/__init__.py +3 -0
  4. keras/_tf_keras/keras/distillation/__init__.py +16 -0
  5. keras/_tf_keras/keras/distribution/__init__.py +3 -0
  6. keras/_tf_keras/keras/dtype_policies/__init__.py +6 -0
  7. keras/_tf_keras/keras/layers/__init__.py +21 -0
  8. keras/_tf_keras/keras/ops/__init__.py +16 -0
  9. keras/_tf_keras/keras/ops/image/__init__.py +1 -0
  10. keras/_tf_keras/keras/ops/linalg/__init__.py +1 -0
  11. keras/_tf_keras/keras/ops/nn/__init__.py +3 -0
  12. keras/_tf_keras/keras/ops/numpy/__init__.py +12 -0
  13. keras/_tf_keras/keras/quantizers/__init__.py +13 -0
  14. keras/callbacks/__init__.py +3 -0
  15. keras/distillation/__init__.py +16 -0
  16. keras/distribution/__init__.py +3 -0
  17. keras/dtype_policies/__init__.py +6 -0
  18. keras/layers/__init__.py +21 -0
  19. keras/ops/__init__.py +16 -0
  20. keras/ops/image/__init__.py +1 -0
  21. keras/ops/linalg/__init__.py +1 -0
  22. keras/ops/nn/__init__.py +3 -0
  23. keras/ops/numpy/__init__.py +12 -0
  24. keras/quantizers/__init__.py +13 -0
  25. keras/src/applications/imagenet_utils.py +4 -1
  26. keras/src/backend/common/backend_utils.py +30 -6
  27. keras/src/backend/common/dtypes.py +6 -12
  28. keras/src/backend/common/name_scope.py +2 -1
  29. keras/src/backend/common/variables.py +38 -20
  30. keras/src/backend/jax/core.py +126 -78
  31. keras/src/backend/jax/distribution_lib.py +16 -2
  32. keras/src/backend/jax/layer.py +3 -1
  33. keras/src/backend/jax/linalg.py +4 -0
  34. keras/src/backend/jax/nn.py +511 -29
  35. keras/src/backend/jax/numpy.py +109 -23
  36. keras/src/backend/jax/optimizer.py +3 -2
  37. keras/src/backend/jax/trainer.py +18 -3
  38. keras/src/backend/numpy/linalg.py +4 -0
  39. keras/src/backend/numpy/nn.py +313 -2
  40. keras/src/backend/numpy/numpy.py +97 -8
  41. keras/src/backend/openvino/__init__.py +1 -0
  42. keras/src/backend/openvino/core.py +6 -23
  43. keras/src/backend/openvino/linalg.py +4 -0
  44. keras/src/backend/openvino/nn.py +271 -20
  45. keras/src/backend/openvino/numpy.py +1369 -195
  46. keras/src/backend/openvino/random.py +7 -14
  47. keras/src/backend/tensorflow/layer.py +43 -9
  48. keras/src/backend/tensorflow/linalg.py +24 -0
  49. keras/src/backend/tensorflow/nn.py +545 -1
  50. keras/src/backend/tensorflow/numpy.py +351 -56
  51. keras/src/backend/tensorflow/trainer.py +6 -2
  52. keras/src/backend/torch/core.py +3 -1
  53. keras/src/backend/torch/linalg.py +4 -0
  54. keras/src/backend/torch/nn.py +125 -0
  55. keras/src/backend/torch/numpy.py +109 -9
  56. keras/src/backend/torch/trainer.py +8 -2
  57. keras/src/callbacks/__init__.py +1 -0
  58. keras/src/callbacks/callback_list.py +45 -11
  59. keras/src/callbacks/model_checkpoint.py +5 -0
  60. keras/src/callbacks/orbax_checkpoint.py +332 -0
  61. keras/src/callbacks/terminate_on_nan.py +54 -5
  62. keras/src/datasets/cifar10.py +5 -0
  63. keras/src/distillation/__init__.py +1 -0
  64. keras/src/distillation/distillation_loss.py +390 -0
  65. keras/src/distillation/distiller.py +598 -0
  66. keras/src/distribution/distribution_lib.py +14 -0
  67. keras/src/dtype_policies/__init__.py +4 -0
  68. keras/src/dtype_policies/dtype_policy.py +180 -1
  69. keras/src/export/__init__.py +2 -0
  70. keras/src/export/export_utils.py +39 -2
  71. keras/src/export/litert.py +248 -0
  72. keras/src/export/onnx.py +6 -0
  73. keras/src/export/openvino.py +1 -1
  74. keras/src/export/tf2onnx_lib.py +3 -0
  75. keras/src/layers/__init__.py +13 -0
  76. keras/src/layers/activations/softmax.py +9 -4
  77. keras/src/layers/attention/attention.py +1 -1
  78. keras/src/layers/attention/multi_head_attention.py +4 -1
  79. keras/src/layers/core/dense.py +406 -102
  80. keras/src/layers/core/einsum_dense.py +521 -116
  81. keras/src/layers/core/embedding.py +257 -99
  82. keras/src/layers/core/input_layer.py +1 -0
  83. keras/src/layers/core/reversible_embedding.py +399 -0
  84. keras/src/layers/input_spec.py +17 -17
  85. keras/src/layers/layer.py +50 -15
  86. keras/src/layers/merging/concatenate.py +6 -5
  87. keras/src/layers/merging/dot.py +4 -1
  88. keras/src/layers/pooling/adaptive_average_pooling1d.py +65 -0
  89. keras/src/layers/pooling/adaptive_average_pooling2d.py +62 -0
  90. keras/src/layers/pooling/adaptive_average_pooling3d.py +63 -0
  91. keras/src/layers/pooling/adaptive_max_pooling1d.py +65 -0
  92. keras/src/layers/pooling/adaptive_max_pooling2d.py +62 -0
  93. keras/src/layers/pooling/adaptive_max_pooling3d.py +63 -0
  94. keras/src/layers/pooling/base_adaptive_pooling.py +63 -0
  95. keras/src/layers/preprocessing/discretization.py +6 -5
  96. keras/src/layers/preprocessing/feature_space.py +8 -4
  97. keras/src/layers/preprocessing/image_preprocessing/aug_mix.py +2 -2
  98. keras/src/layers/preprocessing/image_preprocessing/bounding_boxes/validation.py +5 -5
  99. keras/src/layers/preprocessing/image_preprocessing/random_contrast.py +3 -3
  100. keras/src/layers/preprocessing/image_preprocessing/resizing.py +10 -0
  101. keras/src/layers/preprocessing/index_lookup.py +19 -1
  102. keras/src/layers/preprocessing/normalization.py +16 -1
  103. keras/src/layers/preprocessing/string_lookup.py +26 -28
  104. keras/src/layers/regularization/dropout.py +43 -1
  105. keras/src/layers/rnn/gru.py +1 -1
  106. keras/src/layers/rnn/lstm.py +2 -2
  107. keras/src/layers/rnn/rnn.py +19 -0
  108. keras/src/layers/rnn/simple_rnn.py +1 -1
  109. keras/src/legacy/preprocessing/image.py +4 -1
  110. keras/src/legacy/preprocessing/sequence.py +20 -12
  111. keras/src/losses/loss.py +1 -1
  112. keras/src/losses/losses.py +24 -0
  113. keras/src/metrics/confusion_metrics.py +7 -6
  114. keras/src/models/cloning.py +4 -0
  115. keras/src/models/functional.py +11 -3
  116. keras/src/models/model.py +195 -44
  117. keras/src/ops/image.py +257 -20
  118. keras/src/ops/linalg.py +93 -0
  119. keras/src/ops/nn.py +268 -2
  120. keras/src/ops/numpy.py +701 -44
  121. keras/src/ops/operation.py +90 -29
  122. keras/src/ops/operation_utils.py +2 -0
  123. keras/src/optimizers/adafactor.py +29 -10
  124. keras/src/optimizers/base_optimizer.py +22 -3
  125. keras/src/optimizers/loss_scale_optimizer.py +51 -18
  126. keras/src/optimizers/muon.py +65 -31
  127. keras/src/optimizers/schedules/learning_rate_schedule.py +4 -3
  128. keras/src/quantizers/__init__.py +14 -1
  129. keras/src/quantizers/awq.py +361 -0
  130. keras/src/quantizers/awq_config.py +140 -0
  131. keras/src/quantizers/awq_core.py +217 -0
  132. keras/src/quantizers/gptq.py +346 -207
  133. keras/src/quantizers/gptq_config.py +63 -13
  134. keras/src/quantizers/gptq_core.py +328 -215
  135. keras/src/quantizers/quantization_config.py +246 -0
  136. keras/src/quantizers/quantizers.py +407 -38
  137. keras/src/quantizers/utils.py +23 -0
  138. keras/src/random/seed_generator.py +6 -4
  139. keras/src/saving/file_editor.py +81 -6
  140. keras/src/saving/orbax_util.py +26 -0
  141. keras/src/saving/saving_api.py +37 -14
  142. keras/src/saving/saving_lib.py +1 -1
  143. keras/src/testing/__init__.py +1 -0
  144. keras/src/testing/test_case.py +45 -5
  145. keras/src/trainers/compile_utils.py +38 -17
  146. keras/src/trainers/data_adapters/grain_dataset_adapter.py +1 -5
  147. keras/src/tree/torchtree_impl.py +215 -0
  148. keras/src/tree/tree_api.py +6 -1
  149. keras/src/utils/backend_utils.py +31 -4
  150. keras/src/utils/dataset_utils.py +234 -35
  151. keras/src/utils/file_utils.py +49 -11
  152. keras/src/utils/image_utils.py +14 -2
  153. keras/src/utils/jax_layer.py +244 -55
  154. keras/src/utils/module_utils.py +29 -0
  155. keras/src/utils/progbar.py +10 -12
  156. keras/src/utils/python_utils.py +5 -0
  157. keras/src/utils/rng_utils.py +9 -1
  158. keras/src/utils/tracking.py +70 -5
  159. keras/src/version.py +1 -1
  160. {keras_nightly-3.12.0.dev2025083103.dist-info → keras_nightly-3.14.0.dev2026011604.dist-info}/METADATA +16 -6
  161. {keras_nightly-3.12.0.dev2025083103.dist-info → keras_nightly-3.14.0.dev2026011604.dist-info}/RECORD +163 -142
  162. keras/src/quantizers/gptq_quant.py +0 -133
  163. {keras_nightly-3.12.0.dev2025083103.dist-info → keras_nightly-3.14.0.dev2026011604.dist-info}/WHEEL +0 -0
  164. {keras_nightly-3.12.0.dev2025083103.dist-info → keras_nightly-3.14.0.dev2026011604.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ from openvino import Type
4
4
 
5
5
  from keras.src.backend import config
6
6
  from keras.src.backend.common import dtypes
7
+ from keras.src.backend.common.backend_utils import canonicalize_axis
7
8
  from keras.src.backend.common.variables import standardize_dtype
8
9
  from keras.src.backend.openvino.core import DTYPES_MAX
9
10
  from keras.src.backend.openvino.core import DTYPES_MIN
@@ -46,6 +47,8 @@ def subtract(x1, x2):
46
47
  x1 = get_ov_output(x1, element_type)
47
48
  x2 = get_ov_output(x2, element_type)
48
49
  x1, x2 = _align_operand_types(x1, x2, "subtract()")
50
+ if x1.get_element_type() == Type.boolean:
51
+ return OpenVINOKerasTensor(ov_opset.logical_xor(x1, x2).output(0))
49
52
  return OpenVINOKerasTensor(ov_opset.subtract(x1, x2).output(0))
50
53
 
51
54
 
@@ -74,25 +77,81 @@ def multiply(x1, x2):
74
77
 
75
78
 
76
79
  def mean(x, axis=None, keepdims=False):
77
- x = get_ov_output(x)
78
- if axis is None:
79
- flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
80
- x = ov_opset.reshape(x, flatten_shape, False).output(0)
81
- axis = 0
82
- axis_const = ov_opset.constant(axis, dtype=Type.i32).output(0)
83
- mean_ops = ov_opset.reduce_mean(x, axis_const, keepdims)
84
- return OpenVINOKerasTensor(mean_ops.output(0))
80
+ x_ov = get_ov_output(x)
81
+ x_type = x_ov.get_element_type()
82
+
83
+ was_axis_none = axis is None
84
+ x_resolved, axis_resolved = _resolve_axis(x_ov, axis)
85
+
86
+ if axis_resolved is None:
87
+ return OpenVINOKerasTensor(x_ov)
88
+
89
+ if x_type.is_integral():
90
+ ov_type = OPENVINO_DTYPES[config.floatx()]
91
+ x_resolved = ov_opset.convert(x_resolved, ov_type).output(0)
92
+
93
+ result = ov_opset.reduce_mean(x_resolved, axis_resolved, keepdims).output(0)
94
+
95
+ if keepdims and was_axis_none:
96
+ rank = x.get_partial_shape().rank.get_length()
97
+ result_shape = [1] * rank
98
+ result = ov_opset.reshape(
99
+ result,
100
+ ov_opset.constant(result_shape, Type.i32).output(0),
101
+ False,
102
+ ).output(0)
103
+
104
+ return OpenVINOKerasTensor(result)
85
105
 
86
106
 
87
107
  def max(x, axis=None, keepdims=False, initial=None):
88
- assert initial is None, (
89
- "`max` with not None initial is not supported by openvino backend"
90
- )
108
+ return _compute_extrema(x, "max", axis, keepdims, initial)
109
+
110
+
111
+ def _compute_extrema(x, operation, axis=None, keepdims=False, initial=None):
112
+ if operation == "min":
113
+ reduction_op = ov_opset.reduce_min
114
+ elementwise_op = ov_opset.minimum
115
+ elif operation == "max":
116
+ reduction_op = ov_opset.reduce_max
117
+ elementwise_op = ov_opset.maximum
118
+ else:
119
+ raise ValueError(
120
+ f"Operation must be 'min' or 'max', received {operation}"
121
+ )
122
+
91
123
  x = get_ov_output(x)
92
- reduce_axis = ov_opset.constant(axis, Type.i32).output(0)
93
- return OpenVINOKerasTensor(
94
- ov_opset.reduce_max(x, reduce_axis, keepdims).output(0)
95
- )
124
+ x_type = x.get_element_type()
125
+ x_for_rank = x
126
+
127
+ is_bool = x_type == Type.boolean
128
+ if is_bool:
129
+ x = ov_opset.convert(x, Type.i32).output(0)
130
+ x_type = Type.i32
131
+
132
+ if isinstance(axis, tuple) and len(axis) == 0:
133
+ return OpenVINOKerasTensor(x)
134
+
135
+ was_axis_none = axis is None
136
+ x, axis = _resolve_axis(x, axis)
137
+
138
+ result = reduction_op(x, axis, keepdims).output(0)
139
+
140
+ if initial is not None:
141
+ initial_tensor = ov_opset.constant(initial, x_type).output(0)
142
+ result = elementwise_op(result, initial_tensor).output(0)
143
+
144
+ if keepdims and was_axis_none:
145
+ orig_shape = ov_opset.shape_of(x_for_rank, Type.i32).output(0)
146
+ orig_rank_shape = ov_opset.shape_of(orig_shape, Type.i32).output(0)
147
+ one = ov_opset.constant(1, Type.i32).output(0)
148
+ result_shape = ov_opset.broadcast(one, orig_rank_shape).output(0)
149
+ result = ov_opset.reshape(result, result_shape, False).output(0)
150
+
151
+ if is_bool:
152
+ result = ov_opset.convert(result, Type.boolean).output(0)
153
+
154
+ return OpenVINOKerasTensor(result)
96
155
 
97
156
 
98
157
  def ones(shape, dtype=None):
@@ -123,6 +182,9 @@ def zeros(shape, dtype=None):
123
182
 
124
183
  def absolute(x):
125
184
  x = get_ov_output(x)
185
+ x_type = x.get_element_type()
186
+ if x_type == Type.boolean:
187
+ return OpenVINOKerasTensor(x)
126
188
  return OpenVINOKerasTensor(ov_opset.absolute(x).output(0))
127
189
 
128
190
 
@@ -133,11 +195,10 @@ def abs(x):
133
195
 
134
196
  def all(x, axis=None, keepdims=False):
135
197
  x = get_ov_output(x)
198
+ x, axis = _resolve_axis(x, axis)
136
199
  if axis is None:
137
- flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
138
- x = ov_opset.reshape(x, flatten_shape, False).output(0)
139
- axis = 0
140
- axis = ov_opset.constant(axis, Type.i32).output(0)
200
+ return OpenVINOKerasTensor(x)
201
+ x = ov_opset.convert(x, Type.boolean).output(0)
141
202
  return OpenVINOKerasTensor(
142
203
  ov_opset.reduce_logical_and(x, axis, keepdims).output(0)
143
204
  )
@@ -149,28 +210,21 @@ def angle(x):
149
210
 
150
211
  def any(x, axis=None, keepdims=False):
151
212
  x = get_ov_output(x)
213
+ x, axis = _resolve_axis(x, axis)
152
214
  if axis is None:
153
- flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
154
- x = ov_opset.reshape(x, flatten_shape, False).output(0)
155
- axis = 0
156
- axis = ov_opset.constant(axis, Type.i32).output(0)
215
+ return OpenVINOKerasTensor(x)
216
+ x = ov_opset.convert(x, Type.boolean).output(0)
157
217
  return OpenVINOKerasTensor(
158
218
  ov_opset.reduce_logical_or(x, axis, keepdims).output(0)
159
219
  )
160
220
 
161
221
 
162
222
  def amax(x, axis=None, keepdims=False):
163
- if axis == () or axis == []:
164
- return x
165
223
  x = get_ov_output(x)
166
224
  x_type = x.get_element_type()
225
+ x, axis = _resolve_axis(x, axis)
167
226
  if axis is None:
168
- flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
169
- x = ov_opset.reshape(x, flatten_shape, False).output(0)
170
- axis = 0
171
- if isinstance(axis, tuple):
172
- axis = list(axis)
173
- axis = ov_opset.constant(axis, Type.i32).output(0)
227
+ return OpenVINOKerasTensor(x)
174
228
  if x_type == Type.boolean:
175
229
  return OpenVINOKerasTensor(
176
230
  ov_opset.reduce_logical_or(x, axis, keepdims).output(0)
@@ -179,10 +233,21 @@ def amax(x, axis=None, keepdims=False):
179
233
 
180
234
 
181
235
  def amin(x, axis=None, keepdims=False):
182
- if axis == () or axis == []:
183
- return x
184
236
  x = get_ov_output(x)
185
237
  x_type = x.get_element_type()
238
+ x, axis = _resolve_axis(x, axis)
239
+ if axis is None:
240
+ return OpenVINOKerasTensor(x)
241
+ if x_type == Type.boolean:
242
+ return OpenVINOKerasTensor(
243
+ ov_opset.reduce_logical_and(x, axis, keepdims).output(0)
244
+ )
245
+ return OpenVINOKerasTensor(ov_opset.reduce_min(x, axis, keepdims).output(0))
246
+
247
+
248
+ def _resolve_axis(x, axis):
249
+ if axis == () or axis == []:
250
+ return x, None
186
251
  if axis is None:
187
252
  flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
188
253
  x = ov_opset.reshape(x, flatten_shape, False).output(0)
@@ -190,11 +255,18 @@ def amin(x, axis=None, keepdims=False):
190
255
  if isinstance(axis, tuple):
191
256
  axis = list(axis)
192
257
  axis = ov_opset.constant(axis, Type.i32).output(0)
258
+ return x, axis
259
+
260
+
261
+ def _upcast_type_if_needed(x):
262
+ x_type = x.get_element_type()
193
263
  if x_type == Type.boolean:
194
- return OpenVINOKerasTensor(
195
- ov_opset.reduce_logical_and(x, axis, keepdims).output(0)
196
- )
197
- return OpenVINOKerasTensor(ov_opset.reduce_min(x, axis, keepdims).output(0))
264
+ x = ov_opset.convert(x, Type.i32).output(0)
265
+ elif x_type in (Type.i8, Type.i16):
266
+ x = ov_opset.convert(x, Type.i32).output(0)
267
+ elif x_type in (Type.u8, Type.u16):
268
+ x = ov_opset.convert(x, Type.u32).output(0)
269
+ return x
198
270
 
199
271
 
200
272
  def append(x1, x2, axis=None):
@@ -208,7 +280,7 @@ def append(x1, x2, axis=None):
208
280
  return OpenVINOKerasTensor(ov_opset.concat([x1, x2], axis).output(0))
209
281
 
210
282
 
211
- def arange(start, stop=None, step=1, dtype=None):
283
+ def arange(start, stop=None, step=None, dtype=None):
212
284
  if stop is None:
213
285
  start, stop = get_ov_output(0), get_ov_output(start)
214
286
  else:
@@ -437,6 +509,10 @@ def array(x, dtype=None):
437
509
  return np.array(x)
438
510
 
439
511
 
512
+ def view(x, dtype=None):
513
+ raise NotImplementedError("`view` is not supported with openvino backend")
514
+
515
+
440
516
  def average(x, axis=None, weights=None):
441
517
  x = get_ov_output(x)
442
518
  if weights is not None:
@@ -470,22 +546,79 @@ def average(x, axis=None, weights=None):
470
546
 
471
547
 
472
548
  def bartlett(x):
473
- raise NotImplementedError(
474
- "`bartlett` is not supported with openvino backend"
549
+ x = get_ov_output(x)
550
+ zero_const = ov_opset.constant(0, Type.i64)
551
+ one_const = ov_opset.constant(1, Type.i64)
552
+ two_const = ov_opset.constant(2, Type.i64)
553
+ two_const_f64 = ov_opset.constant(2.0, Type.f64)
554
+ if x.get_element_type() != Type.i64:
555
+ x = ov_opset.convert(x, Type.i64)
556
+ half = ov_opset.convert(
557
+ ov_opset.divide(ov_opset.subtract(x, one_const), two_const), Type.f64
558
+ )
559
+ n = ov_opset.range(zero_const, x, one_const, Type.f64)
560
+ condition = ov_opset.less_equal(n, half)
561
+ first_half = ov_opset.divide(
562
+ ov_opset.multiply(two_const_f64, n),
563
+ ov_opset.convert(ov_opset.subtract(x, one_const), Type.f64),
475
564
  )
565
+ second_half = ov_opset.subtract(two_const_f64, first_half)
566
+ window = ov_opset.select(condition, first_half, second_half)
567
+ window = ov_opset.convert(window, OPENVINO_DTYPES[config.floatx()]).output(
568
+ 0
569
+ )
570
+ return OpenVINOKerasTensor(window)
476
571
 
477
572
 
478
573
  def hamming(x):
479
- raise NotImplementedError(
480
- "`hamming` is not supported with openvino backend"
574
+ m = get_ov_output(x)
575
+
576
+ m_i64 = (
577
+ m if m.get_element_type() == Type.i64 else ov_opset.convert(m, Type.i64)
481
578
  )
482
579
 
580
+ start = ov_opset.constant(0, Type.i64)
581
+ step = ov_opset.constant(1, Type.i64)
582
+ n = ov_opset.range(start, m_i64, step, Type.f64)
483
583
 
484
- def heaviside(x1, x2):
485
- raise NotImplementedError(
486
- "`heaviside` is not supported with openvino backend"
584
+ one_i64 = ov_opset.constant(1, Type.i64)
585
+ denom_i64 = ov_opset.subtract(m_i64, one_i64)
586
+ denom = ov_opset.convert(denom_i64, Type.f64)
587
+
588
+ two_pi = ov_opset.constant(2.0 * np.pi, Type.f64)
589
+ two_pi_over_m_minus_1 = ov_opset.divide(two_pi, denom)
590
+
591
+ x = ov_opset.multiply(two_pi_over_m_minus_1, n)
592
+ c = ov_opset.cos(x)
593
+
594
+ # 0.54 - 0.46 * cos(...)
595
+ a = ov_opset.constant(0.54, Type.f64)
596
+ b = ov_opset.constant(0.46, Type.f64)
597
+ hamming_window = ov_opset.subtract(a, ov_opset.multiply(b, c))
598
+ hamming_window = ov_opset.convert(
599
+ hamming_window, OPENVINO_DTYPES[config.floatx()]
487
600
  )
488
601
 
602
+ return OpenVINOKerasTensor(hamming_window.output(0))
603
+
604
+
605
+ def heaviside(x1, x2):
606
+ x1 = get_ov_output(x1)
607
+ x_type = x1.get_element_type()
608
+ x2 = get_ov_output(x2, x_type)
609
+
610
+ zero_scalar = ov_opset.constant(0, x_type).output(0)
611
+ one_scalar = ov_opset.constant(1, x_type).output(0)
612
+
613
+ neg = ov_opset.less(x1, zero_scalar).output(0)
614
+ pos = ov_opset.greater(x1, zero_scalar).output(0)
615
+ eq = ov_opset.equal(x1, zero_scalar).output(0)
616
+
617
+ x = ov_opset.select(neg, zero_scalar, x1).output(0)
618
+ x = ov_opset.select(pos, one_scalar, x).output(0)
619
+ x = ov_opset.select(eq, x2, x).output(0)
620
+ return OpenVINOKerasTensor(x)
621
+
489
622
 
490
623
  def kaiser(x, beta):
491
624
  raise NotImplementedError("`kaiser` is not supported with openvino backend")
@@ -537,9 +670,30 @@ def bincount(x, weights=None, minlength=0, sparse=False):
537
670
 
538
671
 
539
672
  def blackman(x):
540
- raise NotImplementedError(
541
- "`blackman` is not supported with openvino backend"
673
+ x = get_ov_output(x)
674
+ zero_const = ov_opset.constant(0, Type.i64)
675
+ one_const = ov_opset.constant(1, Type.i64)
676
+ two_pi = ov_opset.constant(2.0 * np.pi, Type.f64)
677
+ term_1 = ov_opset.constant(0.42, Type.f64)
678
+ term_2 = ov_opset.constant(0.5, Type.f64)
679
+ term_3 = ov_opset.constant(0.08, Type.f64)
680
+ if x.get_element_type() != Type.i64:
681
+ x = ov_opset.convert(x, Type.i64)
682
+ n = ov_opset.range(zero_const, x, one_const, Type.f64)
683
+ n_minus_1 = ov_opset.subtract(
684
+ ov_opset.convert(x, Type.f64), ov_opset.constant(1.0, Type.f64)
685
+ ).output(0)
686
+ angle_2pi = ov_opset.divide(ov_opset.multiply(two_pi, n), n_minus_1)
687
+ angle_4pi = ov_opset.multiply(angle_2pi, ov_opset.constant(2.0, Type.f64))
688
+ cos_2pi = ov_opset.cos(angle_2pi)
689
+ cos_4pi = ov_opset.cos(angle_4pi)
690
+ term_2_final = ov_opset.multiply(term_2, cos_2pi)
691
+ term_3_final = ov_opset.multiply(term_3, cos_4pi)
692
+ window = ov_opset.add(ov_opset.subtract(term_1, term_2_final), term_3_final)
693
+ window = ov_opset.convert(window, OPENVINO_DTYPES[config.floatx()]).output(
694
+ 0
542
695
  )
696
+ return OpenVINOKerasTensor(window)
543
697
 
544
698
 
545
699
  def broadcast_to(x, shape):
@@ -552,16 +706,32 @@ def broadcast_to(x, shape):
552
706
 
553
707
 
554
708
  def cbrt(x):
555
- raise NotImplementedError("`cbrt` is not supported with openvino backend")
709
+ x = get_ov_output(x)
710
+ x_type = x.get_element_type()
711
+ if x_type.is_integral() or x_type == Type.boolean:
712
+ x = ov_opset.convert(x, OPENVINO_DTYPES[config.floatx()]).output(0)
713
+ sign_x = ov_opset.sign(x)
714
+ abs_x = ov_opset.absolute(x)
715
+ one_third = ov_opset.constant(1.0 / 3.0, x.get_element_type())
716
+ root_abs = ov_opset.power(abs_x, one_third)
717
+ res = ov_opset.multiply(sign_x, root_abs)
718
+ return OpenVINOKerasTensor(res.output(0))
556
719
 
557
720
 
558
721
  def ceil(x):
559
722
  x = get_ov_output(x)
560
- return OpenVINOKerasTensor(ov_opset.ceil(x).output(0))
723
+ x_type = x.get_element_type()
724
+ if x_type.is_integral():
725
+ x = ov_opset.convert(x, OPENVINO_DTYPES[config.floatx()]).output(0)
726
+ ceiling = ov_opset.ceil(x).output(0)
727
+ return OpenVINOKerasTensor(ceiling)
561
728
 
562
729
 
563
730
  def clip(x, x_min, x_max):
564
731
  x = get_ov_output(x)
732
+ x_type = x.get_element_type()
733
+ if x_type == Type.boolean:
734
+ x = ov_opset.convert(x, Type.i32).output(0)
565
735
  x_min = get_ov_output(x_min, x.get_element_type())
566
736
  x_max = get_ov_output(x_max, x.get_element_type())
567
737
  clip_by_min = ov_opset.maximum(x, x_min).output(0)
@@ -617,15 +787,9 @@ def count_nonzero(x, axis=None):
617
787
  zero_constant = ov_opset.convert_like(zero_constant, x)
618
788
  x = ov_opset.not_equal(x, zero_constant).output(0)
619
789
  x = ov_opset.convert(x, Type.i32).output(0)
620
- if axis is None:
621
- flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
622
- x = ov_opset.reshape(x, flatten_shape, False).output(0)
623
- axis = 0
624
- if isinstance(axis, tuple):
625
- axis = list(axis)
626
- if axis == []:
790
+ x, axis = _resolve_axis(x, axis)
791
+ if not axis:
627
792
  return OpenVINOKerasTensor(x)
628
- axis = ov_opset.constant(axis, Type.i32).output(0)
629
793
  return OpenVINOKerasTensor(ov_opset.reduce_sum(x, axis, False).output(0))
630
794
 
631
795
 
@@ -644,11 +808,9 @@ def cumsum(x, axis=None, dtype=None):
644
808
  if dtype is not None:
645
809
  ov_type = OPENVINO_DTYPES[standardize_dtype(dtype)]
646
810
  x = ov_opset.convert(x, ov_type).output(0)
647
- if axis is None:
648
- flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
649
- x = ov_opset.reshape(x, flatten_shape, False).output(0)
650
- axis = 0
651
- axis = ov_opset.constant(axis, Type.i32).output(0)
811
+ x, axis = _resolve_axis(x, axis)
812
+ if x.get_element_type() == Type.boolean:
813
+ x = ov_opset.convert(x, Type.i32).output(0)
652
814
  return OpenVINOKerasTensor(ov_opset.cumsum(x, axis).output(0))
653
815
 
654
816
 
@@ -674,13 +836,120 @@ def deg2rad(x):
674
836
 
675
837
 
676
838
  def diag(x, k=0):
677
- raise NotImplementedError("`diag` is not supported with openvino backend")
839
+ x = get_ov_output(x)
840
+ x_shape = x.get_partial_shape()
841
+ rank = x_shape.rank.get_length()
842
+
843
+ if rank == 1:
844
+ N_dim = x_shape[0]
845
+ if not N_dim.is_static:
846
+ raise ValueError(
847
+ "diag requires input with static shape for 1D input."
848
+ )
849
+ N = N_dim.get_length()
850
+ output_size = N + np.abs(k)
851
+ out_shape = ov_opset.constant(
852
+ [output_size, output_size], dtype=Type.i32
853
+ ).output(0)
854
+ zeros_const = ov_opset.constant(0, x.get_element_type()).output(0)
855
+ diag_matrix = ov_opset.broadcast(zeros_const, out_shape)
856
+
857
+ indices = []
858
+ if k >= 0:
859
+ for i in range(N):
860
+ indices.append([i, i + k])
861
+ else:
862
+ for i in range(N):
863
+ indices.append([i - k, i])
864
+
865
+ indices = np.array(indices, dtype=np.int32)
866
+ indices_const = ov_opset.constant(indices, dtype=Type.i32).output(0)
867
+ updated = ov_opset.scatter_nd_update(diag_matrix, indices_const, x)
868
+ return OpenVINOKerasTensor(updated.output(0))
869
+
870
+ elif rank == 2:
871
+ M_dim = x_shape[0]
872
+ N_dim = x_shape[1]
873
+ if not M_dim.is_static or not N_dim.is_static:
874
+ raise ValueError(
875
+ "diag requires input with static shape for 2D input."
876
+ )
877
+ M = M_dim.get_length()
878
+ N = N_dim.get_length()
879
+
880
+ if k >= 0:
881
+ L = np.minimum(M, N - k) if (N - k) > 0 else 0
882
+ indices = [[i, i + k] for i in range(L)]
883
+ else:
884
+ L = np.minimum(M + k, N) if (M + k) > 0 else 0
885
+ indices = [[i - k, i] for i in range(L)]
886
+
887
+ if L <= 0:
888
+ keras_dtype = ov_to_keras_type(x.get_element_type())
889
+ np_dtype = np.dtype(keras_dtype)
890
+ empty_np = np.empty((0,), dtype=np_dtype)
891
+ empty_const = ov_opset.constant(
892
+ empty_np, x.get_element_type()
893
+ ).output(0)
894
+ return OpenVINOKerasTensor(empty_const)
895
+
896
+ indices = np.array(indices, dtype=np.int32)
897
+ indices_const = ov_opset.constant(indices, dtype=Type.i32).output(0)
898
+ diag_vec = ov_opset.gather_nd(x, indices_const)
899
+ return OpenVINOKerasTensor(diag_vec.output(0))
900
+
901
+ else:
902
+ raise ValueError("diag supports only 1D or 2D tensors")
678
903
 
679
904
 
680
905
  def diagonal(x, offset=0, axis1=0, axis2=1):
681
- raise NotImplementedError(
682
- "`diagonal` is not supported with openvino backend"
683
- )
906
+ x = get_ov_output(x)
907
+ shape = x.get_partial_shape()
908
+ rank = x.get_partial_shape().rank.get_length()
909
+ if rank is None:
910
+ raise ValueError("`diagonal` requires input tensor with static rank.")
911
+ if rank < 2:
912
+ raise ValueError(
913
+ f"diagonal requires input tensor with rank >= 2.Given rank: {rank}"
914
+ )
915
+ axis1 = canonicalize_axis(axis1, rank)
916
+ axis2 = canonicalize_axis(axis2, rank)
917
+ if axis1 == axis2:
918
+ raise ValueError("`axis1` and `axis2` cannot be the same.")
919
+
920
+ perm_order = [axis1, axis2] + [
921
+ i for i in range(rank) if i != axis1 and i != axis2
922
+ ]
923
+ perm_const = ov_opset.constant(perm_order, dtype=Type.i32).output(0)
924
+ x_transposed = ov_opset.transpose(x, perm_const)
925
+
926
+ N_dim = shape[axis1]
927
+ M_dim = shape[axis2]
928
+ if not N_dim.is_static or not M_dim.is_static:
929
+ raise ValueError(
930
+ "`diagonal` requires input tensor with static shape for axes "
931
+ f"`axis1` ({axis1}) and `axis2` ({axis2})."
932
+ )
933
+ N = N_dim.get_length()
934
+ M = M_dim.get_length()
935
+ if offset >= 0:
936
+ L = np.minimum(N, M - offset) if (M - offset) > 0 else 0
937
+ indices = [[i, i + offset] for i in range(L)]
938
+ else:
939
+ L = np.minimum(N + offset, M) if (N + offset) > 0 else 0
940
+ indices = [[i - offset, i] for i in range(L)]
941
+
942
+ indices = np.array(indices, dtype=np.int32).reshape(L, 2)
943
+ indices_const = ov_opset.constant(indices, dtype=Type.i32).output(0)
944
+
945
+ diag_gathered = ov_opset.gather_nd(x_transposed, indices_const)
946
+
947
+ out_rank = rank - 1
948
+ out_perm_order = list(range(1, out_rank)) + [0]
949
+ out_perm_const = ov_opset.constant(out_perm_order, dtype=Type.i32).output(0)
950
+
951
+ final_output = ov_opset.transpose(diag_gathered, out_perm_const)
952
+ return OpenVINOKerasTensor(final_output.output(0))
684
953
 
685
954
 
686
955
  def diff(a, n=1, axis=-1):
@@ -757,9 +1026,30 @@ def diff(a, n=1, axis=-1):
757
1026
 
758
1027
 
759
1028
  def digitize(x, bins):
760
- raise NotImplementedError(
761
- "`digitize` is not supported with openvino backend"
762
- )
1029
+ x_node = get_ov_output(x)
1030
+
1031
+ if isinstance(bins, OpenVINOKerasTensor):
1032
+ bins_node = get_ov_output(bins)
1033
+ else:
1034
+ bins_np = np.asarray(bins)
1035
+ if bins_np.ndim != 1:
1036
+ raise ValueError("`bins` must be 1-D array-like")
1037
+ bins_node = ov_opset.constant(bins_np).output(0)
1038
+
1039
+ x_node, bins_node = _align_operand_types(x_node, bins_node, "digitize()")
1040
+
1041
+ if x_node.get_element_type() == Type.boolean:
1042
+ x_node = ov_opset.convert(x_node, Type.f32).output(0)
1043
+ bins_node = ov_opset.convert(bins_node, Type.f32).output(0)
1044
+
1045
+ result = ov_opset.bucketize(
1046
+ x_node,
1047
+ bins_node,
1048
+ output_type=Type.i32,
1049
+ with_right_bound=False,
1050
+ ).output(0)
1051
+
1052
+ return OpenVINOKerasTensor(result)
763
1053
 
764
1054
 
765
1055
  def dot(x1, x2):
@@ -789,6 +1079,10 @@ def empty(shape, dtype=None):
789
1079
  return OpenVINOKerasTensor(empty_tensor)
790
1080
 
791
1081
 
1082
+ def empty_like(x, dtype=None):
1083
+ return zeros_like(x, dtype=dtype)
1084
+
1085
+
792
1086
  def equal(x1, x2):
793
1087
  element_type = None
794
1088
  if isinstance(x1, OpenVINOKerasTensor):
@@ -831,11 +1125,101 @@ def expm1(x):
831
1125
 
832
1126
 
833
1127
  def flip(x, axis=None):
834
- raise NotImplementedError("`flip` is not supported with openvino backend")
1128
+ x_node = get_ov_output(x)
1129
+
1130
+ # Using OpenVINO tensor shape
1131
+ ndim = len(x_node.get_partial_shape())
1132
+ if ndim is None:
1133
+ raise ValueError(
1134
+ "The `flip` operation does not support tensors with dynamic rank "
1135
+ "for the OpenVINO backend."
1136
+ )
1137
+
1138
+ if axis is None:
1139
+ axis = list(range(ndim))
1140
+ elif isinstance(axis, int):
1141
+ axis = [axis]
1142
+
1143
+ axis = [a + ndim if a < 0 else a for a in axis]
1144
+
1145
+ begin = [0] * ndim
1146
+ end = [0] * ndim
1147
+ strides = [1] * ndim
1148
+ for a in axis:
1149
+ strides[a] = -1
1150
+
1151
+ all_ones_mask = [1] * ndim
1152
+ result = ov_opset.strided_slice(
1153
+ data=x_node,
1154
+ begin=begin,
1155
+ end=end,
1156
+ strides=strides,
1157
+ begin_mask=all_ones_mask,
1158
+ end_mask=all_ones_mask,
1159
+ )
1160
+ return OpenVINOKerasTensor(result.output(0))
1161
+
1162
+
1163
+ def rot90(array, k=1, axes=(0, 1)):
1164
+ """Rotate an array by 90 degrees in the plane specified by axes."""
1165
+ array = get_ov_output(array)
1166
+
1167
+ if not isinstance(axes, (tuple, list)) or len(axes) != 2:
1168
+ raise ValueError("axes must be a tuple of length 2")
1169
+
1170
+ shape = array.get_partial_shape()
1171
+ ndim = shape.rank.get_length()
1172
+ if ndim is None:
1173
+ raise ValueError(
1174
+ "`rot90` does not support tensors with dynamic rank "
1175
+ "for the OpenVINO backend."
1176
+ )
1177
+
1178
+ axis1 = canonicalize_axis(axes[0], ndim)
1179
+ axis2 = canonicalize_axis(axes[1], ndim)
1180
+
1181
+ if axis1 == axis2:
1182
+ raise ValueError("axes must be different")
1183
+
1184
+ k = k % 4
1185
+ if k == 0:
1186
+ return OpenVINOKerasTensor(array)
1187
+
1188
+ result = array
1189
+
1190
+ for _ in range(k):
1191
+ # 1️ Transpose axis1 <-> axis2
1192
+ perm = list(range(ndim))
1193
+ perm[axis1], perm[axis2] = perm[axis2], perm[axis1]
1194
+ perm_const = ov_opset.constant(perm, Type.i32).output(0)
1195
+ result = ov_opset.transpose(result, perm_const).output(0)
1196
+
1197
+ # 2️ Reverse along axis1 using StridedSlice
1198
+ begin = [0] * ndim
1199
+ end = [0] * ndim
1200
+ strides = [1] * ndim
1201
+ strides[axis1] = -1
1202
+
1203
+ begin_mask = [1] * ndim
1204
+ end_mask = [1] * ndim
1205
+
1206
+ result = ov_opset.strided_slice(
1207
+ data=result,
1208
+ begin=begin,
1209
+ end=end,
1210
+ strides=strides,
1211
+ begin_mask=begin_mask,
1212
+ end_mask=end_mask,
1213
+ ).output(0)
1214
+
1215
+ return OpenVINOKerasTensor(result)
835
1216
 
836
1217
 
837
1218
  def floor(x):
838
1219
  x = get_ov_output(x)
1220
+ x_type = x.get_element_type()
1221
+ if x_type.is_integral():
1222
+ x = ov_opset.convert(x, OPENVINO_DTYPES[config.floatx()])
839
1223
  return OpenVINOKerasTensor(ov_opset.floor(x).output(0))
840
1224
 
841
1225
 
@@ -907,7 +1291,34 @@ def hstack(xs):
907
1291
 
908
1292
 
909
1293
  def hypot(x1, x2):
910
- raise NotImplementedError("`hypot` is not supported with openvino backend")
1294
+ element_type = None
1295
+ if isinstance(x1, OpenVINOKerasTensor):
1296
+ element_type = x1.output.get_element_type()
1297
+ if isinstance(x2, OpenVINOKerasTensor):
1298
+ element_type = x2.output.get_element_type()
1299
+ x1 = get_ov_output(x1, element_type)
1300
+ x2 = get_ov_output(x2, element_type)
1301
+ x1, x2 = _align_operand_types(x1, x2, "hypot()")
1302
+ x_type = x1.get_element_type()
1303
+ if x_type.is_integral() or x_type == Type.boolean:
1304
+ ov_type = OPENVINO_DTYPES[config.floatx()]
1305
+ x1 = ov_opset.convert(x1, ov_type)
1306
+ x2 = ov_opset.convert(x2, ov_type)
1307
+ x1_abs = ov_opset.absolute(x1)
1308
+ x2_abs = ov_opset.absolute(x2)
1309
+ max_val = ov_opset.maximum(x1_abs, x2_abs)
1310
+ min_val = ov_opset.minimum(x1_abs, x2_abs)
1311
+ one = ov_opset.constant(1, max_val.get_element_type())
1312
+ is_zero_mask = ov_opset.equal(
1313
+ max_val, ov_opset.constant(0, max_val.get_element_type())
1314
+ )
1315
+ safe_divisor = ov_opset.select(is_zero_mask, one, max_val)
1316
+ ratio = ov_opset.divide(min_val, safe_divisor)
1317
+ result = ov_opset.multiply(
1318
+ max_val,
1319
+ ov_opset.sqrt(ov_opset.add(one, ov_opset.multiply(ratio, ratio))),
1320
+ )
1321
+ return OpenVINOKerasTensor(result.output(0))
911
1322
 
912
1323
 
913
1324
  def identity(n, dtype=None):
@@ -948,66 +1359,324 @@ def isclose(x1, x2, rtol=1e-5, atol=1e-8, equal_nan=False):
948
1359
 
949
1360
 
950
1361
  def isfinite(x):
951
- x = get_ov_output(x)
952
- return OpenVINOKerasTensor(ov_opset.is_finite(x).output(0))
1362
+ # NOTE: openvino has an is_finite operation but it does not properly
1363
+ # catch np.inf and -np.inf as not finite values. Hence we bootstrap here. If
1364
+ # that ever changes, we could simplify this to just call that operation.
1365
+ inf_values = get_ov_output(isinf(x))
1366
+ nan_values = get_ov_output(isnan(x))
1367
+ all_non_finite_values = ov_opset.logical_or(inf_values, nan_values).output(
1368
+ 0
1369
+ )
1370
+ is_finite = ov_opset.logical_not(all_non_finite_values).output(0)
1371
+ return OpenVINOKerasTensor(is_finite)
953
1372
 
954
1373
 
955
1374
  def isin(x1, x2, assume_unique=False, invert=False):
956
- raise NotImplementedError("`isin` is not supported with openvino backend")
1375
+ x1 = get_ov_output(x1)
1376
+ x2 = get_ov_output(x2)
1377
+ output_shape = ov_opset.shape_of(x1).output(0)
1378
+ x1, x2 = _align_operand_types(x1, x2, "isin()")
1379
+
1380
+ minus_one = ov_opset.constant([-1], dtype=Type.i64)
1381
+ x1 = ov_opset.reshape(x1, minus_one, special_zero=False).output(0)
1382
+ x2 = ov_opset.reshape(x2, minus_one, special_zero=False).output(0)
1383
+ if not assume_unique:
1384
+ x2 = ov_opset.unique(x2).output(0)
1385
+ x1 = ov_opset.unsqueeze(x1, 1).output(0)
1386
+ x2 = ov_opset.unsqueeze(x2, 0).output(0)
1387
+ cmp = ov_opset.equal(x1, x2).output(0)
1388
+ result_flat = ov_opset.reduce_logical_or(cmp, 1).output(0)
1389
+
1390
+ if invert:
1391
+ result_flat = ov_opset.logical_not(result_flat).output(0)
1392
+ result = ov_opset.reshape(result_flat, output_shape, False).output(0)
1393
+ return OpenVINOKerasTensor(result)
957
1394
 
958
1395
 
959
1396
  def isinf(x):
960
- x = get_ov_output(x)
961
- return OpenVINOKerasTensor(ov_opset.is_inf(x).output(0))
1397
+ pos_inf = get_ov_output(isposinf(x))
1398
+ neg_inf = get_ov_output(isneginf(x))
1399
+ inf = ov_opset.logical_or(pos_inf, neg_inf).output(0)
1400
+ return OpenVINOKerasTensor(inf)
962
1401
 
963
1402
 
964
1403
  def isnan(x):
965
1404
  x = get_ov_output(x)
1405
+ x_type = x.get_element_type()
1406
+ if x_type.is_integral():
1407
+ x = ov_opset.convert(x, OPENVINO_DTYPES[config.floatx()])
966
1408
  return OpenVINOKerasTensor(ov_opset.is_nan(x).output(0))
967
1409
 
968
1410
 
969
1411
  def isneginf(x):
970
- raise NotImplementedError(
971
- "`isneginf` is not supported with openvino backend"
972
- )
1412
+ return _is_inf(x, pos=False)
973
1413
 
974
1414
 
975
1415
  def isposinf(x):
976
- raise NotImplementedError(
977
- "`isposinf` is not supported with openvino backend"
978
- )
979
-
1416
+ return _is_inf(x)
980
1417
 
981
- def less(x1, x2):
982
- element_type = None
983
- if isinstance(x1, OpenVINOKerasTensor):
984
- element_type = x1.output.get_element_type()
985
- if isinstance(x2, OpenVINOKerasTensor):
986
- element_type = x2.output.get_element_type()
987
- x1 = get_ov_output(x1, element_type)
988
- x2 = get_ov_output(x2, element_type)
989
- x1, x2 = _align_operand_types(x1, x2, "less()")
990
- return OpenVINOKerasTensor(ov_opset.less(x1, x2).output(0))
991
1418
 
1419
+ def _is_inf(x, pos=True):
1420
+ # NOTE: there is an ov_opset.is_inf but it does not catch
1421
+ # numpy infinite values like np.inf and -np.inf, hence why we have this
1422
+ # if this ever changes in OpenVINO, we can do this instead:
1423
+ # ov_opset.is_inf(x, {"detect_positive": pos, "detect_negative": not pos})
1424
+ # for each infinite sign
1425
+ inf_value = np.inf if pos else -np.inf
1426
+ x = get_ov_output(x)
1427
+ x_type = x.get_element_type()
992
1428
 
993
- def less_equal(x1, x2):
994
- element_type = None
995
- if isinstance(x1, OpenVINOKerasTensor):
996
- element_type = x1.output.get_element_type()
997
- if isinstance(x2, OpenVINOKerasTensor):
998
- element_type = x2.output.get_element_type()
999
- x1 = get_ov_output(x1, element_type)
1000
- x2 = get_ov_output(x2, element_type)
1001
- x1, x2 = _align_operand_types(x1, x2, "less_equal()")
1002
- return OpenVINOKerasTensor(ov_opset.less_equal(x1, x2).output(0))
1429
+ if x_type.is_integral() or x_type == Type.boolean:
1430
+ shape = ov_opset.shape_of(x, "i32").output(0)
1431
+ false_const = ov_opset.constant(False, Type.boolean).output(0)
1432
+ return OpenVINOKerasTensor(
1433
+ ov_opset.broadcast(false_const, shape).output(0)
1434
+ )
1435
+
1436
+ if x_type == Type.bf16:
1437
+ x_f32 = ov_opset.convert(x, Type.f32).output(0)
1438
+ inf = ov_opset.constant(inf_value, Type.f32).output(0)
1439
+ is_inf = ov_opset.equal(x_f32, inf).output(0)
1440
+ else:
1441
+ if x_type == Type.f16:
1442
+ inf = ov_opset.constant(inf_value, Type.f16).output(0)
1443
+ elif x_type == Type.f32:
1444
+ inf = ov_opset.constant(inf_value, Type.f32).output(0)
1445
+ elif x_type == Type.f64:
1446
+ inf = ov_opset.constant(inf_value, Type.f64).output(0)
1447
+ else:
1448
+ inf = ov_opset.constant(inf_value, Type.f32).output(0)
1449
+ is_inf = ov_opset.equal(x, inf).output(0)
1450
+ return OpenVINOKerasTensor(is_inf)
1451
+
1452
+
1453
+ def isreal(x):
1454
+ raise NotImplementedError("`isreal` is not supported with openvino backend")
1455
+
1456
+
1457
+ def kron(x1, x2):
1458
+ x1 = get_ov_output(x1)
1459
+ x2 = get_ov_output(x2)
1460
+ x1, x2 = _align_operand_types(x1, x2, "kron()")
1461
+ x1_shape = x1.get_partial_shape()
1462
+ x2_shape = x2.get_partial_shape()
1463
+ if x1_shape.rank.is_dynamic or x2_shape.rank.is_dynamic:
1464
+ raise ValueError(
1465
+ "`kron` does not support tensors with dynamic rank for "
1466
+ "the OpenVINO backend."
1467
+ )
1468
+ ndim1 = x1_shape.rank.get_length()
1469
+ ndim2 = x2_shape.rank.get_length()
1470
+ if ndim1 < ndim2:
1471
+ axes = ov_opset.range(
1472
+ ov_opset.constant(0, Type.i32),
1473
+ ov_opset.constant(ndim2 - ndim1, Type.i32),
1474
+ ov_opset.constant(1, Type.i32),
1475
+ )
1476
+ x1 = ov_opset.unsqueeze(x1, axes)
1477
+ ndim1 = ndim2
1478
+ elif ndim2 < ndim1:
1479
+ axes = ov_opset.range(
1480
+ ov_opset.constant(0, Type.i32),
1481
+ ov_opset.constant(ndim1 - ndim2, Type.i32),
1482
+ ov_opset.constant(1, Type.i32),
1483
+ )
1484
+ x2 = ov_opset.unsqueeze(x2, axes)
1485
+ ndim2 = ndim1
1486
+ shape1 = ov_opset.shape_of(x1, Type.i32)
1487
+ shape2 = ov_opset.shape_of(x2, Type.i32)
1488
+ ones = ov_opset.broadcast(
1489
+ ov_opset.constant(1, Type.i32), ov_opset.constant([ndim1], Type.i32)
1490
+ )
1491
+ axis = ov_opset.constant(1, Type.i32)
1492
+ flatten = ov_opset.constant([-1], Type.i32)
1493
+ unsqueezed_ones = ov_opset.unsqueeze(ones, axis)
1494
+ x1_new_shape = ov_opset.reshape(
1495
+ ov_opset.concat(
1496
+ [ov_opset.unsqueeze(shape1, axis), unsqueezed_ones],
1497
+ axis=1,
1498
+ ),
1499
+ flatten,
1500
+ False,
1501
+ )
1502
+ x2_new_shape = ov_opset.reshape(
1503
+ ov_opset.concat(
1504
+ [unsqueezed_ones, ov_opset.unsqueeze(shape2, axis)],
1505
+ axis=1,
1506
+ ),
1507
+ flatten,
1508
+ False,
1509
+ )
1510
+ result = ov_opset.multiply(
1511
+ ov_opset.reshape(x1, x1_new_shape, False),
1512
+ ov_opset.reshape(x2, x2_new_shape, False),
1513
+ )
1514
+ result = ov_opset.reshape(
1515
+ result, ov_opset.multiply(shape1, shape2), False
1516
+ ).output(0)
1517
+ return OpenVINOKerasTensor(result)
1518
+
1519
+
1520
+ def lcm(x1, x2):
1521
+ raise NotImplementedError("`lcm` is not supported with openvino backend")
1522
+
1523
+
1524
+ def ldexp(x1, x2):
1525
+ raise NotImplementedError("`ldexp` is not supported with openvino backend")
1526
+
1527
+
1528
+ def less(x1, x2):
1529
+ element_type = None
1530
+ if isinstance(x1, OpenVINOKerasTensor):
1531
+ element_type = x1.output.get_element_type()
1532
+ if isinstance(x2, OpenVINOKerasTensor):
1533
+ element_type = x2.output.get_element_type()
1534
+ x1 = get_ov_output(x1, element_type)
1535
+ x2 = get_ov_output(x2, element_type)
1536
+ x1, x2 = _align_operand_types(x1, x2, "less()")
1537
+ return OpenVINOKerasTensor(ov_opset.less(x1, x2).output(0))
1538
+
1539
+
1540
+ def less_equal(x1, x2):
1541
+ element_type = None
1542
+ if isinstance(x1, OpenVINOKerasTensor):
1543
+ element_type = x1.output.get_element_type()
1544
+ if isinstance(x2, OpenVINOKerasTensor):
1545
+ element_type = x2.output.get_element_type()
1546
+ x1 = get_ov_output(x1, element_type)
1547
+ x2 = get_ov_output(x2, element_type)
1548
+ x1, x2 = _align_operand_types(x1, x2, "less_equal()")
1549
+ return OpenVINOKerasTensor(ov_opset.less_equal(x1, x2).output(0))
1003
1550
 
1004
1551
 
1005
1552
  def linspace(
1006
1553
  start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0
1007
1554
  ):
1008
- raise NotImplementedError(
1009
- "`linspace` is not supported with openvino backend"
1555
+ """Return evenly spaced numbers over a specified interval.
1556
+
1557
+ Supports axis=0 (prepend) and axis=-1 (append). Intermediate axis values are
1558
+ treated as axis=-1.
1559
+
1560
+ If `retstep` is True, also returns the step size between values.
1561
+
1562
+ """
1563
+
1564
+ start = get_ov_output(start)
1565
+ stop = get_ov_output(stop)
1566
+
1567
+ if hasattr(num, "output") or isinstance(num, OpenVINOKerasTensor):
1568
+ num_tensor = get_ov_output(num)
1569
+ try:
1570
+ if num_tensor.get_node().get_type_name() == "Constant":
1571
+ num_value = num_tensor.get_node().get_vector()[0]
1572
+ num = int(num_value)
1573
+ else:
1574
+ raise NotImplementedError(
1575
+ "Dynamic num values not fully supported"
1576
+ )
1577
+ except Exception as e:
1578
+ raise NotImplementedError(
1579
+ "Could not extract num value from tensor"
1580
+ ) from e
1581
+ else:
1582
+ num = int(num)
1583
+
1584
+ if dtype is None:
1585
+ output_type = OPENVINO_DTYPES[config.floatx()]
1586
+ else:
1587
+ output_type = OPENVINO_DTYPES[dtype]
1588
+
1589
+ start = ov_opset.convert(start, output_type).output(0)
1590
+ stop = ov_opset.convert(stop, output_type).output(0)
1591
+
1592
+ if num < 0:
1593
+ raise ValueError("Number of samples, `num`, must be non-negative.")
1594
+
1595
+ if num == 0:
1596
+ empty_shape = ov_opset.constant([0], Type.i32).output(0)
1597
+ result = ov_opset.broadcast(
1598
+ ov_opset.constant(0.0, output_type).output(0), empty_shape
1599
+ ).output(0)
1600
+ if retstep:
1601
+ nan_step = ov_opset.constant(np.nan, output_type).output(0)
1602
+ return OpenVINOKerasTensor(result), OpenVINOKerasTensor(nan_step)
1603
+ return OpenVINOKerasTensor(result)
1604
+
1605
+ if num == 1:
1606
+ result_val = start
1607
+ axis_const = ov_opset.constant([axis], Type.i32).output(0)
1608
+ result = ov_opset.unsqueeze(result_val, axis_const).output(0)
1609
+ if retstep:
1610
+ if endpoint:
1611
+ step = ov_opset.constant(np.nan, output_type).output(0)
1612
+ else:
1613
+ step = ov_opset.subtract(stop, start).output(0)
1614
+ return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step)
1615
+ zero_i32 = ov_opset.constant(0, Type.i32).output(0)
1616
+ one_i32 = ov_opset.constant(1, Type.i32).output(0)
1617
+ one_i32_array = ov_opset.constant([1], Type.i32).output(0)
1618
+
1619
+ num_const = ov_opset.constant(num, output_type).output(0)
1620
+
1621
+ if endpoint:
1622
+ divisor = ov_opset.subtract(
1623
+ num_const, ov_opset.constant(1, output_type).output(0)
1624
+ ).output(0)
1625
+ else:
1626
+ divisor = num_const
1627
+
1628
+ step = ov_opset.divide(
1629
+ ov_opset.subtract(stop, start).output(0), divisor
1630
+ ).output(0)
1631
+
1632
+ indices = ov_opset.range(
1633
+ zero_i32,
1634
+ ov_opset.constant(num, Type.i32).output(0),
1635
+ one_i32,
1636
+ output_type,
1637
+ ).output(0)
1638
+
1639
+ start_shape = ov_opset.convert(
1640
+ ov_opset.shape_of(start).output(0), Type.i32
1641
+ ).output(0)
1642
+ indices_shape = ov_opset.convert(
1643
+ ov_opset.shape_of(indices).output(0), Type.i32
1644
+ ).output(0)
1645
+
1646
+ start_rank = ov_opset.shape_of(start_shape).output(0)
1647
+ ones_for_start = ov_opset.broadcast(one_i32, start_rank).output(0)
1648
+
1649
+ if axis == 0:
1650
+ indices_target_shape = ov_opset.concat(
1651
+ [indices_shape, ones_for_start], 0
1652
+ ).output(0)
1653
+ start_target_shape = ov_opset.concat(
1654
+ [one_i32_array, start_shape], 0
1655
+ ).output(0)
1656
+ else:
1657
+ indices_target_shape = ov_opset.concat(
1658
+ [ones_for_start, indices_shape], 0
1659
+ ).output(0)
1660
+ start_target_shape = ov_opset.concat(
1661
+ [start_shape, one_i32_array], 0
1662
+ ).output(0)
1663
+
1664
+ indices_reshaped = ov_opset.reshape(
1665
+ indices, indices_target_shape, False
1666
+ ).output(0)
1667
+ start_reshaped = ov_opset.reshape(start, start_target_shape, False).output(
1668
+ 0
1010
1669
  )
1670
+ step_reshaped = ov_opset.reshape(step, start_target_shape, False).output(0)
1671
+
1672
+ scaled_indices = ov_opset.multiply(indices_reshaped, step_reshaped).output(
1673
+ 0
1674
+ )
1675
+ result = ov_opset.add(start_reshaped, scaled_indices).output(0)
1676
+
1677
+ if retstep:
1678
+ return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step)
1679
+ return OpenVINOKerasTensor(result)
1011
1680
 
1012
1681
 
1013
1682
  def log(x):
@@ -1060,8 +1729,58 @@ def log2(x):
1060
1729
 
1061
1730
 
1062
1731
  def logaddexp(x1, x2):
1732
+ element_type = None
1733
+ if isinstance(x1, OpenVINOKerasTensor):
1734
+ element_type = x1.output.get_element_type()
1735
+ if isinstance(x2, OpenVINOKerasTensor):
1736
+ element_type = x2.output.get_element_type()
1737
+ x1 = get_ov_output(x1, element_type)
1738
+ x2 = get_ov_output(x2, element_type)
1739
+ x1, x2 = _align_operand_types(x1, x2, "logaddexp()")
1740
+
1741
+ if x1.element_type.is_integral() or x2.element_type.is_integral():
1742
+ float_dtype = OPENVINO_DTYPES[config.floatx()]
1743
+ if x1.element_type.is_integral():
1744
+ x1 = ov_opset.convert(x1, float_dtype)
1745
+ if x2.element_type.is_integral():
1746
+ x2 = ov_opset.convert(x2, float_dtype)
1747
+
1748
+ # Get the output nodes properly
1749
+ max_val_node = ov_opset.maximum(x1, x2)
1750
+ max_val = max_val_node.output(0)
1751
+
1752
+ # Compute absolute difference
1753
+ sub_node = ov_opset.subtract(x1, x2)
1754
+ abs_diff_node = ov_opset.abs(sub_node.output(0))
1755
+ abs_diff = abs_diff_node.output(0)
1756
+
1757
+ # Compute negative absolute difference and its exponential
1758
+ neg_abs_diff_node = ov_opset.negative(abs_diff)
1759
+ neg_abs_diff = neg_abs_diff_node.output(0)
1760
+ exp_neg_abs_node = ov_opset.exp(neg_abs_diff)
1761
+ exp_neg_abs = exp_neg_abs_node.output(0)
1762
+
1763
+ # Get the element type from the node, not the output
1764
+ element_type = exp_neg_abs_node.get_element_type()
1765
+ one_node = ov_opset.constant(1, element_type)
1766
+ one = one_node.output(0)
1767
+
1768
+ # Compute log term
1769
+ one_plus_exp_node = ov_opset.add(one, exp_neg_abs)
1770
+ one_plus_exp = one_plus_exp_node.output(0)
1771
+ log_term_node = ov_opset.log(one_plus_exp)
1772
+ log_term = log_term_node.output(0)
1773
+
1774
+ # Final result
1775
+ result_node = ov_opset.add(max_val, log_term)
1776
+ result = result_node.output(0)
1777
+
1778
+ return OpenVINOKerasTensor(result)
1779
+
1780
+
1781
+ def logaddexp2(x1, x2):
1063
1782
  raise NotImplementedError(
1064
- "`logaddexp` is not supported with openvino backend"
1783
+ "`logaddexp2` is not supported with openvino backend"
1065
1784
  )
1066
1785
 
1067
1786
 
@@ -1088,10 +1807,30 @@ def logical_or(x1, x2):
1088
1807
 
1089
1808
 
1090
1809
  def logspace(start, stop, num=50, endpoint=True, base=10, dtype=None, axis=0):
1091
- raise NotImplementedError(
1092
- "`logspace` is not supported with openvino backend"
1810
+ linear_samples = linspace(
1811
+ start=start,
1812
+ stop=stop,
1813
+ num=num,
1814
+ endpoint=endpoint,
1815
+ retstep=False,
1816
+ dtype=dtype,
1817
+ axis=axis,
1093
1818
  )
1094
1819
 
1820
+ if dtype is None:
1821
+ output_type = OPENVINO_DTYPES[config.floatx()]
1822
+ else:
1823
+ output_type = OPENVINO_DTYPES[dtype]
1824
+
1825
+ linear_output = get_ov_output(linear_samples)
1826
+ base_tensor = get_ov_output(base)
1827
+
1828
+ base_tensor = ov_opset.convert(base_tensor, output_type).output(0)
1829
+
1830
+ result = ov_opset.power(base_tensor, linear_output).output(0)
1831
+
1832
+ return OpenVINOKerasTensor(result)
1833
+
1095
1834
 
1096
1835
  def maximum(x1, x2):
1097
1836
  x1 = get_ov_output(x1)
@@ -1101,7 +1840,138 @@ def maximum(x1, x2):
1101
1840
 
1102
1841
 
1103
1842
  def median(x, axis=None, keepdims=False):
1104
- raise NotImplementedError("`median` is not supported with openvino backend")
1843
+ x = get_ov_output(x)
1844
+ x_shape = x.get_partial_shape()
1845
+ rank = x_shape.rank.get_length()
1846
+
1847
+ if rank == 0:
1848
+ return OpenVINOKerasTensor(x)
1849
+
1850
+ # Handle axis=None by flattening the input
1851
+ flattened_all = False
1852
+ if axis is None:
1853
+ x = ov_opset.reshape(x, [-1], False).output(0)
1854
+ axis = 0
1855
+ original_rank = rank
1856
+ rank = 1
1857
+ flattened_all = True
1858
+ else:
1859
+ # Handle tuple axis - for median, we only support single axis
1860
+ if isinstance(axis, (tuple, list)):
1861
+ if len(axis) != 1:
1862
+ raise ValueError("median only supports single axis reduction")
1863
+ axis = axis[0]
1864
+
1865
+ # Handle negative axis
1866
+ if axis < 0:
1867
+ axis = rank + axis
1868
+ original_rank = rank
1869
+
1870
+ # Get the size of the dimension to sort
1871
+ shape_tensor = ov_opset.shape_of(x, output_type=Type.i32).output(0)
1872
+ k = ov_opset.gather(
1873
+ shape_tensor,
1874
+ ov_opset.constant([axis], Type.i32).output(0),
1875
+ ov_opset.constant(0, Type.i32).output(0),
1876
+ ).output(0)
1877
+
1878
+ # Convert k to a scalar value
1879
+ k_scalar = ov_opset.squeeze(k, [0]).output(0)
1880
+
1881
+ # Use topk with k=size_of_axis to get all elements sorted
1882
+ topk_outputs = ov_opset.topk(
1883
+ x, k=k_scalar, axis=axis, mode="min", sort="value", stable=True
1884
+ )
1885
+
1886
+ # Get the sorted values
1887
+ sorted_values = topk_outputs.output(0)
1888
+
1889
+ # Convert to float for median calculation
1890
+ x1_type = ov_to_keras_type(sorted_values.get_element_type())
1891
+ result_type = dtypes.result_type(x1_type, float)
1892
+ result_type = OPENVINO_DTYPES[result_type]
1893
+ sorted_values = ov_opset.convert(sorted_values, result_type).output(0)
1894
+
1895
+ # Calculate median indices
1896
+ # For odd length: median_idx = (k-1) // 2
1897
+ # For even length: we need indices (k//2 - 1) and k//2, then average
1898
+
1899
+ k_minus_1 = ov_opset.subtract(
1900
+ k_scalar, ov_opset.constant(1, Type.i32).output(0)
1901
+ ).output(0)
1902
+ k_div_2 = ov_opset.divide(
1903
+ k_scalar, ov_opset.constant(2, Type.i32).output(0)
1904
+ ).output(0)
1905
+ k_minus_1_div_2 = ov_opset.divide(
1906
+ k_minus_1, ov_opset.constant(2, Type.i32).output(0)
1907
+ ).output(0)
1908
+
1909
+ # Check if k is odd
1910
+ k_mod_2 = ov_opset.mod(
1911
+ k_scalar, ov_opset.constant(2, Type.i32).output(0)
1912
+ ).output(0)
1913
+ is_odd = ov_opset.equal(
1914
+ k_mod_2, ov_opset.constant(1, Type.i32).output(0)
1915
+ ).output(0)
1916
+
1917
+ # For odd case: take the middle element
1918
+ odd_idx = k_minus_1_div_2
1919
+
1920
+ # For even case: take average of two middle elements
1921
+ even_idx1 = ov_opset.subtract(
1922
+ k_div_2, ov_opset.constant(1, Type.i32).output(0)
1923
+ ).output(0)
1924
+ even_idx2 = k_div_2
1925
+
1926
+ # Gather elements for both cases
1927
+ # Create gather indices tensor for the axis
1928
+ gather_indices_odd = ov_opset.unsqueeze(odd_idx, [0]).output(0)
1929
+ gather_indices_even1 = ov_opset.unsqueeze(even_idx1, [0]).output(0)
1930
+ gather_indices_even2 = ov_opset.unsqueeze(even_idx2, [0]).output(0)
1931
+
1932
+ # Gather the median elements
1933
+ odd_result = ov_opset.gather(
1934
+ sorted_values,
1935
+ gather_indices_odd,
1936
+ ov_opset.constant(axis, Type.i32).output(0),
1937
+ ).output(0)
1938
+ even_result1 = ov_opset.gather(
1939
+ sorted_values,
1940
+ gather_indices_even1,
1941
+ ov_opset.constant(axis, Type.i32).output(0),
1942
+ ).output(0)
1943
+ even_result2 = ov_opset.gather(
1944
+ sorted_values,
1945
+ gather_indices_even2,
1946
+ ov_opset.constant(axis, Type.i32).output(0),
1947
+ ).output(0)
1948
+
1949
+ # Average the two middle elements for even case
1950
+ even_sum = ov_opset.add(even_result1, even_result2).output(0)
1951
+ even_result = ov_opset.divide(
1952
+ even_sum, ov_opset.constant(2.0, result_type).output(0)
1953
+ ).output(0)
1954
+
1955
+ # Select between odd and even results
1956
+ median_result = ov_opset.select(is_odd, odd_result, even_result).output(0)
1957
+
1958
+ # Remove the gathered dimension (squeeze)
1959
+ median_result = ov_opset.squeeze(median_result, [axis]).output(0)
1960
+
1961
+ # Handle keepdims
1962
+ if keepdims:
1963
+ if flattened_all:
1964
+ # When axis=None, keepdims should restore all dimensions as 1
1965
+ ones_shape = ov_opset.constant(
1966
+ [1] * original_rank, Type.i32
1967
+ ).output(0)
1968
+ median_result = ov_opset.reshape(
1969
+ median_result, ones_shape, False
1970
+ ).output(0)
1971
+ else:
1972
+ median_result = ov_opset.unsqueeze(median_result, [axis]).output(0)
1973
+
1974
+ return OpenVINOKerasTensor(median_result)
1105
1975
 
1106
1976
 
1107
1977
  def meshgrid(*x, indexing="xy"):
@@ -1149,46 +2019,7 @@ def meshgrid(*x, indexing="xy"):
1149
2019
 
1150
2020
 
1151
2021
  def min(x, axis=None, keepdims=False, initial=None):
1152
- x = get_ov_output(x)
1153
- original_type = x.get_element_type()
1154
- x_type = original_type
1155
- x_shape = x.get_partial_shape().to_shape()
1156
-
1157
- is_bool = x_type == Type.boolean
1158
- if is_bool:
1159
- x = ov_opset.convert(x, Type.i32).output(0)
1160
- x_type = Type.i32
1161
-
1162
- if isinstance(axis, tuple) and len(axis) == 0:
1163
- return OpenVINOKerasTensor(x)
1164
-
1165
- if axis is None:
1166
- flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
1167
- x = ov_opset.reshape(x, flatten_shape, False).output(0)
1168
- axis = 0
1169
-
1170
- if isinstance(axis, tuple):
1171
- axis = list(axis)
1172
-
1173
- axis_const = ov_opset.constant(axis, Type.i32).output(0)
1174
- min_result = ov_opset.reduce_min(x, axis_const, keepdims).output(0)
1175
-
1176
- if initial is not None:
1177
- initial_tensor = ov_opset.constant(initial, x_type).output(0)
1178
- min_result = ov_opset.minimum(min_result, initial_tensor).output(0)
1179
-
1180
- if keepdims:
1181
- result_shape = [1] * len(x_shape)
1182
- min_result = ov_opset.reshape(
1183
- min_result,
1184
- ov_opset.constant(result_shape, Type.i32).output(0),
1185
- False,
1186
- ).output(0)
1187
-
1188
- if is_bool:
1189
- min_result = ov_opset.convert(min_result, Type.boolean).output(0)
1190
-
1191
- return OpenVINOKerasTensor(min_result)
2022
+ return _compute_extrema(x, "min", axis, keepdims, initial)
1192
2023
 
1193
2024
 
1194
2025
  def minimum(x1, x2):
@@ -1339,7 +2170,9 @@ def pad(x, pad_width, mode="constant", constant_values=None):
1339
2170
  "`pad` operation supports only scalar pad value "
1340
2171
  "in constant mode by openvino backend"
1341
2172
  )
1342
- pad_value = constant_values
2173
+ pad_value = ov_opset.constant(
2174
+ constant_values, x.get_element_type()
2175
+ ).output(0)
1343
2176
 
1344
2177
  # split pad_width into two tensors pads_begin and pads_end
1345
2178
  pads_begin = []
@@ -1355,7 +2188,26 @@ def pad(x, pad_width, mode="constant", constant_values=None):
1355
2188
 
1356
2189
 
1357
2190
  def prod(x, axis=None, keepdims=False, dtype=None):
1358
- raise NotImplementedError("`prod` is not supported with openvino backend")
2191
+ x = get_ov_output(x)
2192
+
2193
+ # If a specific dtype is requested, cast the input to that dtype.
2194
+ if dtype is not None:
2195
+ ov_dtype = OPENVINO_DTYPES[standardize_dtype(dtype)]
2196
+ x = ov_opset.convert(x, ov_dtype).output(0)
2197
+ # Otherwise, apply dtype promotion rules before reduction.
2198
+ else:
2199
+ x = _upcast_type_if_needed(x)
2200
+ x, axis = _resolve_axis(x, axis)
2201
+ if axis is None:
2202
+ return OpenVINOKerasTensor(x)
2203
+ # Compute the product
2204
+ result = ov_opset.reduce_prod(x, axis, keepdims).output(0)
2205
+
2206
+ return OpenVINOKerasTensor(result)
2207
+
2208
+
2209
+ def ptp(x, axis=None, keepdims=False):
2210
+ raise NotImplementedError("`ptp` is not supported with openvino backend")
1359
2211
 
1360
2212
 
1361
2213
  def quantile(x, q, axis=None, method="linear", keepdims=False):
@@ -1451,7 +2303,17 @@ def reshape(x, newshape):
1451
2303
 
1452
2304
 
1453
2305
  def roll(x, shift, axis=None):
1454
- raise NotImplementedError("`roll` is not supported with openvino backend")
2306
+ x = get_ov_output(x)
2307
+ if axis is not None:
2308
+ result = ov_opset.roll(x, shift, axis).output(0)
2309
+ else:
2310
+ output_shape = ov_opset.shape_of(x).output(0)
2311
+ flattened = ov_opset.reshape(
2312
+ x, ov_opset.constant([-1], Type.i32), False
2313
+ ).output(0)
2314
+ result = ov_opset.roll(flattened, shift, 0).output(0)
2315
+ result = ov_opset.reshape(result, output_shape, False).output(0)
2316
+ return OpenVINOKerasTensor(result)
1455
2317
 
1456
2318
 
1457
2319
  def sign(x):
@@ -1484,11 +2346,54 @@ def sinh(x):
1484
2346
 
1485
2347
 
1486
2348
  def size(x):
1487
- raise NotImplementedError("`size` is not supported with openvino backend")
2349
+ x = get_ov_output(x)
2350
+ shape_tensor = ov_opset.shape_of(x, output_type=Type.i64)
2351
+ final_size = ov_opset.reduce_prod(
2352
+ shape_tensor,
2353
+ ov_opset.constant([0], Type.i64),
2354
+ keep_dims=False,
2355
+ )
2356
+ return OpenVINOKerasTensor(final_size.output(0))
1488
2357
 
1489
2358
 
1490
2359
  def sort(x, axis=-1):
1491
- raise NotImplementedError("`sort` is not supported with openvino backend")
2360
+ x = get_ov_output(x)
2361
+ x_shape = x.get_partial_shape()
2362
+ rank = x_shape.rank.get_length()
2363
+
2364
+ if rank == 0:
2365
+ return OpenVINOKerasTensor(x)
2366
+
2367
+ # Handle axis=None by flattening the input
2368
+ if axis is None:
2369
+ x = ov_opset.reshape(
2370
+ x, ov_opset.constant([-1], Type.i32), False
2371
+ ).output(0)
2372
+ axis = 0
2373
+ # Handle negative axis
2374
+ elif axis < 0:
2375
+ axis = rank + axis
2376
+
2377
+ # Get the size of the dimension to sort
2378
+ shape_tensor = ov_opset.shape_of(x, output_type=Type.i32).output(0)
2379
+ k = ov_opset.gather(
2380
+ shape_tensor,
2381
+ ov_opset.constant([axis], Type.i32).output(0),
2382
+ ov_opset.constant(0, Type.i32).output(0),
2383
+ ).output(0)
2384
+
2385
+ # Convert k to a scalar value
2386
+ k_scalar = ov_opset.squeeze(k, ov_opset.constant([0], Type.i32)).output(0)
2387
+
2388
+ # Use topk with k=size_of_axis to get all elements sorted
2389
+ topk_outputs = ov_opset.topk(
2390
+ x, k=k_scalar, axis=axis, mode="min", sort="value", stable=True
2391
+ )
2392
+
2393
+ # Get the sorted values
2394
+ sorted_values = topk_outputs.output(0)
2395
+
2396
+ return OpenVINOKerasTensor(sorted_values)
1492
2397
 
1493
2398
 
1494
2399
  def split(x, indices_or_sections, axis=0):
@@ -1537,6 +2442,37 @@ def split(x, indices_or_sections, axis=0):
1537
2442
  )
1538
2443
 
1539
2444
 
2445
+ def array_split(x, indices_or_sections, axis=0):
2446
+ original_shape = x.shape
2447
+ x = get_ov_output(x)
2448
+
2449
+ num_splits_val = indices_or_sections
2450
+ total_size = original_shape[axis]
2451
+ if total_size is None:
2452
+ raise ValueError(
2453
+ f"Cannot use array_split with static Python logic on dynamic axis. "
2454
+ f"Axis {axis} has unknown dimension for shape {original_shape}."
2455
+ )
2456
+
2457
+ base_size = total_size // num_splits_val
2458
+ remainder = total_size % num_splits_val
2459
+
2460
+ split_lengths = [base_size + 1] * remainder + [base_size] * (
2461
+ num_splits_val - remainder
2462
+ )
2463
+ split_lengths_tensor = ov_opset.constant(
2464
+ split_lengths, dtype=Type.i64
2465
+ ).output(0)
2466
+
2467
+ axis_tensor = ov_opset.constant(axis, dtype=Type.i32).output(0)
2468
+ splits = ov_opset.variadic_split(x, axis_tensor, split_lengths_tensor)
2469
+
2470
+ result = []
2471
+ for i in range(num_splits_val):
2472
+ result.append(OpenVINOKerasTensor(splits.output(i)))
2473
+ return result
2474
+
2475
+
1540
2476
  def stack(x, axis=0):
1541
2477
  if isinstance(x, tuple):
1542
2478
  x = list(x)
@@ -1553,28 +2489,26 @@ def stack(x, axis=0):
1553
2489
 
1554
2490
 
1555
2491
  def std(x, axis=None, keepdims=False):
1556
- x = get_ov_output(x)
1557
- if axis is None:
1558
- flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
1559
- x = ov_opset.reshape(x, flatten_shape, False).output(0)
1560
- axis = 0
1561
- axis = ov_opset.constant(axis, Type.i32).output(0)
1562
- # The variance is computed using $Var = E[|x|^2] - |E[x]|^2$, It is faster
1563
- # but less numerically stable.
1564
- mean = ov_opset.reduce_mean(x, axis, keepdims).output(0)
1565
- const_two = ov_opset.constant(2, x.get_element_type()).output(0)
1566
- squared_x = ov_opset.power(x, const_two).output(0)
1567
- squared_mean = ov_opset.power(mean, const_two).output(0)
1568
- squared_x_mean = ov_opset.reduce_mean(squared_x, axis, keepdims)
1569
- variance = ov_opset.subtract(squared_x_mean, squared_mean).output(0)
1570
- std_var = OpenVINOKerasTensor(ov_opset.sqrt(variance).output(0))
1571
- return std_var
2492
+ var_x = var(x, axis, keepdims)
2493
+ std_dev = ov_opset.sqrt(var_x).output(0)
2494
+ return OpenVINOKerasTensor(std_dev)
1572
2495
 
1573
2496
 
1574
2497
  def swapaxes(x, axis1, axis2):
1575
- raise NotImplementedError(
1576
- "`swapaxes` is not supported with openvino backend"
1577
- )
2498
+ x = get_ov_output(x)
2499
+ x_shape = x.get_partial_shape()
2500
+ if x_shape.rank.is_dynamic:
2501
+ raise ValueError(
2502
+ "`swapaxes` does not support tensors with dynamic rank for the "
2503
+ "OpenVINO backend."
2504
+ )
2505
+ rank = x_shape.rank.get_length()
2506
+ axis1 = canonicalize_axis(axis1, rank)
2507
+ axis2 = canonicalize_axis(axis2, rank)
2508
+ axes = list(range(rank))
2509
+ axes[axis1], axes[axis2] = axes[axis2], axes[axis1]
2510
+ result = ov_opset.transpose(x, ov_opset.constant(axes, Type.i32))
2511
+ return OpenVINOKerasTensor(result.output(0))
1578
2512
 
1579
2513
 
1580
2514
  def take(x, indices, axis=None):
@@ -1693,7 +2627,8 @@ def tile(x, repeats):
1693
2627
 
1694
2628
 
1695
2629
  def trace(x, offset=0, axis1=0, axis2=1):
1696
- raise NotImplementedError("`trace` is not supported with openvino backend")
2630
+ x = diagonal(x, offset=offset, axis1=axis1, axis2=axis2)
2631
+ return sum(x, axis=-1)
1697
2632
 
1698
2633
 
1699
2634
  def tri(N, M=None, k=0, dtype=None):
@@ -1788,7 +2723,20 @@ def triu(x, k=0):
1788
2723
 
1789
2724
 
1790
2725
  def vdot(x1, x2):
1791
- raise NotImplementedError("`vdot` is not supported with openvino backend")
2726
+ element_type = None
2727
+ if isinstance(x1, OpenVINOKerasTensor):
2728
+ element_type = x1.output.get_element_type()
2729
+ if isinstance(x2, OpenVINOKerasTensor):
2730
+ element_type = x2.output.get_element_type()
2731
+ x1 = get_ov_output(x1, element_type)
2732
+ x2 = get_ov_output(x2, element_type)
2733
+ x1, x2 = _align_operand_types(x1, x2, "vdot()")
2734
+ if x1.get_partial_shape().rank == 0 or x2.get_partial_shape().rank == 0:
2735
+ return OpenVINOKerasTensor(ov_opset.multiply(x1, x2).output(0))
2736
+ flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
2737
+ x1 = ov_opset.reshape(x1, flatten_shape, False).output(0)
2738
+ x2 = ov_opset.reshape(x2, flatten_shape, False).output(0)
2739
+ return OpenVINOKerasTensor(ov_opset.matmul(x1, x2, False, False).output(0))
1792
2740
 
1793
2741
 
1794
2742
  def vstack(xs):
@@ -1874,14 +2822,27 @@ def negative(x):
1874
2822
  return OpenVINOKerasTensor(ov_opset.negative(x).output(0))
1875
2823
 
1876
2824
 
2825
+ def nextafter(x1, x2):
2826
+ raise NotImplementedError(
2827
+ "`nextafter` is not supported with openvino backend"
2828
+ )
2829
+
2830
+
1877
2831
  def square(x):
1878
2832
  x = get_ov_output(x)
2833
+ x_type = x.get_element_type()
2834
+ if x_type == Type.boolean:
2835
+ x = ov_opset.convert(x, Type.i32).output(0)
1879
2836
  const_two = ov_opset.constant(2, x.get_element_type()).output(0)
1880
2837
  return OpenVINOKerasTensor(ov_opset.power(x, const_two).output(0))
1881
2838
 
1882
2839
 
1883
2840
  def sqrt(x):
1884
2841
  x = get_ov_output(x)
2842
+ x_type = x.get_element_type()
2843
+ if x_type.is_integral():
2844
+ ov_type = OPENVINO_DTYPES[config.floatx()]
2845
+ x = ov_opset.convert(x, ov_type).output(0)
1885
2846
  return OpenVINOKerasTensor(ov_opset.sqrt(x).output(0))
1886
2847
 
1887
2848
 
@@ -1892,6 +2853,8 @@ def squeeze(x, axis=None):
1892
2853
  for idx, dim in enumerate(x.get_partial_shape()):
1893
2854
  if dim == 1:
1894
2855
  axis.append(idx)
2856
+ if isinstance(axis, tuple):
2857
+ axis = list(axis)
1895
2858
  axis = ov_opset.constant(axis, Type.i32).output(0)
1896
2859
  return OpenVINOKerasTensor(ov_opset.squeeze(x, axis).output(0))
1897
2860
 
@@ -1916,20 +2879,135 @@ def transpose(x, axes=None):
1916
2879
  return OpenVINOKerasTensor(ov_opset.transpose(x, axes).output(0))
1917
2880
 
1918
2881
 
2882
+ def _helper_trapezoid(y, axis):
2883
+ rank = y.get_partial_shape().rank.get_length()
2884
+ strides = ov_opset.constant([1] * rank, dtype=Type.i64).output(0)
2885
+
2886
+ # y[:-1]
2887
+ begin1 = ov_opset.constant([0] * rank, dtype=Type.i64).output(0)
2888
+ end1_list = [0] * rank
2889
+ end1_list[axis] = -1
2890
+ end1 = ov_opset.constant(end1_list, dtype=Type.i64).output(0)
2891
+ begin_mask1 = [1] * rank
2892
+ begin_mask1[axis] = 0
2893
+ end_mask1 = [1] * rank
2894
+ end_mask1[axis] = 0
2895
+ y1 = ov_opset.strided_slice(
2896
+ y, begin1, end1, strides, begin_mask1, end_mask1
2897
+ ).output(0)
2898
+
2899
+ # y[1:]
2900
+ begin2_list = [0] * rank
2901
+ begin2_list[axis] = 1
2902
+ begin2 = ov_opset.constant(begin2_list, dtype=Type.i64).output(0)
2903
+ end2 = ov_opset.constant([0] * rank, dtype=Type.i64).output(0)
2904
+ begin_mask2 = [1] * rank
2905
+ begin_mask2[axis] = 0
2906
+ end_mask2 = [1] * rank
2907
+ y2 = ov_opset.strided_slice(
2908
+ y, begin2, end2, strides, begin_mask2, end_mask2
2909
+ ).output(0)
2910
+
2911
+ return y1, y2
2912
+
2913
+
2914
+ def trapezoid(y, x=None, dx=1.0, axis=-1):
2915
+ y = get_ov_output(y)
2916
+ y_type = y.get_element_type()
2917
+
2918
+ if y_type.is_integral():
2919
+ y_type = OPENVINO_DTYPES[config.floatx()]
2920
+ y = ov_opset.convert(y, y_type).output(0)
2921
+
2922
+ y1, y2 = _helper_trapezoid(y, axis)
2923
+ y_final = ov_opset.add(y1, y2).output(0)
2924
+ const_two = ov_opset.constant(2, dtype=y_type).output(0)
2925
+ y_final = ov_opset.divide(y_final, const_two).output(0)
2926
+
2927
+ if x is not None:
2928
+ x = get_ov_output(x)
2929
+ x_type = x.get_element_type()
2930
+ if x_type.is_integral():
2931
+ x_type = OPENVINO_DTYPES[config.floatx()]
2932
+ x = ov_opset.convert(x, x_type).output(0)
2933
+
2934
+ x1, x2 = _helper_trapezoid(x, axis)
2935
+ x_final = ov_opset.subtract(x2, x1).output(0)
2936
+
2937
+ else:
2938
+ x_final = ov_opset.constant(dx, dtype=y_type).output(0)
2939
+
2940
+ result = ov_opset.multiply(y_final, x_final).output(0)
2941
+ const_axis = ov_opset.constant([axis], Type.i64).output(0)
2942
+ result = ov_opset.reduce_sum(result, const_axis, False).output(0)
2943
+
2944
+ return OpenVINOKerasTensor(result)
2945
+
2946
+
2947
+ def vander(x, N=None, increasing=False):
2948
+ x = get_ov_output(x)
2949
+ x_type = x.get_element_type()
2950
+
2951
+ shape_x = ov_opset.shape_of(x, Type.i64).output(0)
2952
+
2953
+ const_zero_1D = ov_opset.constant([0], dtype=Type.i64).output(0)
2954
+ const_zero = ov_opset.constant(0, dtype=Type.i64).output(0)
2955
+ const_one = ov_opset.constant(1, dtype=Type.i64).output(0)
2956
+ const_mone = ov_opset.constant(-1, dtype=Type.i64).output(0)
2957
+
2958
+ if N is None:
2959
+ const_N = ov_opset.squeeze(shape_x, const_zero_1D).output(0)
2960
+ const_N_1D = shape_x
2961
+ else:
2962
+ const_N = ov_opset.constant(N, Type.i64).output(0)
2963
+ const_N_1D = ov_opset.constant([N], Type.i64).output(0)
2964
+
2965
+ const_N_minus_one = ov_opset.subtract(const_N, const_one).output(0)
2966
+ if increasing:
2967
+ powers = ov_opset.range(const_zero, const_N, const_one, x_type).output(
2968
+ 0
2969
+ )
2970
+ else:
2971
+ powers = ov_opset.range(
2972
+ const_N_minus_one, const_mone, const_mone, x_type
2973
+ ).output(0)
2974
+
2975
+ target_shape = ov_opset.concat([shape_x, const_N_1D], 0).output(0)
2976
+
2977
+ const_one_1D = ov_opset.constant([1], dtype=Type.i64).output(0)
2978
+
2979
+ powers = ov_opset.unsqueeze(powers, const_zero_1D).output(0)
2980
+ x = ov_opset.unsqueeze(x, const_one_1D).output(0)
2981
+
2982
+ result = ov_opset.broadcast(x, target_shape).output(0)
2983
+
2984
+ result = ov_opset.power(result, powers).output(0)
2985
+
2986
+ return OpenVINOKerasTensor(result)
2987
+
2988
+
1919
2989
  def var(x, axis=None, keepdims=False):
1920
2990
  x = get_ov_output(x)
2991
+ x_type = x.get_element_type()
2992
+ x, axis = _resolve_axis(x, axis)
2993
+
2994
+ work_dtype = Type.f64 if x_type.is_integral() else x.get_element_type()
2995
+ if x_type.is_integral():
2996
+ x = ov_opset.convert(x, work_dtype).output(0)
1921
2997
  if axis is None:
1922
- flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
1923
- x = ov_opset.reshape(x, flatten_shape, False).output(0)
1924
- axis = 0
1925
- axis = ov_opset.constant(axis, Type.i32).output(0)
2998
+ const_zero = ov_opset.constant(0, dtype=work_dtype).output(0)
2999
+ return OpenVINOKerasTensor(
3000
+ ov_opset.broadcast(const_zero, ov_opset.shape_of(x)).output(0)
3001
+ )
1926
3002
  # The variance is computed using $Var = E[|x|^2] - |E[x]|^2$, It is faster
1927
3003
  # but less numerically stable.
1928
3004
  mean = ov_opset.reduce_mean(x, axis, keepdims).output(0)
1929
- const_two = ov_opset.constant(2, x.get_element_type()).output(0)
3005
+ const_two = ov_opset.constant(2, work_dtype).output(0)
3006
+
1930
3007
  squared_x = ov_opset.power(x, const_two).output(0)
1931
3008
  squared_mean = ov_opset.power(mean, const_two).output(0)
1932
- squared_x_mean = ov_opset.reduce_mean(squared_x, axis, keepdims)
3009
+
3010
+ squared_x_mean = ov_opset.reduce_mean(squared_x, axis, keepdims).output(0)
1933
3011
  variance = OpenVINOKerasTensor(
1934
3012
  ov_opset.subtract(squared_x_mean, squared_mean).output(0)
1935
3013
  )
@@ -1938,22 +3016,46 @@ def var(x, axis=None, keepdims=False):
1938
3016
 
1939
3017
  def sum(x, axis=None, keepdims=False):
1940
3018
  x = get_ov_output(x)
3019
+ x, axis = _resolve_axis(x, axis)
1941
3020
  if axis is None:
1942
- flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
1943
- x = ov_opset.reshape(x, flatten_shape, False).output(0)
1944
- axis = 0
1945
- axis = ov_opset.constant(axis, Type.i32).output(0)
1946
- return OpenVINOKerasTensor(ov_opset.reduce_sum(x, axis, keepdims).output(0))
3021
+ return OpenVINOKerasTensor(x)
3022
+ x = _upcast_type_if_needed(x)
3023
+ summed_value = ov_opset.reduce_sum(x, axis, keepdims).output(0)
3024
+ return OpenVINOKerasTensor(summed_value)
1947
3025
 
1948
3026
 
1949
3027
  def eye(N, M=None, k=0, dtype=None):
1950
- raise NotImplementedError("`eye` is not supported with openvino backend")
3028
+ dtype = standardize_dtype(dtype) or config.floatx()
3029
+ ov_type = OPENVINO_DTYPES[dtype]
3030
+ if M is None:
3031
+ M = N
3032
+ return OpenVINOKerasTensor(
3033
+ ov_opset.eye(
3034
+ ov_opset.constant(N, Type.i32),
3035
+ ov_opset.constant(M, Type.i32),
3036
+ ov_opset.constant(k, Type.i32),
3037
+ output_type=ov_type,
3038
+ ).output(0)
3039
+ )
1951
3040
 
1952
3041
 
1953
3042
  def floor_divide(x1, x2):
1954
- raise NotImplementedError(
1955
- "`floor_divide` is not supported with openvino backend"
1956
- )
3043
+ x1_output = get_ov_output(x1)
3044
+ x2_output = get_ov_output(x2)
3045
+ if x1_output.get_element_type() == Type.boolean:
3046
+ x1_output = ov_opset.convert(x1_output, Type.i32).output(0)
3047
+ if isinstance(x2, (int, float)):
3048
+ if x1_output.get_element_type().is_integral() and isinstance(x2, float):
3049
+ ov_type = OPENVINO_DTYPES[config.floatx()]
3050
+ else:
3051
+ ov_type = x1_output.get_element_type()
3052
+ x1 = ov_opset.convert(x1_output, ov_type).output(0)
3053
+ x2 = ov_opset.convert(x2_output, ov_type).output(0)
3054
+ else:
3055
+ x1, x2 = _align_operand_types(x1_output, x2_output, "floor_divide()")
3056
+ div = ov_opset.divide(x1, x2).output(0)
3057
+ floored_div = ov_opset.floor(div).output(0)
3058
+ return OpenVINOKerasTensor(floored_div)
1957
3059
 
1958
3060
 
1959
3061
  def logical_xor(x1, x2):
@@ -1965,16 +3067,88 @@ def logical_xor(x1, x2):
1965
3067
 
1966
3068
 
1967
3069
  def corrcoef(x):
1968
- raise NotImplementedError(
1969
- "`corrcoef` is not supported with openvino backend"
1970
- )
3070
+ x_ov = get_ov_output(x)
3071
+ x_type = x_ov.get_element_type()
3072
+ ov_type = x_type
3073
+
3074
+ if x_type.is_integral():
3075
+ ov_type = OPENVINO_DTYPES[config.floatx()]
3076
+ x_ov = ov_opset.convert(x_ov, ov_type).output(0)
3077
+
3078
+ const_one = ov_opset.constant(1, dtype=Type.i64).output(0)
3079
+ const_two = ov_opset.constant(2, dtype=ov_type).output(0)
3080
+
3081
+ mean = ov_opset.reduce_mean(x_ov, const_one, True).output(0)
3082
+ x_ov = ov_opset.subtract(x_ov, mean).output(0)
3083
+
3084
+ cov = ov_opset.matmul(x_ov, x_ov, False, True).output(0)
3085
+ xsqr = ov_opset.power(x_ov, const_two).output(0)
3086
+ xvar = ov_opset.reduce_sum(xsqr, const_one, True).output(0)
3087
+ xstd = ov_opset.sqrt(xvar).output(0)
3088
+
3089
+ den = ov_opset.matmul(xstd, xstd, False, True).output(0)
3090
+
3091
+ result = ov_opset.divide(cov, den).output(0)
3092
+
3093
+ return OpenVINOKerasTensor(result)
1971
3094
 
1972
3095
 
1973
3096
  def correlate(x1, x2, mode="valid"):
1974
- raise NotImplementedError(
1975
- "`correlate` is not supported with openvino backend"
3097
+ x1 = get_ov_output(x1)
3098
+ x2 = get_ov_output(x2)
3099
+ x1_type = x1.get_element_type()
3100
+ x2_type = x2.get_element_type()
3101
+ x1_type = ov_to_keras_type(x1_type)
3102
+ x2_type = ov_to_keras_type(x2_type)
3103
+ result_type = dtypes.result_type(x1_type, x2_type, float)
3104
+
3105
+ result_type = OPENVINO_DTYPES[result_type]
3106
+ x1 = ov_opset.convert(x1, result_type).output(0)
3107
+ x2 = ov_opset.convert(x2, result_type).output(0)
3108
+
3109
+ shape_filter = ov_opset.shape_of(x2, Type.i64).output(0)
3110
+ const_two = ov_opset.constant(2, Type.f64).output(0)
3111
+ const_one = ov_opset.constant(1, Type.i64).output(0)
3112
+ const_zero = ov_opset.constant(0, result_type).output(0)
3113
+ shape_filter_minus_one = ov_opset.subtract(shape_filter, const_one).output(
3114
+ 0
1976
3115
  )
1977
3116
 
3117
+ # padding x1
3118
+ if mode == "valid":
3119
+ pass
3120
+
3121
+ elif mode == "same":
3122
+ shape_minus_one_float = ov_opset.convert(
3123
+ shape_filter_minus_one, Type.f64
3124
+ ).output(0)
3125
+
3126
+ right = ov_opset.divide(shape_minus_one_float, const_two).output(0)
3127
+ left = ov_opset.ceil(right).output(0)
3128
+ right = ov_opset.floor(right).output(0)
3129
+ left = ov_opset.convert(left, Type.i64).output(0)
3130
+ right = ov_opset.convert(right, Type.i64).output(0)
3131
+ x1 = ov_opset.pad(x1, left, right, "constant", const_zero).output(0)
3132
+
3133
+ elif mode == "full":
3134
+ pad = shape_filter_minus_one
3135
+ x1 = ov_opset.pad(x1, pad, pad, "constant", const_zero).output(0)
3136
+
3137
+ else:
3138
+ raise ValueError(
3139
+ f"mode: {mode} not available chose from valid, same, full."
3140
+ )
3141
+
3142
+ axes = ov_opset.constant([0, 1], dtype=Type.i64).output(0)
3143
+ x2 = ov_opset.unsqueeze(x2, axes).output(0)
3144
+ x1 = ov_opset.unsqueeze(x1, axes).output(0)
3145
+
3146
+ result = ov_opset.convolution(x1, x2, [1], [0], [0], [1]).output(0)
3147
+
3148
+ result = ov_opset.squeeze(result, axes).output(0)
3149
+
3150
+ return OpenVINOKerasTensor(result)
3151
+
1978
3152
 
1979
3153
  def select(condlist, choicelist, default=0):
1980
3154
  raise NotImplementedError("`select` is not supported with openvino backend")