def convert_like(tensor1, tensor2): """Convert a tensor to the same type as another. Args: tensor1 (tensor_like): tensor to convert tensor2 (tensor_like): tensor with corresponding type to convert to Returns: tensor_like: a tensor with the same shape, values, and dtype as ``tensor1`` and the same type as ``tensor2``. **Example** >>> x = np.array([1, 2]) >>> y = tf.Variable([3, 4]) >>> convert_like(x, y) <tf.Tensor: shape=(2,), dtype=int64, numpy=array([1, 2])> """ interface = get_interface(tensor2) if interface == "torch": dev = tensor2.device return np.asarray(tensor1, device=dev, like=interface) return np.asarray(tensor1, like=interface)
def _multi_dispatch(values): """Determines the correct framework to dispatch to given a sequence of tensor-like objects. Args: values (Sequence[tensor_like]): a sequence of tensor like objects Returns: str: the name of the interface To determine the framework to dispatch to, the following rules are applied: * Tensors that are incompatible (such as Torch and TensorFlow tensors) cannot both be present. * Autograd tensors *may* be present alongside Torch and TensorFlow tensors, but Torch and TensorFlow take precendence; the autograd arrays will be treated as non-differentiable NumPy arrays. A warning will be raised suggesting that vanilla NumPy be used instead. * Vanilla NumPy arrays can be used alongside other tensor objects; they will always be treated as non-differentiable constants. """ if "resource_variable" in getattr(values, "__module__", tuple()): values = np.asarray(values) interfaces = {get_interface(v) for v in values} if len(set(interfaces) - {"numpy", "autograd"}) > 1: # contains multiple non-autograd interfaces raise ValueError( "Tensors contain mixed types; cannot determine dispatch library") non_numpy_interfaces = set(interfaces) - {"numpy"} if len(non_numpy_interfaces) > 1: # contains autograd and another interface warnings.warn( f"Contains tensors of types {non_numpy_interfaces}; dispatch will prioritize " "TensorFlow and PyTorch over autograd. Consider replacing Autograd with vanilla NumPy.", UserWarning, ) if "tensorflow" in interfaces: return "tensorflow" if "torch" in interfaces: return "torch" if "autograd" in interfaces: return "autograd" if "jax" in interfaces: return "jax" return "numpy"
def cast(tensor, dtype): """Casts the given tensor to a new type. Args: tensor (tensor_like): tensor to cast dtype (str, np.dtype): Any supported NumPy dtype representation; this can be a string (``"float64"``), a ``np.dtype`` object (``np.dtype("float64")``), or a dtype class (``np.float64``). If ``tensor`` is not a NumPy array, the **equivalent** dtype in the dispatched framework is used. Returns: tensor_like: a tensor with the same shape and values as ``tensor`` and the same dtype as ``dtype`` **Example** We can use NumPy dtype specifiers: >>> x = torch.tensor([1, 2]) >>> cast(x, np.float64) tensor([1., 2.], dtype=torch.float64) We can also use strings: >>> x = tf.Variable([1, 2]) >>> cast(x, "complex128") <tf.Tensor: shape=(2,), dtype=complex128, numpy=array([1.+0.j, 2.+0.j])> """ if isinstance(tensor, (list, tuple)): tensor = np.asarray(tensor) if not isinstance(dtype, str): try: dtype = np.dtype(dtype).name except (AttributeError, TypeError): dtype = getattr(dtype, "name", dtype) return ar.astype(tensor, ar.to_backend_dtype(dtype, like=ar.infer_backend(tensor)))
def _reconstruct_gen(fun, spectrum, shifts=None, x0=None, f0=None, interface=None): r"""Reconstruct a univariate (real-valued) Fourier series with given spectrum. Args: fun (callable): Univariate finite Fourier series to reconstruct. It must have signature ``float -> float`` . spectrum (Collection): Frequency spectrum of the Fourier series; non-positive frequencies are ignored. shifts (Sequence): Shift angles at which to evaluate ``fun`` for the reconstruction. Chosen equidistantly within the interval :math:`[0, 2\pi/f_\text{max}]` if ``shifts=None`` , where :math:`f_\text{max}` is the biggest frequency in ``spectrum``. x0 (float): Center to which to shift the reconstruction. The points at which ``fun`` is evaluated are *not* affected by ``x0`` . f0 (float): Value of ``fun`` at zero; If :math:`0` is among the ``shifts`` and ``f0`` is provided, one evaluation of ``fun`` is saved. interface (str): Which auto-differentiation framework to use as interface. This determines in which interface the output reconstructed function is intended to be used. Returns: callable: Reconstructed Fourier series with :math:`R` frequencies in ``spectrum`` . This function is a purely classical function. Furthermore, it is fully differentiable. """ # pylint: disable=unused-argument, too-many-arguments have_f0 = f0 is not None have_shifts = shifts is not None spectrum = anp.asarray(spectrum, like=interface) spectrum = spectrum[spectrum > 0] f_max = qml.math.max(spectrum) # If no shifts are provided, choose equidistant ones if not have_shifts: R = qml.math.shape(spectrum)[0] shifts = qml.math.arange(-R, R + 1) * 2 * np.pi / (f_max * (2 * R + 1)) * R zero_idx = R need_f0 = True elif have_f0: zero_idx = qml.math.where(qml.math.isclose(shifts, qml.math.zeros_like(shifts[0]))) zero_idx = zero_idx[0][0] if (len(zero_idx) > 0 and len(zero_idx[0]) > 0) else None need_f0 = zero_idx is not None # Take care of shifts close to zero if f0 was provided if have_f0 and need_f0: # Only one shift may be zero at a time shifts = qml.math.concatenate( [shifts[zero_idx : zero_idx + 1], shifts[:zero_idx], shifts[zero_idx + 1 :]] ) shifts = anp.asarray(shifts, like=interface) evals = anp.asarray([f0] + list(map(fun, shifts[1:])), like=interface) else: shifts = anp.asarray(shifts, like=interface) if have_f0 and not need_f0: warnings.warn(_warn_text_f0_ignored) evals = anp.asarray(list(map(fun, shifts)), like=interface) L = len(shifts) # Construct the coefficient matrix case by case C1 = qml.math.ones((L, 1)) C2 = qml.math.cos(qml.math.tensordot(shifts, spectrum, axes=0)) C3 = qml.math.sin(qml.math.tensordot(shifts, spectrum, axes=0)) C = qml.math.hstack([C1, C2, C3]) # Solve the system of linear equations cond = qml.math.linalg.cond(C) if cond > 1e8: warnings.warn( f"The condition number of the Fourier transform matrix is very large: {cond}.", UserWarning, ) W = qml.math.linalg.solve(C, evals) # Extract the Fourier coefficients R = (L - 1) // 2 a0 = W[0] a = anp.asarray(W[1 : R + 1], like=interface) b = anp.asarray(W[R + 1 :], like=interface) x0 = anp.asarray(np.float64(0.0), like=interface) if x0 is None else x0 # Construct the Fourier series def _reconstruction(x): """Univariate reconstruction based on arbitrary shifts.""" x = x - x0 return ( a0 + qml.math.tensordot(qml.math.cos(spectrum * x), a, axes=[[0], [0]]) + qml.math.tensordot(qml.math.sin(spectrum * x), b, axes=[[0], [0]]) ) return _reconstruction
def _reconstruct_equ(fun, num_frequency, x0=None, f0=None, interface=None): r"""Reconstruct a univariate Fourier series with consecutive integer frequencies, using trigonometric interpolation and equidistant shifts. This technique is based on `Dirichlet kernels <https://en.wikipedia.org/wiki/Dirichlet_kernel>`_, see `Vidal and Theis (2018) <https://arxiv.org/abs/1812.06323>`_ or `Wierichs et al. (2021) <https://arxiv.org/abs/2107.12390>`_. Args: fun (callable): Univariate finite Fourier series to reconstruct. It must have signature ``float -> float`` . num_frequency (int): Number of integer frequencies in ``fun``. All integer frequencies below ``num_frequency`` are assumed to be present in ``fun`` as well; if they are not, the output is correct put the reconstruction could have been performed with fewer evaluations of ``fun`` . x0 (float): Center to which to shift the reconstruction. The points at which ``fun`` is evaluated are *not* affected by ``x0`` . f0 (float): Value of ``fun`` at zero; Providing ``f0`` saves one evaluation of ``fun``. interface (str): Which auto-differentiation framework to use as interface. This determines in which interface the output reconstructed function is intended to be used. Returns: callable: Reconstructed Fourier series with ``num_frequency`` frequencies. This function is a purely classical function. Furthermore, it is fully differentiable. """ if not abs(int(num_frequency)) == num_frequency: raise ValueError(f"num_frequency must be a non-negative integer, got {num_frequency}") a = (num_frequency + 0.5) / np.pi b = 0.5 / np.pi shifts_pos = qml.math.arange(1, num_frequency + 1) / a shifts_neg = -shifts_pos[::-1] shifts = qml.math.concatenate([shifts_neg, [0.0], shifts_pos]) shifts = anp.asarray(shifts, like=interface) f0 = fun(0.0) if f0 is None else f0 evals = ( list(map(fun, shifts[:num_frequency])) + [f0] + list(map(fun, shifts[num_frequency + 1 :])) ) evals = anp.asarray(evals, like=interface) x0 = anp.asarray(np.float64(0.0), like=interface) if x0 is None else x0 def _reconstruction(x): """Univariate reconstruction based on equidistant shifts and Dirichlet kernels. The derivative at of ``sinc`` are not well-implemented in TensorFlow and Autograd, use the Fourier transform reconstruction if this derivative is needed. """ _x = x - x0 - shifts return qml.math.tensordot( qml.math.sinc(a * _x) / qml.math.sinc(b * _x), evals, axes=[[0], [0]], ) return _reconstruction