Source code for quchip.utils.jax_utils
"""Tracer-detection and array-namespace dispatch helpers shared across quchip.
JAX is a required dependency of quchip (see ``pyproject.toml``), so these
helpers import it unconditionally.
"""
from __future__ import annotations
from typing import Any
from jax.core import Tracer
from jax.tree_util import tree_leaves
import jax.numpy as jnp
import numpy as np
[docs]
def contains_tracer(pytree: Any) -> bool:
"""Return ``True`` if *pytree* has any :class:`jax.core.Tracer` leaf.
Uses :func:`jax.tree_util.tree_leaves` so nested tuples/lists/dicts
and custom registered pytrees are traversed correctly.
Covers all built-in JAX transforms: ``jit``, ``vmap``, ``grad``,
``linearize``, ``pmap``, and their composition all produce
subclasses of :class:`jax.core.Tracer`.
"""
for leaf in tree_leaves(pytree):
if isinstance(leaf, Tracer):
return True
return False
[docs]
def array_namespace(array: Any) -> Any:
"""Return the array's namespace (``jax.numpy`` or ``numpy``).
Uses ``__array_namespace__`` when available, falling back to NumPy.
Single source of truth so dispatch logic stays consistent across
engine, control, and analysis layers.
"""
namespace = getattr(array, "__array_namespace__", None)
if callable(namespace):
return namespace()
return np
[docs]
def is_jax_namespace(xp: Any) -> bool:
"""Return ``True`` if *xp* is ``jax.numpy`` (by module name)."""
return getattr(xp, "__name__", "").startswith("jax")
[docs]
def is_jax_array(array: Any) -> bool:
"""Return ``True`` if *array* uses ``jax.numpy`` as its namespace."""
return is_jax_namespace(array_namespace(array))
[docs]
def select_array_module(prefer_jax: bool) -> Any:
"""Return ``jax.numpy`` if *prefer_jax*, else NumPy."""
return jnp if prefer_jax else np
[docs]
def maybe_concrete_scalar(value: Any) -> float | None:
"""Return a Python ``float`` if *value* is a concrete, real-valued 0-d scalar, else ``None``.
Used throughout device, drive, and envelope constructors to inspect a
parameter that *might* be a JAX tracer. Returns ``None`` — not a
``float`` or a raised error — for a JAX tracer, a non-scalar, or a
complex or otherwise non-float-convertible payload. Callers therefore
treat complex-valued input the same as traced input: the concrete
validation check is skipped rather than run against a lossy cast.
"""
if value is None:
return None
try:
array = np.asarray(value)
except Exception:
return None
if array.ndim != 0:
return None
try:
return float(array)
except (TypeError, ValueError):
# 0-d object arrays (e.g. an envelope or other non-numeric payload
# passed as a declared parameter) are not concrete scalars.
return None