ndlike 0.2.2__tar.gz → 0.2.4__tar.gz

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 (27) hide show
  1. {ndlike-0.2.2/src/ndlike.egg-info → ndlike-0.2.4}/PKG-INFO +1 -1
  2. {ndlike-0.2.2 → ndlike-0.2.4}/pyproject.toml +1 -1
  3. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/jax_functions.py +30 -0
  4. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/jax_operators.py +2 -2
  5. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/protected_axis/jax.py +2 -2
  6. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/protected_axis/jax_functions.py +5 -2
  7. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/protected_axis/numpy.py +41 -32
  8. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/torch_operators.py +2 -2
  9. {ndlike-0.2.2 → ndlike-0.2.4/src/ndlike.egg-info}/PKG-INFO +1 -1
  10. {ndlike-0.2.2 → ndlike-0.2.4}/LICENSE +0 -0
  11. {ndlike-0.2.2 → ndlike-0.2.4}/README.md +0 -0
  12. {ndlike-0.2.2 → ndlike-0.2.4}/setup.cfg +0 -0
  13. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/__init__.py +0 -0
  14. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/array_like.py +0 -0
  15. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/jax_like.py +0 -0
  16. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/protected_axis/__init__.py +0 -0
  17. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/protected_axis/_common_functions.py +0 -0
  18. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/protected_axis/numpy_functions.py +0 -0
  19. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/protected_axis/torch.py +0 -0
  20. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/protected_axis/torch_functions.py +0 -0
  21. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/py.typed +0 -0
  22. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/torch_functions.py +0 -0
  23. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike/torch_like.py +0 -0
  24. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike.egg-info/SOURCES.txt +0 -0
  25. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike.egg-info/dependency_links.txt +0 -0
  26. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike.egg-info/requires.txt +0 -0
  27. {ndlike-0.2.2 → ndlike-0.2.4}/src/ndlike.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ndlike
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A toolkit for defining custom array types on top of NumPy, JAX, and PyTorch.
5
5
  Author-email: Clemens Damke <clemens@cortys.de>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ndlike"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "A toolkit for defining custom array types on top of NumPy, JAX, and PyTorch."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -318,6 +318,34 @@ def jax_divmod(
318
318
  return jnp.divmod(x1, x2) # ty: ignore[invalid-argument-type]
319
319
 
320
320
 
321
+ @jax_function_dispatch("x")
322
+ def jax_frexp(x: _JaxFamily | JaxArrayLike, /) -> tuple[jax.Array | _JaxFamily, jax.Array | _JaxFamily]:
323
+ """Decompose values into mantissas and exponents, mirroring ``jax.numpy.frexp``.
324
+
325
+ Args:
326
+ x: Input values.
327
+
328
+ Returns:
329
+ A tuple of floating-point mantissas and integer exponents for native
330
+ inputs, or the result supplied by a custom override.
331
+ """
332
+ return jnp.frexp(x) # ty: ignore[invalid-argument-type]
333
+
334
+
335
+ @jax_function_dispatch("x")
336
+ def jax_modf(x: _JaxFamily | JaxArrayLike, /) -> tuple[jax.Array | _JaxFamily, jax.Array | _JaxFamily]:
337
+ """Split values into fractional and integral parts, mirroring ``jax.numpy.modf``.
338
+
339
+ Args:
340
+ x: Input values.
341
+
342
+ Returns:
343
+ A tuple of fractional and integral parts for native inputs, or the result
344
+ supplied by a custom override.
345
+ """
346
+ return jnp.modf(x) # ty: ignore[invalid-argument-type]
347
+
348
+
321
349
  @jax_function_dispatch("x1", "x2")
322
350
  def jax_power(x1: _JaxFamily | JaxArrayLike, x2: _JaxFamily | JaxArrayLike, /) -> jax.Array | _JaxFamily:
323
351
  """Raise operands to powers elementwise, mirroring ``jax.numpy.power``.
@@ -966,6 +994,7 @@ __all__ = [
966
994
  "jax_equal",
967
995
  "jax_expand_dims",
968
996
  "jax_floor_divide",
997
+ "jax_frexp",
969
998
  "jax_function_dispatch",
970
999
  "jax_greater",
971
1000
  "jax_greater_equal",
@@ -976,6 +1005,7 @@ __all__ = [
976
1005
  "jax_matmul",
977
1006
  "jax_matrix_transpose",
978
1007
  "jax_mean",
1008
+ "jax_modf",
979
1009
  "jax_moveaxis",
980
1010
  "jax_multiply",
981
1011
  "jax_negative",
@@ -70,8 +70,8 @@ class JaxOperatorsMixin: # noqa: PLW1641
70
70
  Subclasses can define ``__array_ufunc__`` to override or disable that behavior.
71
71
  """
72
72
 
73
- # Prefer reflected operators in mixed NumPy infix expressions.
74
- __array_priority__ = 1000
73
+ # Let NumPy handle its own infix operations without forced deferral.
74
+ __array_priority__ = 0
75
75
 
76
76
  __add__ = _binary(jf.jax_add)
77
77
  __radd__ = _binary(jf.jax_add, reflected=True)
@@ -442,8 +442,8 @@ class JaxAxisProtected[J: JaxLike | jax.Array | np.ndarray](JaxLikeImplementatio
442
442
  operands: Original operands in expression order.
443
443
 
444
444
  Returns:
445
- The field values to validate and reconstruct. For ``divmod``, this
446
- hook is called separately for the quotient and remainder.
445
+ The field values to validate and reconstruct. For ``divmod``,
446
+ ``frexp``, and ``modf``, this hook is called separately for each output.
447
447
  """
448
448
  del func, operands
449
449
  return values
@@ -678,6 +678,8 @@ def _batch_comparison(values: dict[str, Any], template: JaxAxisProtectedInternal
678
678
  jf.jax_floor_divide,
679
679
  jf.jax_remainder,
680
680
  jf.jax_divmod,
681
+ jf.jax_frexp,
682
+ jf.jax_modf,
681
683
  jf.jax_power,
682
684
  jf.jax_matmul,
683
685
  jf.jax_bitwise_and,
@@ -721,9 +723,10 @@ def protected_operator_function(
721
723
  operation = jax.vmap(operation, in_axes=in_axes, out_axes=-1)
722
724
  results[name] = operation(*field_args)
723
725
 
726
+ multiple_outputs = func in (jf.jax_divmod, jf.jax_frexp, jf.jax_modf)
724
727
  result_sets = (
725
728
  tuple({name: value[index] for name, value in results.items()} for index in range(2))
726
- if func is jf.jax_divmod
729
+ if multiple_outputs
727
730
  else (results,)
728
731
  )
729
732
  reconstructed = []
@@ -734,4 +737,4 @@ def protected_operator_function(
734
737
  processed = owner._postprocess_elementwise_result(values, func=func, operands=args) # noqa: SLF001
735
738
  _validate_operator_result(processed, template)
736
739
  reconstructed.append(template.create(processed))
737
- return tuple(reconstructed) if func is jf.jax_divmod else reconstructed[0]
740
+ return tuple(reconstructed) if multiple_outputs else reconstructed[0]
@@ -377,7 +377,9 @@ class NumpyAxisProtected[T: NumpyLike | np.ndarray](NumpyLikeImplementation[T],
377
377
  else:
378
378
  out_values.append(None)
379
379
 
380
- if out is not None and len(protected_axes) != 1 and any(values is None for values in out_values):
380
+ if len(protected_axes) != 1 and any(
381
+ item is not None and values is None for item, values in zip(out_items, out_values, strict=True)
382
+ ):
381
383
  msg = "non-protected out is only supported for single-field protected objects."
382
384
  raise TypeError(msg)
383
385
 
@@ -398,7 +400,8 @@ class NumpyAxisProtected[T: NumpyLike | np.ndarray](NumpyLikeImplementation[T],
398
400
  raise TypeError(msg)
399
401
  return normalize_axis(axis, batch_ndim)
400
402
 
401
- results: dict[str, NumpyProtectedValue] = {}
403
+ output_count = ufunc.nout if method == "__call__" else 1
404
+ results: list[dict[str, NumpyProtectedValue]] = [{} for _ in range(output_count)]
402
405
  for name, axes_count in protected_axes.items():
403
406
  field_kwargs = dict(kwargs)
404
407
 
@@ -445,43 +448,49 @@ class NumpyAxisProtected[T: NumpyLike | np.ndarray](NumpyLikeImplementation[T],
445
448
 
446
449
  result = getattr(ufunc, method)(*field_input_items, **field_kwargs)
447
450
 
448
- if out is not None:
449
- continue
451
+ result_items = result if output_count > 1 else (result,)
452
+ for index, result_item in enumerate(result_items):
453
+ if out is not None and out_items[index] is not None:
454
+ continue
450
455
 
451
- if axes_count == 0 and not hasattr(result, "ndim"):
452
- result = np.asarray(result)
456
+ item = result_item
457
+ if axes_count == 0 and not hasattr(item, "ndim"):
458
+ item = np.asarray(item)
453
459
 
454
- result_ndim = value_ndim(result)
455
- result_shape = value_shape(result)
456
- original_shape = value_shape(self_values[name])
457
- if result_ndim < axes_count:
458
- msg = f"Ufunc operation removed protected trailing axes for field {name!r}."
459
- raise ValueError(msg)
460
- if protected_shape(result_shape, axes_count) != protected_shape(original_shape, axes_count):
461
- msg = f"Ufunc operation modified protected trailing axes for field {name!r}."
462
- raise ValueError(msg)
460
+ result_ndim = value_ndim(item)
461
+ result_shape = value_shape(item)
462
+ original_shape = value_shape(self_values[name])
463
+ if result_ndim < axes_count:
464
+ msg = f"Ufunc operation removed protected trailing axes for field {name!r}."
465
+ raise ValueError(msg)
466
+ if protected_shape(result_shape, axes_count) != protected_shape(original_shape, axes_count):
467
+ msg = f"Ufunc operation modified protected trailing axes for field {name!r}."
468
+ raise ValueError(msg)
469
+
470
+ if isinstance(item, np.ndarray):
471
+ item = self._postprocess_ufunc_result(item, ufunc=ufunc, method=method)
463
472
 
464
- if isinstance(result, np.ndarray):
465
- result = self._postprocess_ufunc_result(result, ufunc=ufunc, method=method)
473
+ results[index][name] = cast("NumpyProtectedValue", item)
466
474
 
467
- results[name] = cast("NumpyProtectedValue", result)
475
+ outputs: list[object] = []
476
+ for index, values in enumerate(results):
477
+ if out is not None and out_items[index] is not None:
478
+ outputs.append(out_items[index])
479
+ continue
468
480
 
469
- if out is not None:
470
- if method == "__call__":
471
- return out
472
- return out_items[0] if isinstance(out, tuple) else out
481
+ expected_batch_shape: tuple[int, ...] | None = None
482
+ for name, value in values.items():
483
+ axes_count = protected_axes[name]
484
+ current_batch_shape = batch_shape(value_shape(value), axes_count)
485
+ if expected_batch_shape is None:
486
+ expected_batch_shape = current_batch_shape
487
+ elif current_batch_shape != expected_batch_shape:
488
+ msg = "Ufunc operation produced inconsistent batch-shapes across protected fields."
489
+ raise ValueError(msg)
473
490
 
474
- expected_batch_shape: tuple[int, ...] | None = None
475
- for name, value in results.items():
476
- axes_count = protected_axes[name]
477
- current_batch_shape = batch_shape(value_shape(value), axes_count)
478
- if expected_batch_shape is None:
479
- expected_batch_shape = current_batch_shape
480
- elif current_batch_shape != expected_batch_shape:
481
- msg = "Ufunc operation produced inconsistent batch-shapes across protected fields."
482
- raise ValueError(msg)
491
+ outputs.append(self.with_protected_values(values))
483
492
 
484
- return self.with_protected_values(results)
493
+ return tuple(outputs) if output_count > 1 else outputs[0]
485
494
 
486
495
  def __array_function__(
487
496
  self,
@@ -66,8 +66,8 @@ class TorchOperatorsMixin: # noqa: PLW1641
66
66
  Subclasses can define ``__array_ufunc__`` to override or disable that behavior.
67
67
  """
68
68
 
69
- # Prefer reflected operators in mixed NumPy infix expressions.
70
- __array_priority__ = 1000
69
+ # Let NumPy handle its own infix operations without forced deferral.
70
+ __array_priority__ = 0
71
71
 
72
72
  __add__ = _binary(torch.add)
73
73
  __radd__ = _binary(torch.add, reflected=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ndlike
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A toolkit for defining custom array types on top of NumPy, JAX, and PyTorch.
5
5
  Author-email: Clemens Damke <clemens@cortys.de>
6
6
  License-Expression: MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes