From 02edfce7075872d0815f0a97f5b30eb14aa00cc3 Mon Sep 17 00:00:00 2001 From: to24toro Date: Mon, 16 Oct 2023 17:26:54 +0900 Subject: [PATCH 01/38] arraylias to signal class --- qiskit_dynamics/arraylias/__init__.py | 8 +- qiskit_dynamics/signals/signals.py | 263 +++++++----------- test/dynamics/signals/test_signals.py | 223 +++++++-------- test/dynamics/signals/test_signals_algebra.py | 8 +- 4 files changed, 224 insertions(+), 278 deletions(-) diff --git a/qiskit_dynamics/arraylias/__init__.py b/qiskit_dynamics/arraylias/__init__.py index 66102606e..4cd406e3b 100644 --- a/qiskit_dynamics/arraylias/__init__.py +++ b/qiskit_dynamics/arraylias/__init__.py @@ -22,4 +22,10 @@ Module for Qiskit Dynamics global NumPy and SciPy aliases. """ -from .alias import DYNAMICS_NUMPY_ALIAS, DYNAMICS_SCIPY_ALIAS, DYNAMICS_NUMPY, DYNAMICS_SCIPY +from .alias import ( + DYNAMICS_NUMPY_ALIAS, + DYNAMICS_SCIPY_ALIAS, + DYNAMICS_NUMPY, + DYNAMICS_SCIPY, + ArrayLike, +) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 493702227..5739e58b7 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2023. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -24,13 +24,10 @@ import numpy as np from matplotlib import pyplot as plt -try: - import jax.numpy as jnp -except ImportError: - pass - from qiskit import QiskitError -from qiskit_dynamics.array import Array +from qiskit_dynamics.arraylias import ArrayLike +from qiskit_dynamics.arraylias import DYNAMICS_NUMPY_ALIAS as alias +from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp class Signal: @@ -72,9 +69,9 @@ class Signal: def __init__( self, - envelope: Union[Callable, complex, float, int, Array], - carrier_freq: Union[float, List, Array] = 0.0, - phase: Union[float, List, Array] = 0.0, + envelope: Union[Callable, complex, float, int, ArrayLike], + carrier_freq: Union[float, List, ArrayLike] = 0.0, + phase: Union[float, List, ArrayLike] = 0.0, name: Optional[str] = None, ): """ @@ -91,10 +88,9 @@ def __init__( self._name = name self._is_constant = False - if isinstance(envelope, (complex, float, int)): - envelope = Array(complex(envelope)) - - if isinstance(envelope, Array): + if not callable(envelope): + # if not callable, we assume a constant + envelope = unp.asarray(envelope) # if envelope is constant and the carrier is zero, this is a constant signal try: # try block is for catching JAX tracer errors @@ -103,15 +99,9 @@ def __init__( except Exception: # pylint: disable=broad-except pass - if envelope.backend == "jax": - self._envelope = lambda t: envelope * jnp.ones_like(Array(t).data) - else: - self._envelope = lambda t: envelope * np.ones_like(t) - elif callable(envelope): - if Array.default_backend() == "jax": - self._envelope = lambda t: Array(envelope(t)) - else: - self._envelope = envelope + self._envelope = lambda t: envelope * unp.ones_like(t) + else: + self._envelope = envelope # set carrier and phase self.carrier_freq = carrier_freq @@ -128,45 +118,41 @@ def is_constant(self) -> bool: return self._is_constant @property - def carrier_freq(self) -> Array: + def carrier_freq(self) -> ArrayLike: """The carrier frequency of the signal.""" return self._carrier_freq @carrier_freq.setter - def carrier_freq(self, carrier_freq: Union[float, list, Array]): + def carrier_freq(self, carrier_freq: Union[float, list, ArrayLike]): """Carrier frequency setter. List handling is to support subclasses storing a list of frequencies.""" - if type(carrier_freq) == list: - carrier_freq = [Array(entry).data for entry in carrier_freq] - self._carrier_freq = Array(carrier_freq) + self._carrier_freq = unp.asarray(carrier_freq) self._carrier_arg = 1j * 2 * np.pi * self._carrier_freq @property - def phase(self) -> Array: + def phase(self) -> ArrayLike: """The phase of the signal.""" return self._phase @phase.setter - def phase(self, phase: Union[float, list, Array]): + def phase(self, phase: Union[float, list, ArrayLike]): """Phase setter. List handling is to support subclasses storing a list of phases.""" - if type(phase) == list: - phase = [Array(entry).data for entry in phase] - self._phase = Array(phase) + self._phase = unp.asarray(phase) self._phase_arg = 1j * self._phase - def envelope(self, t: Union[float, np.array, Array]) -> Union[complex, np.array, Array]: + def envelope(self, t: Union[float, ArrayLike]) -> Union[complex, ArrayLike]: """Vectorized evaluation of the envelope at time t.""" return self._envelope(t) - def complex_value(self, t: Union[float, np.array, Array]) -> Union[complex, np.array, Array]: + def complex_value(self, t: Union[float, ArrayLike]) -> Union[complex, ArrayLike]: """Vectorized evaluation of the complex value at time t.""" arg = self._carrier_arg * t + self._phase_arg - return self.envelope(t) * np.exp(arg) + return self.envelope(t) * unp.exp(arg) - def __call__(self, t: Union[float, np.array, Array]) -> Union[complex, np.array, Array]: + def __call__(self, t: Union[float, ArrayLike]) -> Union[complex, ArrayLike]: """Vectorized evaluation of the signal at time(s) t.""" - return np.real(self.complex_value(t)) + return unp.real(self.complex_value(t)) def __str__(self) -> str: """Return string representation.""" @@ -203,7 +189,7 @@ def conjugate(self): """Return a new signal whose complex value is the complex conjugate of this one.""" def conj_env(t): - return np.conjugate(self.envelope(t)) + return unp.conjugate(self.envelope(t)) return Signal(conj_env, -self.carrier_freq, -self.phase) @@ -283,10 +269,10 @@ class DiscreteSignal(Signal): def __init__( self, dt: float, - samples: Union[Array, List], + samples: ArrayLike, start_time: float = 0.0, - carrier_freq: Union[float, List, Array] = 0.0, - phase: Union[float, List, Array] = 0.0, + carrier_freq: Union[float, ArrayLike] = 0.0, + phase: Union[float, ArrayLike] = 0.0, name: str = None, ): """Initialize a piecewise constant signal. @@ -303,38 +289,25 @@ def __init__( """ self._dt = dt - samples = Array(samples) + samples = unp.asarray(samples) if len(samples) == 0: - zero_pad = np.array([0]) + zero_pad = unp.asarray([0]) else: - zero_pad = np.expand_dims(np.zeros_like(Array(samples[0])), 0) - self._padded_samples = np.append(samples, zero_pad, axis=0) + zero_pad = unp.expand_dims(unp.zeros_like(unp.asarray(samples[0])), 0) + self._padded_samples = unp.append(samples, zero_pad, axis=0) self._start_time = start_time # define internal envelope function - if samples.backend == "jax": - - def envelope(t): - t = Array(t).data - idx = jnp.clip( - jnp.array((t - self._start_time) // self._dt, dtype=int), - -1, - len(self.samples), - ) - return self._padded_samples[idx] - - else: - - def envelope(t): - t = Array(t).data - idx = np.clip( - np.array((t - self._start_time) // self._dt, dtype=int), - -1, - len(self.samples), - ) - return self._padded_samples[idx] + def envelope(t): + t = unp.asarray(t) + idx = unp.clip( + unp.array((t - self._start_time) // self._dt, dtype=int), + -1, + len(self.samples), + ) + return alias(like=idx).asarray(self._padded_samples)[idx] Signal.__init__(self, envelope=envelope, carrier_freq=carrier_freq, phase=phase, name=name) @@ -410,12 +383,12 @@ def dt(self) -> float: return self._dt @property - def samples(self) -> Array: + def samples(self) -> ArrayLike: """ Returns: samples: the samples of the piecewise constant signal. """ - return Array(self._padded_samples[:-1]) + return self._padded_samples[:-1] @property def start_time(self) -> float: @@ -428,7 +401,7 @@ def start_time(self) -> float: def conjugate(self): return self.__class__( dt=self._dt, - samples=np.conjugate(self.samples), + samples=unp.conjugate(self.samples), start_time=self._start_time, carrier_freq=-self.carrier_freq, phase=-self.phase, @@ -445,7 +418,7 @@ def add_samples(self, start_sample: int, samples: List): Raises: QiskitError: If start_sample is less than the current length of samples. """ - samples = Array(samples) + samples = unp.asarray(samples) if len(samples) < 1: return @@ -453,16 +426,16 @@ def add_samples(self, start_sample: int, samples: List): if start_sample < len(self.samples): raise QiskitError("Samples can only be added afer the last sample.") - zero_pad = np.expand_dims(np.zeros_like(Array(samples[0])), 0) + zero_pad = unp.expand_dims(unp.zeros_like(unp.asarray(samples[0])), 0) new_samples = self.samples if len(self.samples) < start_sample: - new_samples = np.append( - new_samples, np.repeat(zero_pad, start_sample - len(self.samples)) + new_samples = unp.append( + new_samples, unp.repeat(zero_pad, start_sample - len(self.samples)) ) - new_samples = np.append(new_samples, samples) - self._padded_samples = np.append(new_samples, zero_pad, axis=0) + new_samples = unp.append(new_samples, samples) + self._padded_samples = unp.append(new_samples, zero_pad, axis=0) def __str__(self) -> str: """Return string representation.""" @@ -494,12 +467,10 @@ def __len__(self): """Number of components.""" return len(self.components) - def __getitem__( - self, idx: Union[int, List, np.array, slice] - ) -> Union[Signal, "SignalCollection"]: + def __getitem__(self, idx: Union[int, ArrayLike, slice]) -> Union[Signal, "SignalCollection"]: """Get item with NumPy-style subscripting, as if this class were a 1d array.""" - if type(idx) == np.ndarray and idx.ndim > 0: + if type(idx) != int and type(idx) != slice and type(idx) != list and idx.ndim > 0: idx = list(idx) # get a list of the subcomponents @@ -543,9 +514,9 @@ class SignalSum(SignalCollection, Signal): - ``__call__`` evaluates the sum. - ``complex_value`` evaluates the sum of the complex values of the individual summands. - Attributes ``carrier_freq`` and ``phase`` here correspond to an ``Array`` of + Attributes ``carrier_freq`` and ``phase`` here correspond to an ``ArrayLike`` of frequencies/phases for each term in the sum, and the ``envelope`` method returns an - ``Array`` of the envelopes for each summand. + ``ArrayLike`` of the envelopes for each summand. Internally, the signals are stored as a list in the ``components`` attribute, which can be accessed via direct subscripting of the object. @@ -573,7 +544,7 @@ def __init__(self, *signals, name: Optional[str] = None): elif isinstance(sig, Signal): components.append(sig) elif isinstance(sig, (int, float, complex)) or ( - isinstance(sig, Array) and sig.ndim == 0 + isinstance(sig, ArrayLike) and sig.ndim == 0 ): components.append(Signal(sig)) else: @@ -583,17 +554,8 @@ def __init__(self, *signals, name: Optional[str] = None): SignalCollection.__init__(self, components) - # set up routine for evaluating envelopes if jax - if Array.default_backend() == "jax": - jax_arraylist_eval = array_funclist_evaluate([sig.envelope for sig in self.components]) - - def envelope(t): - return np.moveaxis(jax_arraylist_eval(t), 0, -1) - - else: - - def envelope(t): - return np.moveaxis([sig.envelope(t) for sig in self.components], 0, -1) + def envelope(t): + return unp.moveaxis(unp.asarray([sig.envelope(t) for sig in self.components]), 0, -1) carrier_freqs = [] for sig in self.components: @@ -607,12 +569,12 @@ def envelope(t): self, envelope=envelope, carrier_freq=carrier_freqs, phase=phases, name=name ) - def complex_value(self, t: Union[float, np.array, Array]) -> Union[complex, np.array, Array]: + def complex_value(self, t: Union[float, ArrayLike]) -> Union[complex, ArrayLike]: """Return the sum of the complex values of each component.""" - if Array.default_backend() == "jax": - t = Array(t) - exp_phases = np.exp(np.expand_dims(t, -1) * self._carrier_arg + self._phase_arg) - return np.sum(self.envelope(t) * exp_phases, axis=-1) + exp_phases = unp.exp( + unp.expand_dims(unp.asarray(t), -1) * self._carrier_arg + self._phase_arg + ) + return unp.sum(self.envelope(t) * exp_phases, axis=-1) def __str__(self): if self.name is not None: @@ -637,12 +599,14 @@ def flatten(self) -> Signal: elif len(self) == 1: return self.components[0] - ave_freq = np.sum(self.carrier_freq) / len(self) + ave_freq = unp.sum(self.carrier_freq) / len(self) shifted_arg = self._carrier_arg - (1j * 2 * np.pi * ave_freq) def merged_env(t): - exp_phases = np.exp(np.expand_dims(Array(t), -1) * shifted_arg + self._phase_arg) - return np.sum(self.envelope(t) * exp_phases, axis=-1) + exp_phases = unp.exp( + unp.expand_dims(unp.asarray(t), -1) * shifted_arg + self._phase_arg + ) + return unp.sum(self.envelope(t) * exp_phases, axis=-1) return Signal(envelope=merged_env, carrier_freq=ave_freq, name=str(self)) @@ -655,10 +619,10 @@ class DiscreteSignalSum(DiscreteSignal, SignalSum): def __init__( self, dt: float, - samples: Union[List, Array], + samples: ArrayLike, start_time: float = 0.0, - carrier_freq: Union[List, np.array, Array] = None, - phase: Union[List, np.array, Array] = None, + carrier_freq: ArrayLike = None, + phase: ArrayLike = None, name: str = None, ): r"""Directly initialize a ``DiscreteSignalSum``\. Samples of all terms in the @@ -675,12 +639,12 @@ def __init__( name: name of the signal. """ - samples = Array(samples) + samples = unp.asarray(samples) if carrier_freq is None: - carrier_freq = np.zeros(samples.shape[-1], dtype=float) + carrier_freq = unp.zeros(samples.shape[-1], dtype=float) if phase is None: - phase = np.zeros(samples.shape[-1], dtype=float) + phase = unp.zeros(samples.shape[-1], dtype=float) DiscreteSignal.__init__( self, @@ -750,7 +714,7 @@ def from_SignalSum( if sample_carrier: freq = 0.0 * freq - exp_phases = np.exp(np.expand_dims(Array(times), -1) * signal_sum._carrier_arg) + exp_phases = unp.exp(unp.expand_dims(unp.asarray(times), -1) * signal_sum._carrier_arg) samples = signal_sum.envelope(times) * exp_phases else: samples = signal_sum.envelope(times) @@ -778,7 +742,7 @@ def __str__(self): return default_str - def __getitem__(self, idx: Union[int, List, np.array, slice]) -> Signal: + def __getitem__(self, idx: Union[int, ArrayLike]) -> Signal: """Enables numpy-style subscripting, as if this class were a 1d array.""" if type(idx) == int and idx >= len(self): @@ -789,13 +753,13 @@ def __getitem__(self, idx: Union[int, List, np.array, slice]) -> Signal: phases = self.phase[idx] if samples.ndim == 1: - samples = Array([samples]) + samples = unp.asarray([samples]) if carrier_freqs.ndim == 0: - carrier_freqs = Array([carrier_freqs]) + carrier_freqs = unp.asarray([carrier_freqs]) if phases.ndim == 0: - phases = Array([phases]) + phases = unp.asarray([phases]) if len(samples) == 1: return DiscreteSignal( @@ -827,22 +791,18 @@ def __init__(self, signal_list: List[Signal]): super().__init__(signal_list) # setup complex value and full signal evaluation - if Array.default_backend() == "jax": - self._eval_complex_value = array_funclist_evaluate( - [sig.complex_value for sig in self.components] - ) - self._eval_signals = array_funclist_evaluate(self.components) - else: - self._eval_complex_value = lambda t: [sig.complex_value(t) for sig in self.components] - self._eval_signals = lambda t: [sig(t) for sig in self.components] + self._eval_complex_value = lambda t: unp.asarray( + [sig.complex_value(t) for sig in self.components] + ) + self._eval_signals = lambda t: unp.asarray([sig(t) for sig in self.components]) - def complex_value(self, t: Union[float, np.array, Array]) -> Union[np.array, Array]: + def complex_value(self, t: Union[float, ArrayLike]) -> ArrayLike: """Vectorized evaluation of complex value of components.""" - return np.moveaxis(self._eval_complex_value(t), 0, -1) + return unp.moveaxis(self._eval_complex_value(t), 0, -1) - def __call__(self, t: Union[float, np.array, Array]) -> Union[np.array, Array]: + def __call__(self, t: Union[float, ArrayLike]) -> ArrayLike: """Vectorized evaluation of all components.""" - return np.moveaxis(self._eval_signals(t), 0, -1) + return unp.moveaxis(self._eval_signals(t), 0, -1) def flatten(self) -> "SignalList": """Return a ``SignalList`` with each component flattened.""" @@ -856,8 +816,8 @@ def flatten(self) -> "SignalList": return SignalList(flattened_list) @property - def drift(self) -> Array: - r"""Return the drift ``Array``\, i.e. return an ``Array`` whose entries are the sum + def drift(self) -> ArrayLike: + r"""Return the drift ``ArrayLike``\, i.e. return an ``ArrayLike`` whose entries are the sum of the constant parts of the corresponding component of this ``SignalList``\. """ @@ -870,11 +830,11 @@ def drift(self) -> Array: for term in sig_entry: if term.is_constant: - val += Array(term(0.0)).data + val += term(0.0) drift_array.append(val) - return Array(drift_array) + return unp.asarray(drift_array) def signal_add(sig1: Signal, sig2: Signal) -> SignalSum: @@ -895,9 +855,9 @@ def signal_add(sig1: Signal, sig2: Signal) -> SignalSum: and sig1.start_time == sig2.start_time and sig1.duration == sig2.duration ): - samples = np.append(sig1.samples, sig2.samples, axis=1) - carrier_freq = np.append(sig1.carrier_freq, sig2.carrier_freq) - phase = np.append(sig1.phase, sig2.phase) + samples = unp.append(sig1.samples, sig2.samples, axis=1) + carrier_freq = unp.append(sig1.carrier_freq, sig2.carrier_freq) + phase = unp.append(sig1.phase, sig2.phase) return DiscreteSignalSum( dt=sig1.dt, samples=samples, @@ -951,29 +911,29 @@ def signal_multiply(sig1: Signal, sig2: Signal) -> SignalSum: ): # this vectorized operation produces a 2d array whose columns are the products of # the original columns - new_samples = Array( + new_samples = unp.asarray( 0.5 * (sig1.samples[:, :, None] * sig2.samples[:, None, :]).reshape( (sig1.samples.shape[0], sig1.samples.shape[1] * sig2.samples.shape[1]), order="C", ) ) - new_samples_conj = Array( + new_samples_conj = unp.asarray( 0.5 * (sig1.samples[:, :, None] * sig2.samples[:, None, :].conj()).reshape( (sig1.samples.shape[0], sig1.samples.shape[1] * sig2.samples.shape[1]), order="C", ) ) - samples = np.append(new_samples, new_samples_conj, axis=1) + samples = unp.append(new_samples, new_samples_conj, axis=1) new_freqs = sig1.carrier_freq + sig2.carrier_freq new_freqs_conj = sig1.carrier_freq - sig2.carrier_freq - freqs = np.append(Array(new_freqs), Array(new_freqs_conj)) + freqs = unp.append(unp.asarray(new_freqs), unp.asarray(new_freqs_conj)) new_phases = sig1.phase + sig2.phase new_phases_conj = sig1.phase - sig2.phase - phases = np.append(Array(new_phases), Array(new_phases_conj)) + phases = unp.append(unp.asarray(new_phases), unp.asarray(new_phases_conj)) return DiscreteSignalSum( dt=sig1.dt, @@ -1059,7 +1019,7 @@ def new_env(t): ) pwc2 = DiscreteSignal( dt=sig2.dt, - samples=0.5 * sig1.samples * np.conjugate(sig2.samples), + samples=0.5 * sig1.samples * unp.conjugate(sig2.samples), start_time=sig2.start_time, carrier_freq=sig1.carrier_freq - sig2.carrier_freq, phase=sig1.phase - sig2.phase, @@ -1071,7 +1031,7 @@ def new_env1(t): return 0.5 * sig1.envelope(t) * sig2.envelope(t) def new_env2(t): - return 0.5 * sig1.envelope(t) * np.conjugate(sig2.envelope(t)) + return 0.5 * sig1.envelope(t) * unp.conjugate(sig2.envelope(t)) prod1 = Signal( envelope=new_env1, @@ -1116,7 +1076,7 @@ def sort_signals(sig1: Signal, sig2: Signal) -> Tuple[Signal, Signal]: return sig1, sig2 -def to_SignalSum(sig: Union[int, float, complex, Array, Signal]) -> SignalSum: +def to_SignalSum(sig: Union[int, float, complex, ArrayLike, Signal]) -> SignalSum: r"""Convert the input to a SignalSum according to: - If it is already a ``SignalSum``\, do nothing. @@ -1134,19 +1094,19 @@ def to_SignalSum(sig: Union[int, float, complex, Array, Signal]) -> SignalSum: QiskitError: If the input type is incompatible with SignalSum. """ - if isinstance(sig, (int, float, complex)) or (isinstance(sig, Array) and sig.ndim == 0): + if isinstance(sig, (int, float, complex)) or (isinstance(sig, ArrayLike) and sig.ndim == 0): return SignalSum(Signal(sig)) elif isinstance(sig, DiscreteSignal) and not isinstance(sig, DiscreteSignalSum): - if Array(sig.samples.data).shape == (0,): - new_samples = Array([sig.samples.data]) + if sig.samples.shape == (0,): + new_samples = unp.asarray([sig.samples]) else: - new_samples = Array([sig.samples.data]).transpose(1, 0) + new_samples = unp.asarray([sig.samples]).transpose(1, 0) return DiscreteSignalSum( dt=sig.dt, samples=new_samples, start_time=sig.start_time, - carrier_freq=Array([sig.carrier_freq.data]), - phase=Array([sig.phase.data]), + carrier_freq=unp.asarray([sig.carrier_freq]), + phase=unp.asarray([sig.phase]), ) elif isinstance(sig, Signal) and not isinstance(sig, SignalSum): return SignalSum(sig) @@ -1154,14 +1114,3 @@ def to_SignalSum(sig: Union[int, float, complex, Array, Signal]) -> SignalSum: return sig raise QiskitError("Input type incompatible with SignalSum.") - - -def array_funclist_evaluate(func_list: List[Callable]) -> Callable: - """Utility for evaluating a list of functions in a way that respects Arrays. - Currently relevant for JAX evaluation. - """ - - def eval_func(t): - return Array([Array(func(t)).data for func in func_list]) - - return eval_func diff --git a/test/dynamics/signals/test_signals.py b/test/dynamics/signals/test_signals.py index cd1b107a2..18c691a00 100644 --- a/test/dynamics/signals/test_signals.py +++ b/test/dynamics/signals/test_signals.py @@ -15,13 +15,15 @@ Tests for signals. """ +from functools import partial + import numpy as np from qiskit_dynamics.signals import Signal, DiscreteSignal, DiscreteSignalSum, SignalList from qiskit_dynamics.signals.signals import to_SignalSum -from qiskit_dynamics.array import Array +from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp -from ..common import QiskitDynamicsTestCase, TestJaxBase +from ..common import JAXTestBase, QiskitDynamicsTestCase, test_array_backends try: from jax import jit, grad @@ -30,11 +32,12 @@ pass +@partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) class TestSignal(QiskitDynamicsTestCase): """Tests for Signal object.""" def setUp(self): - self.signal1 = Signal(lambda t: 0.25, carrier_freq=0.3) + self.signal1 = Signal(lambda _: 0.25, carrier_freq=0.3) self.signal2 = Signal(lambda t: 2.0 * (t**2), carrier_freq=0.1) self.signal3 = Signal(lambda t: 2.0 * (t**2) + 1j * t, carrier_freq=0.1, phase=-0.1) @@ -51,25 +54,27 @@ def test_envelope(self): def test_envelope_vectorized(self): """Test vectorized evaluation of envelope.""" - t_vals = np.array([1.1, 1.23]) - self.assertAllClose(self.signal1.envelope(t_vals), np.array([0.25, 0.25])) + t_vals = self.asarray([1.1, 1.23]) + self.assertAllClose(self.signal1.envelope(t_vals), self.asarray([0.25, 0.25])) self.assertAllClose( - self.signal2.envelope(t_vals), np.array([2 * (1.1**2), 2 * (1.23**2)]) + self.signal2.envelope(t_vals), self.asarray([2 * (1.1**2), 2 * (1.23**2)]) ) self.assertAllClose( self.signal3.envelope(t_vals), - np.array([2 * (1.1**2) + 1j * 1.1, 2 * (1.23**2) + 1j * 1.23]), + self.asarray([2 * (1.1**2) + 1j * 1.1, 2 * (1.23**2) + 1j * 1.23]), ) - t_vals = np.array([[1.1, 1.23], [0.1, 0.24]]) - self.assertAllClose(self.signal1.envelope(t_vals), np.array([[0.25, 0.25], [0.25, 0.25]])) + t_vals = self.asarray([[1.1, 1.23], [0.1, 0.24]]) + self.assertAllClose( + self.signal1.envelope(t_vals), self.asarray([[0.25, 0.25], [0.25, 0.25]]) + ) self.assertAllClose( self.signal2.envelope(t_vals), - np.array([[2 * (1.1**2), 2 * (1.23**2)], [2 * (0.1**2), 2 * (0.24**2)]]), + self.asarray([[2 * (1.1**2), 2 * (1.23**2)], [2 * (0.1**2), 2 * (0.24**2)]]), ) self.assertAllClose( self.signal3.envelope(t_vals), - np.array( + self.asarray( [ [2 * (1.1**2) + 1j * 1.1, 2 * (1.23**2) + 1j * 1.23], [2 * (0.1**2) + 1j * 0.1, 2 * (0.24**2) + 1j * 0.24], @@ -104,10 +109,10 @@ def test_complex_value(self): def test_complex_value_vectorized(self): """Test vectorized complex_value evaluation.""" - t_vals = np.array([1.1, 1.23]) + t_vals = self.asarray([1.1, 1.23]) self.assertAllClose( self.signal1.complex_value(t_vals), - np.array( + self.asarray( [ 0.25 * np.exp(1j * 2 * np.pi * 0.3 * 1.1), 0.25 * np.exp(1j * 2 * np.pi * 0.3 * 1.23), @@ -116,7 +121,7 @@ def test_complex_value_vectorized(self): ) self.assertAllClose( self.signal2.complex_value(t_vals), - np.array( + self.asarray( [ 2 * (1.1**2) * np.exp(1j * 2 * np.pi * 0.1 * 1.1), 2 * (1.23**2) * np.exp(1j * 2 * np.pi * 0.1 * 1.23), @@ -125,7 +130,7 @@ def test_complex_value_vectorized(self): ) self.assertAllClose( self.signal3.complex_value(t_vals), - np.array( + self.asarray( [ (2 * (1.1**2) + 1j * 1.1) * np.exp(1j * 2 * np.pi * 0.1 * 1.1 + 1j * (-0.1)), (2 * (1.23**2) + 1j * 1.23) @@ -134,10 +139,10 @@ def test_complex_value_vectorized(self): ), ) - t_vals = np.array([[1.1, 1.23], [0.1, 0.24]]) + t_vals = self.asarray([[1.1, 1.23], [0.1, 0.24]]) self.assertAllClose( self.signal1.complex_value(t_vals), - np.array( + self.asarray( [ [ 0.25 * np.exp(1j * 2 * np.pi * 0.3 * 1.1), @@ -152,7 +157,7 @@ def test_complex_value_vectorized(self): ) self.assertAllClose( self.signal2.complex_value(t_vals), - np.array( + self.asarray( [ [ 2 * (1.1**2) * np.exp(1j * 2 * np.pi * 0.1 * 1.1), @@ -167,7 +172,7 @@ def test_complex_value_vectorized(self): ) self.assertAllClose( self.signal3.complex_value(t_vals), - np.array( + self.asarray( [ [ (2 * (1.1**2) + 1j * 1.1) @@ -210,10 +215,10 @@ def test_call(self): def test_call_vectorized(self): """Test vectorized __call__.""" - t_vals = np.array([1.1, 1.23]) + t_vals = self.asarray([1.1, 1.23]) self.assertAllClose( self.signal1(t_vals), - np.array( + self.asarray( [ 0.25 * np.exp(1j * 2 * np.pi * 0.3 * 1.1), 0.25 * np.exp(1j * 2 * np.pi * 0.3 * 1.23), @@ -222,7 +227,7 @@ def test_call_vectorized(self): ) self.assertAllClose( self.signal2(t_vals), - np.array( + self.asarray( [ 2 * (1.1**2) * np.exp(1j * 2 * np.pi * 0.1 * 1.1), 2 * (1.23**2) * np.exp(1j * 2 * np.pi * 0.1 * 1.23), @@ -231,7 +236,7 @@ def test_call_vectorized(self): ) self.assertAllClose( self.signal3(t_vals), - np.array( + self.asarray( [ (2 * (1.1**2) + 1j * 1.1) * np.exp(1j * 2 * np.pi * 0.1 * 1.1 + 1j * (-0.1)), (2 * (1.23**2) + 1j * 1.23) @@ -240,10 +245,10 @@ def test_call_vectorized(self): ).real, ) - t_vals = np.array([[1.1, 1.23], [0.1, 0.24]]) + t_vals = self.asarray([[1.1, 1.23], [0.1, 0.24]]) self.assertAllClose( self.signal1(t_vals), - np.array( + self.asarray( [ [ 0.25 * np.exp(1j * 2 * np.pi * 0.3 * 1.1), @@ -258,7 +263,7 @@ def test_call_vectorized(self): ) self.assertAllClose( self.signal2(t_vals), - np.array( + self.asarray( [ [ 2 * (1.1**2) * np.exp(1j * 2 * np.pi * 0.1 * 1.1), @@ -273,7 +278,7 @@ def test_call_vectorized(self): ) self.assertAllClose( self.signal3(t_vals), - np.array( + self.asarray( [ [ (2 * (1.1**2) + 1j * 1.1) @@ -307,6 +312,7 @@ def test_conjugate(self): ) +@partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) class TestConstant(QiskitDynamicsTestCase): """Tests for constant signal object.""" @@ -324,12 +330,12 @@ def test_envelope(self): def test_envelope_vectorized(self): """Test vectorized evaluation of envelope.""" - t_vals = np.array([1.1, 1.23]) - self.assertAllClose(self.constant1.envelope(t_vals), np.array([1.0, 1.0])) + t_vals = self.asarray([1.1, 1.23]) + self.assertAllClose(self.constant1.envelope(t_vals), self.asarray([1.0, 1.0])) self.assertAllClose(self.constant2.envelope(t_vals), (3.0 + 2j) * np.ones_like(t_vals)) - t_vals = np.array([[1.1, 1.23], [0.1, 0.24]]) - self.assertAllClose(self.constant1.envelope(t_vals), np.array([[1.0, 1.0], [1.0, 1.0]])) + t_vals = self.asarray([[1.1, 1.23], [0.1, 0.24]]) + self.assertAllClose(self.constant1.envelope(t_vals), self.asarray([[1.0, 1.0], [1.0, 1.0]])) self.assertAllClose(self.constant2.envelope(t_vals), (3.0 + 2j) * np.ones_like(t_vals)) def test_complex_value(self): @@ -342,13 +348,13 @@ def test_complex_value(self): def test_complex_value_vectorized(self): """Test vectorized complex_value evaluation.""" - t_vals = np.array([1.1, 1.23]) - self.assertAllClose(self.constant1.complex_value(t_vals), np.array([1.0, 1.0])) + t_vals = self.asarray([1.1, 1.23]) + self.assertAllClose(self.constant1.complex_value(t_vals), self.asarray([1.0, 1.0])) self.assertAllClose(self.constant2.complex_value(t_vals), (3.0 + 2j) * np.ones_like(t_vals)) - t_vals = np.array([[1.1, 1.23], [0.1, 0.24]]) + t_vals = self.asarray([[1.1, 1.23], [0.1, 0.24]]) self.assertAllClose( - self.constant1.complex_value(t_vals), np.array([[1.0, 1.0], [1.0, 1.0]]) + self.constant1.complex_value(t_vals), self.asarray([[1.0, 1.0], [1.0, 1.0]]) ) self.assertAllClose(self.constant2.complex_value(t_vals), (3.0 + 2j) * np.ones_like(t_vals)) @@ -362,13 +368,13 @@ def test_call(self): def test_call_vectorized(self): """Test vectorized __call__.""" - t_vals = np.array([1.1, 1.23]) - self.assertAllClose(self.constant1(t_vals), np.array([1.0, 1.0])) - self.assertAllClose(self.constant2(t_vals), np.array([3.0, 3.0])) + t_vals = self.asarray([1.1, 1.23]) + self.assertAllClose(self.constant1(t_vals), self.asarray([1.0, 1.0])) + self.assertAllClose(self.constant2(t_vals), self.asarray([3.0, 3.0])) - t_vals = np.array([[1.1, 1.23], [0.1, 0.24]]) - self.assertAllClose(self.constant1(t_vals), np.array([[1.0, 1.0], [1.0, 1.0]])) - self.assertAllClose(self.constant2(t_vals), np.array([[3.0, 3.0], [3.0, 3.0]])) + t_vals = self.asarray([[1.1, 1.23], [0.1, 0.24]]) + self.assertAllClose(self.constant1(t_vals), self.asarray([[1.0, 1.0], [1.0, 1.0]])) + self.assertAllClose(self.constant2(t_vals), self.asarray([[3.0, 3.0], [3.0, 3.0]])) def test_conjugate(self): """Verify conjugate() functioning correctly.""" @@ -377,13 +383,16 @@ def test_conjugate(self): self.assertAllClose(const_conj(1.1), 3.0) +@partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) class TestDiscreteSignal(QiskitDynamicsTestCase): """Tests for DiscreteSignal object.""" def setUp(self): - self.discrete1 = DiscreteSignal(dt=0.5, samples=np.array([1.0, 2.0, 3.0]), carrier_freq=3.0) + self.discrete1 = DiscreteSignal( + dt=0.5, samples=self.asarray([1.0, 2.0, 3.0]), carrier_freq=3.0 + ) self.discrete2 = DiscreteSignal( - dt=0.5, samples=np.array([1.0 + 2j, 2.0 + 1j, 3.0]), carrier_freq=1.0, phase=3.0 + dt=0.5, samples=self.asarray([1.0 + 2j, 2.0 + 1j, 3.0]), carrier_freq=1.0, phase=3.0 ) def test_envelope(self): @@ -405,14 +414,14 @@ def test_envelope_outside(self): def test_envelope_vectorized(self): """Test vectorized evaluation of envelope.""" - t_vals = np.array([0.1, 1.23]) - self.assertAllClose(self.discrete1.envelope(t_vals), np.array([1.0, 3.0])) - self.assertAllClose(self.discrete2.envelope(t_vals), np.array([1.0 + 2j, 3.0])) + t_vals = self.asarray([0.1, 1.23]) + self.assertAllClose(self.discrete1.envelope(t_vals), self.asarray([1.0, 3.0])) + self.assertAllClose(self.discrete2.envelope(t_vals), self.asarray([1.0 + 2j, 3.0])) - t_vals = np.array([[0.8, 1.23], [0.1, 0.24]]) - self.assertAllClose(self.discrete1.envelope(t_vals), np.array([[2.0, 3.0], [1.0, 1.0]])) + t_vals = self.asarray([[0.8, 1.23], [0.1, 0.24]]) + self.assertAllClose(self.discrete1.envelope(t_vals), self.asarray([[2.0, 3.0], [1.0, 1.0]])) self.assertAllClose( - self.discrete2.envelope(t_vals), np.array([[2.0 + 1j, 3.0], [1 + 2j, 1.0 + 2j]]) + self.discrete2.envelope(t_vals), self.asarray([[2.0 + 1j, 3.0], [1 + 2j, 1.0 + 2j]]) ) def test_complex_value(self): @@ -432,23 +441,23 @@ def test_complex_value(self): def test_complex_value_vectorized(self): """Test vectorized complex_value evaluation.""" - t_vals = np.array([0.1, 1.23]) + t_vals = self.asarray([0.1, 1.23]) phases = np.exp(1j * 2 * np.pi * 3.0 * t_vals) - self.assertAllClose(self.discrete1.complex_value(t_vals), np.array([1.0, 3.0]) * phases) + self.assertAllClose(self.discrete1.complex_value(t_vals), self.asarray([1.0, 3.0]) * phases) phases = np.exp(1j * 2 * np.pi * 1.0 * t_vals + 1j * 3.0) self.assertAllClose( - self.discrete2.complex_value(t_vals), np.array([1.0 + 2j, 3.0]) * phases + self.discrete2.complex_value(t_vals), self.asarray([1.0 + 2j, 3.0]) * phases ) - t_vals = np.array([[0.8, 1.23], [0.1, 0.24]]) + t_vals = self.asarray([[0.8, 1.23], [0.1, 0.24]]) phases = np.exp(1j * 2 * np.pi * 3.0 * t_vals) self.assertAllClose( - self.discrete1.complex_value(t_vals), np.array([[2.0, 3.0], [1.0, 1.0]]) * phases + self.discrete1.complex_value(t_vals), self.asarray([[2.0, 3.0], [1.0, 1.0]]) * phases ) phases = np.exp(1j * 2 * np.pi * 1.0 * t_vals + 1j * 3.0) self.assertAllClose( self.discrete2.complex_value(t_vals), - np.array([[2.0 + 1j, 3.0], [1 + 2j, 1.0 + 2j]]) * phases, + self.asarray([[2.0 + 1j, 3.0], [1 + 2j, 1.0 + 2j]]) * phases, ) def test_call(self): @@ -467,21 +476,21 @@ def test_call(self): def test_call_vectorized(self): """Test vectorized __call__.""" - t_vals = np.array([0.1, 1.23]) + t_vals = self.asarray([0.1, 1.23]) phases = np.exp(1j * 2 * np.pi * 3.0 * t_vals) - self.assertAllClose(self.discrete1(t_vals), np.real(np.array([1.0, 3.0]) * phases)) + self.assertAllClose(self.discrete1(t_vals), np.real(self.asarray([1.0, 3.0]) * phases)) phases = np.exp(1j * 2 * np.pi * 1.0 * t_vals + 1j * 3.0) - self.assertAllClose(self.discrete2(t_vals), np.real(np.array([1.0 + 2j, 3.0]) * phases)) + self.assertAllClose(self.discrete2(t_vals), np.real(self.asarray([1.0 + 2j, 3.0]) * phases)) - t_vals = np.array([[0.8, 1.23], [0.1, 0.24]]) + t_vals = self.asarray([[0.8, 1.23], [0.1, 0.24]]) phases = np.exp(1j * 2 * np.pi * 3.0 * t_vals) self.assertAllClose( - self.discrete1(t_vals), np.real(np.array([[2.0, 3.0], [1.0, 1.0]]) * phases) + self.discrete1(t_vals), np.real(self.asarray([[2.0, 3.0], [1.0, 1.0]]) * phases) ) phases = np.exp(1j * 2 * np.pi * 1.0 * t_vals + 1j * 3.0) self.assertAllClose( self.discrete2(t_vals), - np.real(np.array([[2.0 + 1j, 3.0], [1 + 2j, 1.0 + 2j]]) * phases), + np.real(self.asarray([[2.0 + 1j, 3.0], [1 + 2j, 1.0 + 2j]]) * phases), ) def test_conjugate(self): @@ -495,9 +504,9 @@ def test_conjugate(self): def test_add_samples(self): """Verify that add_samples function works correctly""" - discrete1 = DiscreteSignal(dt=0.5, samples=np.array([]), carrier_freq=3.0) + discrete1 = DiscreteSignal(dt=0.5, samples=self.asarray([]), carrier_freq=3.0) discrete2 = DiscreteSignal( - dt=0.5, samples=np.array([1.0 + 2j, 2.0 + 1j, 3.0]), carrier_freq=1.0, phase=3.0 + dt=0.5, samples=self.asarray([1.0 + 2j, 2.0 + 1j, 3.0]), carrier_freq=1.0, phase=3.0 ) discrete3 = DiscreteSignal(dt=0.5, samples=[], carrier_freq=3.0) @@ -511,6 +520,7 @@ def test_add_samples(self): self.assertAllClose(discrete3.samples, [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 2.0]) +@partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) class TestSignalSum(QiskitDynamicsTestCase): """Test evaluation functions for ``SignalSum``.""" @@ -560,7 +570,7 @@ def test_envelope(self): def test_envelope_vectorized(self): """Test vectorized envelope evaluation.""" - t_vals = np.array([0.0, 1.23]) + t_vals = self.asarray([0.0, 1.23]) self.assertAllClose( self.sig_sum1.envelope(t_vals), [[self.signal1.envelope(t), self.signal2.envelope(t)] for t in t_vals], @@ -581,7 +591,7 @@ def test_envelope_vectorized(self): for t in t_vals ], ) - t_vals = np.array([[0.0, 1.23], [0.1, 2.0]]) + t_vals = self.asarray([[0.0, 1.23], [0.1, 2.0]]) self.assertAllClose( self.sig_sum1.envelope(t_vals), [ @@ -643,7 +653,7 @@ def test_complex_value(self): def test_complex_value_vectorized(self): """Test vectorized complex_value evaluation.""" - t_vals = np.array([0.0, 1.23]) + t_vals = self.asarray([0.0, 1.23]) self.assertAllClose( self.sig_sum1.complex_value(t_vals), [self.signal1.complex_value(t) + self.signal2.complex_value(t) for t in t_vals], @@ -656,7 +666,7 @@ def test_complex_value_vectorized(self): self.double_sig_sum.complex_value(t_vals), [self.signal1.complex_value(t) + self.signal3.complex_value(t) for t in t_vals], ) - t_vals = np.array([[0.0, 1.23], [0.1, 2.0]]) + t_vals = self.asarray([[0.0, 1.23], [0.1, 2.0]]) self.assertAllClose( self.sig_sum1.complex_value(t_vals), [ @@ -692,7 +702,7 @@ def test_call(self): def test_call_vectorized(self): """Test vectorized __call__.""" - t_vals = np.array([0.0, 1.23]) + t_vals = self.asarray([0.0, 1.23]) self.assertAllClose( self.sig_sum1(t_vals), [self.signal1(t) + self.signal2(t) for t in t_vals] ) @@ -702,7 +712,7 @@ def test_call_vectorized(self): self.assertAllClose( self.double_sig_sum(t_vals), [self.signal1(t) + self.signal3(t) for t in t_vals] ) - t_vals = np.array([[0.0, 1.23], [0.1, 2.0]]) + t_vals = self.asarray([[0.0, 1.23], [0.1, 2.0]]) self.assertAllClose( self.sig_sum1(t_vals), [[self.signal1(t) + self.signal2(t) for t in t_row] for t_row in t_vals], @@ -728,6 +738,7 @@ def test_conjugate(self): ) +@partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) class TestDiscreteSignalSum(TestSignalSum): """Tests for DiscreteSignalSum.""" @@ -757,6 +768,7 @@ def test_empty_DiscreteSignal_to_sum(self): self.assertTrue(empty_sum.samples.shape == (1, 0)) +@partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) class TestSignalList(QiskitDynamicsTestCase): """Test cases for SignalList class.""" @@ -774,9 +786,9 @@ def setUp(self): def test_eval(self): """Test evaluation of signal sum.""" - t_vals = np.array([0.12, 0.23, 1.23]) + t_vals = self.asarray([0.12, 0.23, 1.23]) - expected = np.array( + expected = self.asarray( [ self.sig(t_vals) + self.const(t_vals), self.sig(t_vals) * self.discrete_sig(t_vals), @@ -789,9 +801,9 @@ def test_eval(self): def test_complex_value(self): """Test evaluation of signal sum.""" - t_vals = np.array([0.12, 0.23, 1.23]) + t_vals = self.asarray([0.12, 0.23, 1.23]) - expected = np.array( + expected = self.asarray( [ self.sig.complex_value(t_vals) + self.const.complex_value(t_vals), np.real(self.sig.complex_value(t_vals)) * self.discrete_sig.complex_value(t_vals), @@ -804,7 +816,7 @@ def test_complex_value(self): def test_drift(self): """Test drift evaluation.""" - expected = np.array([self.const(0.0), 0, self.const(0.0)]) + expected = self.asarray([self.const(0.0), 0, self.const(0.0)]) self.assertAllClose(self.sig_list.drift, expected) def test_construction_with_numbers(self): @@ -818,9 +830,10 @@ def test_construction_with_numbers(self): # pylint: disable=no-member self.assertFalse(sig_list[2][0].is_constant) - self.assertAllClose(sig_list(3.0), np.array([4.0, 2.0, 3.0])) + self.assertAllClose(sig_list(3.0), self.asarray([4.0, 2.0, 3.0])) +@partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) class TestSignalCollection(QiskitDynamicsTestCase): """Test cases for SignalCollection functionality.""" @@ -843,7 +856,7 @@ def test_SignalSum_subscript(self): sub02 = self.sig_sum[[0, 2]] self.assertTrue(len(sub02) == 2) - t_vals = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) + t_vals = self.asarray([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) self.assertAllClose(sub02(t_vals), self.sig1(t_vals) + self.sig3(t_vals)) def test_DiscreteSignalSum_subscript(self): @@ -851,7 +864,7 @@ def test_DiscreteSignalSum_subscript(self): sub02 = self.discrete_sig_sum[[0, 2]] self.assertTrue(len(sub02) == 2) - t_vals = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) / 4.0 + t_vals = self.asarray([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) / 4.0 self.assertAllClose(sub02(t_vals), self.discrete_sig1(t_vals) + self.discrete_sig3(t_vals)) def test_SignalSum_iterator(self): @@ -873,31 +886,7 @@ def test_DiscreteSignalSum_iterator(self): self.assertAllClose(sum_val, self.discrete_sig_sum(3.0)) -class TestSignalJax(TestSignal, TestJaxBase): - """Jax version of TestSignal.""" - - -class TestConstantJax(TestSignal, TestJaxBase): - """Jax version of TestConstant.""" - - -class TestDiscreteSignalJax(TestDiscreteSignal, TestJaxBase): - """Jax version of TestDiscreteSignal.""" - - -class TestSignalSumJax(TestSignalSum, TestJaxBase): - """Jax version of TestSignalSum.""" - - -class TestDiscreteSignalSumJax(TestDiscreteSignalSum, TestJaxBase): - """Jax version of TestSignalSum.""" - - -class TestSignalListJax(TestSignalList, TestJaxBase): - """Jax version of TestSignalList.""" - - -class TestSignalsJaxTransformations(QiskitDynamicsTestCase, TestJaxBase): +class TestSignalsJaxTransformations(QiskitDynamicsTestCase, JAXTestBase): """Test cases for jax transformations of signals.""" def setUp(self): @@ -924,8 +913,8 @@ def test_jit_grad_constant_construct(self): """Test jitting and grad through a function which constructs a constant signal.""" def eval_const(a): - a = Array(a) - return Signal(a)(1.1).data + a = unp.asarray(a) + return Signal(a)(1.1) jit_eval = jit(eval_const) self.assertAllClose(jit_eval(3.0), 3.0) @@ -935,7 +924,7 @@ def eval_const(a): # validate that is_constant is being properly set def eval_const_conditional(a): - a = Array(a) + a = unp.asarray(a) sig = Signal(a) if sig.is_constant: @@ -952,9 +941,9 @@ def test_jit_grad_carrier_freq_construct(self): """ def eval_sig(a, v, t): - a = Array(a) - v = Array(v) - return Array(Signal(a, v)(t)).data + a = unp.asarray(a) + v = unp.asarray(v) + return unp.asarray(Signal(a, v)(t)) jit_eval = jit(eval_sig) self.assertAllClose(jit_eval(1.0, 1.0, 1.0), 1.0) @@ -964,9 +953,9 @@ def eval_sig(a, v, t): def test_signal_list_jit_eval(self): """Test jit-compilation of SignalList evaluation.""" - call_jit = jit(lambda t: Array(self.signal_list(t)).data) + call_jit = jit(lambda t: unp.asarray(self.signal_list(t))) - t_vals = np.array([0.123, 0.5324, 1.232]) + t_vals = self.asarray([0.123, 0.5324, 1.232]) self.assertAllClose(call_jit(t_vals), self.signal_list(t_vals)) def test_jit_grad_eval(self): @@ -1022,20 +1011,22 @@ def test_jit_grad_eval(self): def _test_jit_signal_eval(self, signal, t=2.1): """jit compilation and evaluation of main signal functions.""" - sig_call_jit = jit(lambda t: Array(signal(t)).data) + sig_call_jit = jit(lambda t: self.asarray(signal(t))) self.assertAllClose(sig_call_jit(t), signal(t)) - sig_envelope_jit = jit(lambda t: Array(signal.envelope(t)).data) + sig_envelope_jit = jit(lambda t: unp.asarray(signal.envelope(t))) self.assertAllClose(sig_envelope_jit(t), signal.envelope(t)) - sig_complex_value_jit = jit(lambda t: Array(signal.complex_value(t)).data) + sig_complex_value_jit = jit(lambda t: unp.asarray(signal.complex_value(t))) self.assertAllClose(sig_complex_value_jit(t), signal.complex_value(t)) def _test_grad_eval(self, signal, t, sig_deriv_val, complex_deriv_val): """Test chained grad and jit compilation.""" - sig_call_jit = jit(grad(lambda t: Array(signal(t)).data)) + sig_call_jit = jit(grad(lambda t: unp.asarray(signal(t)))) self.assertAllClose(sig_call_jit(t), sig_deriv_val) - sig_complex_value_jit_re = jit(grad(lambda t: np.real(Array(signal.complex_value(t))).data)) + sig_complex_value_jit_re = jit( + grad(lambda t: unp.real(unp.asarray(signal.complex_value(t)))) + ) sig_complex_value_jit_imag = jit( - grad(lambda t: np.imag(Array(signal.complex_value(t))).data) + grad(lambda t: np.imag(unp.asarray(signal.complex_value(t)))) ) self.assertAllClose(sig_complex_value_jit_re(t), np.real(complex_deriv_val)) self.assertAllClose(sig_complex_value_jit_imag(t), np.imag(complex_deriv_val)) diff --git a/test/dynamics/signals/test_signals_algebra.py b/test/dynamics/signals/test_signals_algebra.py index e3e974df6..434046756 100644 --- a/test/dynamics/signals/test_signals_algebra.py +++ b/test/dynamics/signals/test_signals_algebra.py @@ -269,7 +269,7 @@ def _test_jit_sum_eval(self, sig1, sig2, t_vals): def eval_func(t): sig_sum = sig1 + sig2 - return sig_sum(t).data + return sig_sum(t) jit_eval_func = jit(eval_func) self.assertAllClose(jit_eval_func(t_vals), eval_func(t_vals)) @@ -279,7 +279,7 @@ def _test_grad_jit_sum_eval(self, sig1, sig2, t): def eval_func(t): sig_sum = sig1 + sig2 - return sig_sum(t).data + return sig_sum(t) jit_eval_func = jit(grad(eval_func)) jit_eval_func(t) @@ -289,7 +289,7 @@ def _test_jit_prod_eval(self, sig1, sig2, t_vals): def eval_func(t): sig_sum = sig1 * sig2 - return sig_sum(t).data + return sig_sum(t) jit_eval_func = jit(eval_func) self.assertAllClose(jit_eval_func(t_vals), eval_func(t_vals)) @@ -299,7 +299,7 @@ def _test_grad_jit_prod_eval(self, sig1, sig2, t): def eval_func(t): sig_sum = sig1 * sig2 - return sig_sum(t).data + return sig_sum(t) jit_eval_func = jit(grad(eval_func)) jit_eval_func(t) From 693e378365c65d5c9ecb3bbc211ae3e752ea60af Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Mon, 16 Oct 2023 14:31:45 -0700 Subject: [PATCH 02/38] potential fix for using test_array_backends --- test/dynamics/common.py | 8 ++++---- test/dynamics/signals/test_signals.py | 20 +++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/test/dynamics/common.py b/test/dynamics/common.py index 7aecc4bd2..5ae15a63c 100644 --- a/test/dynamics/common.py +++ b/test/dynamics/common.py @@ -76,7 +76,7 @@ def assertAllCloseSparse(self, A, B, rtol=1e-8, atol=1e-8): self.assertTrue(np.allclose(A, B, rtol=rtol, atol=atol)) -class NumpyTestBase(unittest.TestCase): +class NumpyTestBase(QiskitDynamicsTestCase): """Base class for tests working with numpy arrays.""" @classmethod @@ -93,7 +93,7 @@ def assertArrayType(self, a): return isinstance(a, np.ndarray) -class JAXTestBase(unittest.TestCase): +class JAXTestBase(QiskitDynamicsTestCase): """Base class for tests working with JAX arrays.""" @classmethod @@ -121,7 +121,7 @@ def assertArrayType(self, a): return isinstance(a, jnp.ndarray) -class ArrayNumpyTestBase(unittest.TestCase): +class ArrayNumpyTestBase(QiskitDynamicsTestCase): """Base class for tests working with qiskit_dynamics Arrays with numpy backend.""" @classmethod @@ -138,7 +138,7 @@ def assertArrayType(self, a): return isinstance(a, Array) and a.backend == "numpy" -class ArrayJaxTestBase(unittest.TestCase): +class ArrayJaxTestBase(QiskitDynamicsTestCase): """Base class for tests working with qiskit_dynamics Arrays with jax backend.""" @classmethod diff --git a/test/dynamics/signals/test_signals.py b/test/dynamics/signals/test_signals.py index 18c691a00..f786f61a1 100644 --- a/test/dynamics/signals/test_signals.py +++ b/test/dynamics/signals/test_signals.py @@ -33,7 +33,7 @@ @partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) -class TestSignal(QiskitDynamicsTestCase): +class TestSignal: """Tests for Signal object.""" def setUp(self): @@ -313,7 +313,7 @@ def test_conjugate(self): @partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) -class TestConstant(QiskitDynamicsTestCase): +class TestConstant: """Tests for constant signal object.""" def setUp(self): @@ -384,7 +384,7 @@ def test_conjugate(self): @partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) -class TestDiscreteSignal(QiskitDynamicsTestCase): +class TestDiscreteSignal: """Tests for DiscreteSignal object.""" def setUp(self): @@ -520,8 +520,7 @@ def test_add_samples(self): self.assertAllClose(discrete3.samples, [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 2.0]) -@partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) -class TestSignalSum(QiskitDynamicsTestCase): +class TestSignalSum: """Test evaluation functions for ``SignalSum``.""" def setUp(self): @@ -738,7 +737,6 @@ def test_conjugate(self): ) -@partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) class TestDiscreteSignalSum(TestSignalSum): """Tests for DiscreteSignalSum.""" @@ -768,8 +766,12 @@ def test_empty_DiscreteSignal_to_sum(self): self.assertTrue(empty_sum.samples.shape == (1, 0)) +test_array_backends(TestSignalSum, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) +test_array_backends(TestDiscreteSignalSum, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) + + @partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) -class TestSignalList(QiskitDynamicsTestCase): +class TestSignalList: """Test cases for SignalList class.""" def setUp(self): @@ -834,7 +836,7 @@ def test_construction_with_numbers(self): @partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) -class TestSignalCollection(QiskitDynamicsTestCase): +class TestSignalCollection: """Test cases for SignalCollection functionality.""" def setUp(self): @@ -886,7 +888,7 @@ def test_DiscreteSignalSum_iterator(self): self.assertAllClose(sum_val, self.discrete_sig_sum(3.0)) -class TestSignalsJaxTransformations(QiskitDynamicsTestCase, JAXTestBase): +class TestSignalsJaxTransformations(JAXTestBase): """Test cases for jax transformations of signals.""" def setUp(self): From 055a086e596fb58da841bfd8f138b93d2f050c01 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 18 Oct 2023 19:22:22 +0900 Subject: [PATCH 03/38] added test_array_backends to TestSignalsJaxTransformations --- test/dynamics/signals/test_signals.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dynamics/signals/test_signals.py b/test/dynamics/signals/test_signals.py index f786f61a1..d10e317b1 100644 --- a/test/dynamics/signals/test_signals.py +++ b/test/dynamics/signals/test_signals.py @@ -887,8 +887,8 @@ def test_DiscreteSignalSum_iterator(self): self.assertAllClose(sum_val, self.discrete_sig_sum(3.0)) - -class TestSignalsJaxTransformations(JAXTestBase): +@partial(test_array_backends, array_libraries=["jax"]) +class TestSignalsJaxTransformations: """Test cases for jax transformations of signals.""" def setUp(self): From f8a3b62853da529d04ca83686423ab47805e2984 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 18 Oct 2023 19:23:55 +0900 Subject: [PATCH 04/38] changed signals.py --- qiskit_dynamics/signals/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 5739e58b7..1d66419db 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2023. +# (C) Copyright IBM 2021, 2023. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory From fcaaa82c664ae58421ca7198ea01272e169ed85b Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 18 Oct 2023 20:14:42 +0900 Subject: [PATCH 05/38] remove unsed module --- test/dynamics/signals/test_signals.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/dynamics/signals/test_signals.py b/test/dynamics/signals/test_signals.py index d10e317b1..24f86b590 100644 --- a/test/dynamics/signals/test_signals.py +++ b/test/dynamics/signals/test_signals.py @@ -23,7 +23,7 @@ from qiskit_dynamics.signals.signals import to_SignalSum from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp -from ..common import JAXTestBase, QiskitDynamicsTestCase, test_array_backends +from ..common import test_array_backends try: from jax import jit, grad @@ -767,7 +767,9 @@ def test_empty_DiscreteSignal_to_sum(self): test_array_backends(TestSignalSum, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) -test_array_backends(TestDiscreteSignalSum, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) +test_array_backends( + TestDiscreteSignalSum, array_libraries=["numpy", "jax", "array_numpy", "array_jax"] +) @partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) @@ -887,6 +889,7 @@ def test_DiscreteSignalSum_iterator(self): self.assertAllClose(sum_val, self.discrete_sig_sum(3.0)) + @partial(test_array_backends, array_libraries=["jax"]) class TestSignalsJaxTransformations: """Test cases for jax transformations of signals.""" From bb5422c3330731f055602c853a4331b2572f8907 Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 19 Oct 2023 10:42:04 +0900 Subject: [PATCH 06/38] pylint: disable=no-member in test_signals.py --- test/dynamics/signals/test_signals.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/dynamics/signals/test_signals.py b/test/dynamics/signals/test_signals.py index 24f86b590..ae7ea4acd 100644 --- a/test/dynamics/signals/test_signals.py +++ b/test/dynamics/signals/test_signals.py @@ -30,6 +30,8 @@ import jax.numpy as jnp except ImportError: pass +# Classes that don't explicitly inherit QiskitDynamicsTestCase get no-member errors +# pylint: disable=no-member @partial(test_array_backends, array_libraries=["numpy", "jax", "array_numpy", "array_jax"]) @@ -827,11 +829,8 @@ def test_construction_with_numbers(self): """Test construction with non-wrapped constant values.""" sig_list = SignalList([4.0, 2.0, Signal(lambda t: t)]) - # pylint: disable=no-member self.assertTrue(sig_list[0][0].is_constant) - # pylint: disable=no-member self.assertTrue(sig_list[1][0].is_constant) - # pylint: disable=no-member self.assertFalse(sig_list[2][0].is_constant) self.assertAllClose(sig_list(3.0), self.asarray([4.0, 2.0, 3.0])) From 47a9de4abd0a98eb0f752fb342d6d78cfbd2ed0b Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 19 Oct 2023 11:53:31 +0900 Subject: [PATCH 07/38] added docstrings to setup in test_signals.py --- test/dynamics/signals/test_signals.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/dynamics/signals/test_signals.py b/test/dynamics/signals/test_signals.py index ae7ea4acd..c291dd4fd 100644 --- a/test/dynamics/signals/test_signals.py +++ b/test/dynamics/signals/test_signals.py @@ -39,6 +39,7 @@ class TestSignal: """Tests for Signal object.""" def setUp(self): + """Setup Signals""" self.signal1 = Signal(lambda _: 0.25, carrier_freq=0.3) self.signal2 = Signal(lambda t: 2.0 * (t**2), carrier_freq=0.1) self.signal3 = Signal(lambda t: 2.0 * (t**2) + 1j * t, carrier_freq=0.1, phase=-0.1) @@ -319,6 +320,7 @@ class TestConstant: """Tests for constant signal object.""" def setUp(self): + """Setup constant Signals""" self.constant1 = Signal(1.0) self.constant2 = Signal(3.0 + 1j * 2) @@ -390,6 +392,7 @@ class TestDiscreteSignal: """Tests for DiscreteSignal object.""" def setUp(self): + """Setup DiscreteSignals""" self.discrete1 = DiscreteSignal( dt=0.5, samples=self.asarray([1.0, 2.0, 3.0]), carrier_freq=3.0 ) @@ -526,6 +529,7 @@ class TestSignalSum: """Test evaluation functions for ``SignalSum``.""" def setUp(self): + """Setup SignalSums""" self.signal1 = Signal(np.vectorize(lambda t: 0.25), carrier_freq=0.3) self.signal2 = Signal(lambda t: 2.0 * (t**2), carrier_freq=0.1) self.signal3 = Signal(lambda t: 2.0 * (t**2) + 1j * t, carrier_freq=0.1, phase=-0.1) @@ -743,6 +747,7 @@ class TestDiscreteSignalSum(TestSignalSum): """Tests for DiscreteSignalSum.""" def setUp(self): + """Setup DiscreteSignalSums""" self.signal1 = Signal(np.vectorize(lambda t: 0.25), carrier_freq=0.3) self.signal2 = Signal(lambda t: 2.0 * (t**2), carrier_freq=0.1) self.signal3 = Signal(lambda t: 2.0 * (t**2) + 1j * t, carrier_freq=0.1, phase=-0.1) @@ -779,6 +784,7 @@ class TestSignalList: """Test cases for SignalList class.""" def setUp(self): + """Setup a SignalList""" self.sig = Signal(lambda t: t, carrier_freq=3.0) self.const = Signal(5.0) self.discrete_sig = DiscreteSignal( @@ -841,6 +847,7 @@ class TestSignalCollection: """Test cases for SignalCollection functionality.""" def setUp(self): + """Setup SignalCollections""" self.sig1 = Signal(lambda t: t, carrier_freq=0.1) self.sig2 = Signal(lambda t: t + 1j * t**2, carrier_freq=3.0, phase=1.0) self.sig3 = Signal(lambda t: t + 1j * t**2, carrier_freq=3.0, phase=1.2) @@ -894,6 +901,7 @@ class TestSignalsJaxTransformations: """Test cases for jax transformations of signals.""" def setUp(self): + """Setup Signals""" self.signal = Signal(lambda t: t**2, carrier_freq=3.0) self.constant = Signal(3 * np.pi) self.discrete_signal = DiscreteSignal( From 941adde8037ed397ad4958863aa31f3a4af9655c Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 20 Oct 2023 14:22:49 +0900 Subject: [PATCH 08/38] transfer_functions.py --- qiskit_dynamics/signals/transfer_functions.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit_dynamics/signals/transfer_functions.py b/qiskit_dynamics/signals/transfer_functions.py index f9b137a5a..46550fefc 100644 --- a/qiskit_dynamics/signals/transfer_functions.py +++ b/qiskit_dynamics/signals/transfer_functions.py @@ -22,7 +22,7 @@ import numpy as np from qiskit import QiskitError -from qiskit_dynamics.array import Array +from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp from .signals import Signal, DiscreteSignal @@ -117,11 +117,11 @@ def _apply(self, signal: Signal) -> Signal: if isinstance(signal, DiscreteSignal): # Perform a discrete time convolution. dt = signal.dt - func_samples = Array([self._func(dt * i) for i in range(signal.duration)]) + func_samples = unp.asarray([self._func(dt * i) for i in range(signal.duration)]) func_samples = func_samples / sum(func_samples) - sig_samples = signal(dt * np.arange(signal.duration)) + sig_samples = signal(dt * unp.arange(signal.duration)) - convoluted_samples = list(np.convolve(func_samples, sig_samples)) + convoluted_samples = list(unp.convolve(func_samples, sig_samples)) return DiscreteSignal(dt, convoluted_samples, carrier_freq=0.0, phase=0.0) else: @@ -233,8 +233,8 @@ def _apply(self, si: Signal, sq: Signal) -> Signal: def mixer_func(t): """Function of the IQ mixer.""" - osc_i = np.cos(wp * t + phi_i) + np.cos(wm * t + phi_i) - osc_q = np.cos(wp * t + phi_q - np.pi / 2) + np.cos(wm * t + phi_q + np.pi / 2) + osc_i = unp.cos(wp * t + phi_i) + unp.cos(wm * t + phi_i) + osc_q = unp.cos(wp * t + phi_q - np.pi / 2) + unp.cos(wm * t + phi_q + np.pi / 2) return si.envelope(t) * osc_i / 2 + sq.envelope(t) * osc_q / 2 return Signal(mixer_func, carrier_freq=0, phase=0) From 46801f70383584a5615eff5e5a6dbe83627856b4 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Oct 2023 13:06:45 -0700 Subject: [PATCH 09/38] changing Signal(Array(x)) to Signal(x) for constant signals --- test/dynamics/models/test_generator_model.py | 2 +- test/dynamics/solvers/test_dyson_magnus_solvers.py | 4 ++-- test/dynamics/solvers/test_solver_classes.py | 2 +- test/dynamics/solvers/test_solver_functions.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/dynamics/models/test_generator_model.py b/test/dynamics/models/test_generator_model.py index b387ca311..5d1b6bb93 100644 --- a/test/dynamics/models/test_generator_model.py +++ b/test/dynamics/models/test_generator_model.py @@ -702,7 +702,7 @@ def test_jit_grad(self): def func(a): model_copy = model.copy() - model_copy.signals = [Signal(Array(a))] + model_copy.signals = [Signal(a)] return model_copy(0.232, y) jitted_func = self.jit_wrap(func) diff --git a/test/dynamics/solvers/test_dyson_magnus_solvers.py b/test/dynamics/solvers/test_dyson_magnus_solvers.py index ec9989f37..1b8b7ba9c 100644 --- a/test/dynamics/solvers/test_dyson_magnus_solvers.py +++ b/test/dynamics/solvers/test_dyson_magnus_solvers.py @@ -344,7 +344,7 @@ def func(c): t0=0.0, n_steps=self.n_steps, y0=np.eye(2, dtype=complex), - signals=[Signal(Array(c), carrier_freq=5.0)], + signals=[Signal(c, carrier_freq=5.0)], ).y[-1] return dyson_yf @@ -362,7 +362,7 @@ def func(c): t0=0.0, n_steps=self.n_steps, y0=np.eye(2, dtype=complex), - signals=[Signal(Array(c), carrier_freq=5.0)], + signals=[Signal(c, carrier_freq=5.0)], ).y[-1] return magnus_yf diff --git a/test/dynamics/solvers/test_solver_classes.py b/test/dynamics/solvers/test_solver_classes.py index f45507a6e..3099fa6a9 100644 --- a/test/dynamics/solvers/test_solver_classes.py +++ b/test/dynamics/solvers/test_solver_classes.py @@ -710,7 +710,7 @@ def func(a): yf = solver.solve( t_span=np.array([0.0, 0.1]), y0=np.array([0.0, 1.0]), - signals=[Signal(Array(a), 5.0)], + signals=[Signal(a, 5.0)], method=self.method, ).y[-1] return yf diff --git a/test/dynamics/solvers/test_solver_functions.py b/test/dynamics/solvers/test_solver_functions.py index 45e29d735..20c9e282e 100644 --- a/test/dynamics/solvers/test_solver_functions.py +++ b/test/dynamics/solvers/test_solver_functions.py @@ -220,7 +220,7 @@ def test_pseudo_random_jit_grad(self): def func(a): model_copy = self.pseudo_random_model.copy() - model_copy.signals = [Signal(Array(a), carrier_freq=1.0)] + model_copy.signals = [Signal(a, carrier_freq=1.0)] results = self.solve(model_copy, t_span=[0.0, 0.1], y0=self.pseudo_random_y0) return results.y[-1] From 2dd286bbb93f7d8cbea8a11ecc9c4891505edb12 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Oct 2023 13:10:18 -0700 Subject: [PATCH 10/38] fixing lanczos jax diag test --- test/dynamics/solvers/test_solver_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dynamics/solvers/test_solver_functions.py b/test/dynamics/solvers/test_solver_functions.py index 20c9e282e..66d8cdcdb 100644 --- a/test/dynamics/solvers/test_solver_functions.py +++ b/test/dynamics/solvers/test_solver_functions.py @@ -367,7 +367,7 @@ def setUp(self): # simulate directly out of frame def pseudo_random_rhs(t, y=None): - op = self.static_operator + self.pseudo_random_signal(t).data * self.operators[0] + op = self.static_operator + self.pseudo_random_signal(t) * self.operators[0] op = -1j * op if y is None: return op From 44a935e9aaed5f8ede7a7ad566ccc52f6ddc4123 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Oct 2023 13:17:56 -0700 Subject: [PATCH 11/38] fixing JAX perturbative solver tests --- test/dynamics/solvers/test_dyson_magnus_solvers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dynamics/solvers/test_dyson_magnus_solvers.py b/test/dynamics/solvers/test_dyson_magnus_solvers.py index 1b8b7ba9c..bc3e04b2f 100644 --- a/test/dynamics/solvers/test_dyson_magnus_solvers.py +++ b/test/dynamics/solvers/test_dyson_magnus_solvers.py @@ -94,13 +94,13 @@ def gaussian(amp, sig, t0, t): T = 7 * sig # end of signal # Function to define gaussian envelope, using gaussian wave function - gaussian_envelope = lambda t: gaussian(Array(amp), Array(sig), Array(t0), Array(t)) + gaussian_envelope = lambda t: gaussian(Array(amp), Array(sig), Array(t0), Array(t)).data obj.gauss_signal = Signal(gaussian_envelope, carrier_freq=5.0) dt = 0.0125 obj.n_steps = int(T // dt) // 3 - + import pdb; pdb.set_trace() hamiltonian_operators = 2 * np.pi * r * np.array([[[0.0, 1.0], [1.0, 0.0]]]) / 2 static_hamiltonian = 2 * np.pi * 5.0 * np.array([[1.0, 0.0], [0.0, -1.0]]) / 2 From 42ff34f36f32405b868b5b612b69110f618ad585 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 25 Oct 2023 14:40:30 +0900 Subject: [PATCH 12/38] remove pdb --- test/dynamics/solvers/test_dyson_magnus_solvers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/dynamics/solvers/test_dyson_magnus_solvers.py b/test/dynamics/solvers/test_dyson_magnus_solvers.py index bc3e04b2f..dbfa83a12 100644 --- a/test/dynamics/solvers/test_dyson_magnus_solvers.py +++ b/test/dynamics/solvers/test_dyson_magnus_solvers.py @@ -100,7 +100,6 @@ def gaussian(amp, sig, t0, t): dt = 0.0125 obj.n_steps = int(T // dt) // 3 - import pdb; pdb.set_trace() hamiltonian_operators = 2 * np.pi * r * np.array([[[0.0, 1.0], [1.0, 0.0]]]) / 2 static_hamiltonian = 2 * np.pi * 5.0 * np.array([[1.0, 0.0], [0.0, -1.0]]) / 2 From d3fd3dc96230e5021da7228f0d6ec6725eceeb75 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 25 Oct 2023 14:51:06 +0900 Subject: [PATCH 13/38] lint --- test/dynamics/solvers/test_solver_classes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/dynamics/solvers/test_solver_classes.py b/test/dynamics/solvers/test_solver_classes.py index 3099fa6a9..34eece960 100644 --- a/test/dynamics/solvers/test_solver_classes.py +++ b/test/dynamics/solvers/test_solver_classes.py @@ -24,7 +24,6 @@ from qiskit_dynamics import Solver, Signal, DiscreteSignal, solve_lmde from qiskit_dynamics.models import HamiltonianModel, LindbladModel, rotating_wave_approximation -from qiskit_dynamics.array import Array from qiskit_dynamics.type_utils import to_array from qiskit_dynamics.solvers.solver_classes import organize_signals_to_channels From 79c45ceacadcaba54e2bb49824a34a19b4719ec8 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 25 Oct 2023 17:02:35 +0900 Subject: [PATCH 14/38] change alias to numpy_alias --- qiskit_dynamics/signals/signals.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 1d66419db..158017415 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -26,7 +26,7 @@ from qiskit import QiskitError from qiskit_dynamics.arraylias import ArrayLike -from qiskit_dynamics.arraylias import DYNAMICS_NUMPY_ALIAS as alias +from qiskit_dynamics.arraylias import DYNAMICS_NUMPY_ALIAS as numpy_alias from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp @@ -307,7 +307,7 @@ def envelope(t): -1, len(self.samples), ) - return alias(like=idx).asarray(self._padded_samples)[idx] + return numpy_alias(like=idx).asarray(self._padded_samples)[idx] Signal.__init__(self, envelope=envelope, carrier_freq=carrier_freq, phase=phase, name=name) From a3509f51a75c690bd89967820c5aeea6a96b1124 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 25 Oct 2023 17:38:29 +0900 Subject: [PATCH 15/38] remove type hint included in ArrayLike --- qiskit_dynamics/signals/signals.py | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 158017415..3d28f7d1d 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -69,9 +69,9 @@ class Signal: def __init__( self, - envelope: Union[Callable, complex, float, int, ArrayLike], - carrier_freq: Union[float, List, ArrayLike] = 0.0, - phase: Union[float, List, ArrayLike] = 0.0, + envelope: Union[Callable, ArrayLike], + carrier_freq: Union[List, ArrayLike] = 0.0, + phase: Union[List, ArrayLike] = 0.0, name: Optional[str] = None, ): """ @@ -123,7 +123,7 @@ def carrier_freq(self) -> ArrayLike: return self._carrier_freq @carrier_freq.setter - def carrier_freq(self, carrier_freq: Union[float, list, ArrayLike]): + def carrier_freq(self, carrier_freq: Union[List, ArrayLike]): """Carrier frequency setter. List handling is to support subclasses storing a list of frequencies.""" self._carrier_freq = unp.asarray(carrier_freq) @@ -135,22 +135,22 @@ def phase(self) -> ArrayLike: return self._phase @phase.setter - def phase(self, phase: Union[float, list, ArrayLike]): + def phase(self, phase: Union[List, ArrayLike]): """Phase setter. List handling is to support subclasses storing a list of phases.""" self._phase = unp.asarray(phase) self._phase_arg = 1j * self._phase - def envelope(self, t: Union[float, ArrayLike]) -> Union[complex, ArrayLike]: + def envelope(self, t: ArrayLike) -> ArrayLike: """Vectorized evaluation of the envelope at time t.""" return self._envelope(t) - def complex_value(self, t: Union[float, ArrayLike]) -> Union[complex, ArrayLike]: + def complex_value(self, t: ArrayLike) -> ArrayLike: """Vectorized evaluation of the complex value at time t.""" arg = self._carrier_arg * t + self._phase_arg return self.envelope(t) * unp.exp(arg) - def __call__(self, t: Union[float, ArrayLike]) -> Union[complex, ArrayLike]: + def __call__(self, t: ArrayLike) -> ArrayLike: """Vectorized evaluation of the signal at time(s) t.""" return unp.real(self.complex_value(t)) @@ -271,8 +271,8 @@ def __init__( dt: float, samples: ArrayLike, start_time: float = 0.0, - carrier_freq: Union[float, ArrayLike] = 0.0, - phase: Union[float, ArrayLike] = 0.0, + carrier_freq: ArrayLike = 0.0, + phase: ArrayLike = 0.0, name: str = None, ): """Initialize a piecewise constant signal. @@ -467,7 +467,7 @@ def __len__(self): """Number of components.""" return len(self.components) - def __getitem__(self, idx: Union[int, ArrayLike, slice]) -> Union[Signal, "SignalCollection"]: + def __getitem__(self, idx: Union[ArrayLike, slice]) -> Union[Signal, "SignalCollection"]: """Get item with NumPy-style subscripting, as if this class were a 1d array.""" if type(idx) != int and type(idx) != slice and type(idx) != list and idx.ndim > 0: @@ -569,7 +569,7 @@ def envelope(t): self, envelope=envelope, carrier_freq=carrier_freqs, phase=phases, name=name ) - def complex_value(self, t: Union[float, ArrayLike]) -> Union[complex, ArrayLike]: + def complex_value(self, t: ArrayLike) -> ArrayLike: """Return the sum of the complex values of each component.""" exp_phases = unp.exp( unp.expand_dims(unp.asarray(t), -1) * self._carrier_arg + self._phase_arg @@ -742,7 +742,7 @@ def __str__(self): return default_str - def __getitem__(self, idx: Union[int, ArrayLike]) -> Signal: + def __getitem__(self, idx: ArrayLike) -> Signal: """Enables numpy-style subscripting, as if this class were a 1d array.""" if type(idx) == int and idx >= len(self): @@ -796,11 +796,11 @@ def __init__(self, signal_list: List[Signal]): ) self._eval_signals = lambda t: unp.asarray([sig(t) for sig in self.components]) - def complex_value(self, t: Union[float, ArrayLike]) -> ArrayLike: + def complex_value(self, t: ArrayLike) -> ArrayLike: """Vectorized evaluation of complex value of components.""" return unp.moveaxis(self._eval_complex_value(t), 0, -1) - def __call__(self, t: Union[float, ArrayLike]) -> ArrayLike: + def __call__(self, t: ArrayLike) -> ArrayLike: """Vectorized evaluation of all components.""" return unp.moveaxis(self._eval_signals(t), 0, -1) @@ -1076,7 +1076,7 @@ def sort_signals(sig1: Signal, sig2: Signal) -> Tuple[Signal, Signal]: return sig1, sig2 -def to_SignalSum(sig: Union[int, float, complex, ArrayLike, Signal]) -> SignalSum: +def to_SignalSum(sig: Union[ArrayLike, Signal]) -> SignalSum: r"""Convert the input to a SignalSum according to: - If it is already a ``SignalSum``\, do nothing. From 28491688c3a048196e7368915067d10975002741 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 25 Oct 2023 17:47:28 +0900 Subject: [PATCH 16/38] add list to Arraylike --- qiskit_dynamics/arraylias/alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_dynamics/arraylias/alias.py b/qiskit_dynamics/arraylias/alias.py index f7d2326bb..aef1fdcfd 100644 --- a/qiskit_dynamics/arraylias/alias.py +++ b/qiskit_dynamics/arraylias/alias.py @@ -34,4 +34,4 @@ DYNAMICS_SCIPY = DYNAMICS_SCIPY_ALIAS() -ArrayLike = Union[DYNAMICS_NUMPY_ALIAS.registered_types()] +ArrayLike = Union[Union[DYNAMICS_NUMPY_ALIAS.registered_types()], list] From 921126b104b3ce99fe2799a04f62ed40876a5210 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 25 Oct 2023 17:47:46 +0900 Subject: [PATCH 17/38] remove list from type hint --- qiskit_dynamics/signals/signals.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 3d28f7d1d..aa87b03b7 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -70,8 +70,8 @@ class Signal: def __init__( self, envelope: Union[Callable, ArrayLike], - carrier_freq: Union[List, ArrayLike] = 0.0, - phase: Union[List, ArrayLike] = 0.0, + carrier_freq: ArrayLike = 0.0, + phase: ArrayLike = 0.0, name: Optional[str] = None, ): """ @@ -123,7 +123,7 @@ def carrier_freq(self) -> ArrayLike: return self._carrier_freq @carrier_freq.setter - def carrier_freq(self, carrier_freq: Union[List, ArrayLike]): + def carrier_freq(self, carrier_freq: ArrayLike): """Carrier frequency setter. List handling is to support subclasses storing a list of frequencies.""" self._carrier_freq = unp.asarray(carrier_freq) @@ -135,7 +135,7 @@ def phase(self) -> ArrayLike: return self._phase @phase.setter - def phase(self, phase: Union[List, ArrayLike]): + def phase(self, phase: ArrayLike): """Phase setter. List handling is to support subclasses storing a list of phases.""" self._phase = unp.asarray(phase) From c0b15c5ce0f6df40e49b9095cf2a9ba5542c832c Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Wed, 25 Oct 2023 08:43:28 -0700 Subject: [PATCH 18/38] fixing JAX perturbative solver test --- test/dynamics/solvers/test_dyson_magnus_solvers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/dynamics/solvers/test_dyson_magnus_solvers.py b/test/dynamics/solvers/test_dyson_magnus_solvers.py index dbfa83a12..7243a0392 100644 --- a/test/dynamics/solvers/test_dyson_magnus_solvers.py +++ b/test/dynamics/solvers/test_dyson_magnus_solvers.py @@ -21,6 +21,7 @@ from qiskit_dynamics import Signal, Solver, DysonSolver, MagnusSolver from qiskit_dynamics.array import Array +from qiskit_dynamics import DYNAMICS_NUMPY as unp from qiskit_dynamics.solvers.perturbative_solvers.expansion_model import ( _construct_DCT, @@ -85,7 +86,7 @@ def build_testing_objects(obj, integration_method="DOP853"): r = 0.2 def gaussian(amp, sig, t0, t): - return amp * np.exp(-((t - t0) ** 2) / (2 * sig**2)) + return amp * unp.exp(-((t - t0) ** 2) / (2 * sig**2)) # specifications for generating envelope amp = 1.0 # amplitude @@ -94,7 +95,7 @@ def gaussian(amp, sig, t0, t): T = 7 * sig # end of signal # Function to define gaussian envelope, using gaussian wave function - gaussian_envelope = lambda t: gaussian(Array(amp), Array(sig), Array(t0), Array(t)).data + gaussian_envelope = lambda t: gaussian(amp, sig, t0, t) obj.gauss_signal = Signal(gaussian_envelope, carrier_freq=5.0) From 9717146590ff557c754014b70df8c87e560d4da1 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Wed, 25 Oct 2023 09:14:21 -0700 Subject: [PATCH 19/38] getting pulse tests working through addition of _preferred_lib function --- qiskit_dynamics/arraylias/alias.py | 17 +++++++++++++++++ qiskit_dynamics/pulse/pulse_to_signals.py | 13 ++++++------- qiskit_dynamics/signals/signals.py | 3 ++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/qiskit_dynamics/arraylias/alias.py b/qiskit_dynamics/arraylias/alias.py index aef1fdcfd..705bc551c 100644 --- a/qiskit_dynamics/arraylias/alias.py +++ b/qiskit_dynamics/arraylias/alias.py @@ -20,6 +20,8 @@ from arraylias import numpy_alias, scipy_alias +from qiskit import QiskitError + from qiskit_dynamics.array import Array # global NumPy and SciPy aliases @@ -35,3 +37,18 @@ ArrayLike = Union[Union[DYNAMICS_NUMPY_ALIAS.registered_types()], list] + + +def _preferred_lib(*args): + if len(args) == 1: + return DYNAMICS_NUMPY_ALIAS.infer_libs(args[0]) + + lib0 = DYNAMICS_NUMPY_ALIAS.infer_libs(args[0])[0] + lib1 = _preferred_lib(args[1:])[0] + + if lib0 == "numpy" and lib1 == "numpy": + return "numpy" + elif lib0 == "jax" or lib1 == "jax": + return "jax" + + raise QiskitError("_preferred_lib could not resolve preferred library.") \ No newline at end of file diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index 9ac451295..dacbc0457 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -38,6 +38,7 @@ from qiskit.pulse.library import SymbolicPulse from qiskit import QiskitError +from qiskit_dynamics import DYNAMICS_NUMPY as unp from qiskit_dynamics.array import Array from qiskit_dynamics.signals import DiscreteSignal @@ -186,12 +187,10 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: # build sample array to append to signal times = self._dt * (start_sample + np.arange(len(inst_samples))) - samples = inst_samples * np.exp( - Array( - 2.0j * np.pi * frequency_shifts[chan] * times - + 1.0j * phases[chan] - + 2.0j * np.pi * phase_accumulations[chan] - ) + samples = inst_samples * unp.exp( + 2.0j * np.pi * frequency_shifts[chan] * times + + 1.0j * phases[chan] + + 2.0j * np.pi * phase_accumulations[chan] ) signals[chan].add_samples(start_sample, samples) @@ -202,7 +201,7 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: phases[chan] = inst.phase if isinstance(inst, ShiftFrequency): - frequency_shifts[chan] = frequency_shifts[chan] + Array(inst.frequency) + frequency_shifts[chan] = frequency_shifts[chan] + inst.frequency phase_accumulations[chan] = ( phase_accumulations[chan] - inst.frequency * start_sample * self._dt ) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index aa87b03b7..4ebf51a69 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -28,6 +28,7 @@ from qiskit_dynamics.arraylias import ArrayLike from qiskit_dynamics.arraylias import DYNAMICS_NUMPY_ALIAS as numpy_alias from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp +from qiskit_dynamics.arraylias.alias import _preferred_lib class Signal: @@ -434,7 +435,7 @@ def add_samples(self, start_sample: int, samples: List): new_samples, unp.repeat(zero_pad, start_sample - len(self.samples)) ) - new_samples = unp.append(new_samples, samples) + new_samples = numpy_alias(like=_preferred_lib(new_samples, samples)).append(new_samples, samples) self._padded_samples = unp.append(new_samples, zero_pad, axis=0) def __str__(self) -> str: From a6043b31e56d91ebd9db9013172b2d2d8314226c Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Wed, 25 Oct 2023 09:44:06 -0700 Subject: [PATCH 20/38] fixing pulse conversion errors --- qiskit_dynamics/models/operator_collections.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit_dynamics/models/operator_collections.py b/qiskit_dynamics/models/operator_collections.py index d7863d7a5..8afa4f605 100644 --- a/qiskit_dynamics/models/operator_collections.py +++ b/qiskit_dynamics/models/operator_collections.py @@ -20,6 +20,8 @@ from qiskit import QiskitError from qiskit.quantum_info.operators.operator import Operator +from qiskit_dynamics.arraylias import DYNAMICS_NUMPY_ALIAS as numpy_alias +from qiskit_dynamics.arraylias.alias import _preferred_lib from qiskit_dynamics.array import Array, wrap from qiskit_dynamics.type_utils import to_array, to_csr, to_BCOO, vec_commutator, vec_dissipator @@ -1357,7 +1359,7 @@ def concatenate_signals( ) -> Array: """Concatenate hamiltonian and linblad signals.""" if self._hamiltonian_operators is not None and self._dissipator_operators is not None: - return np.append(ham_sig_vals, dis_sig_vals, axis=-1) + return numpy_alias(like=_preferred_lib(ham_sig_vals, dis_sig_vals)).append(ham_sig_vals, dis_sig_vals, axis=-1) if self._hamiltonian_operators is not None and self._dissipator_operators is None: return ham_sig_vals if self._hamiltonian_operators is None and self._dissipator_operators is not None: From 09ffd3726d5fb8d94016b3dbde0f5917c6bffce4 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Wed, 25 Oct 2023 10:23:18 -0700 Subject: [PATCH 21/38] fixing remaining errors with simple multiple dispatching function --- qiskit_dynamics/arraylias/alias.py | 36 +++++++++++++++++-- .../models/operator_collections.py | 4 +-- qiskit_dynamics/signals/signals.py | 4 +-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/qiskit_dynamics/arraylias/alias.py b/qiskit_dynamics/arraylias/alias.py index 705bc551c..9f1e567e4 100644 --- a/qiskit_dynamics/arraylias/alias.py +++ b/qiskit_dynamics/arraylias/alias.py @@ -39,7 +39,22 @@ ArrayLike = Union[Union[DYNAMICS_NUMPY_ALIAS.registered_types()], list] -def _preferred_lib(*args): +def _preferred_lib(*args, **kwargs): + """Given a list of args and kwargs with potentially mixed array types, determine the appropriate + library to dispatch to. + + For each argument, DYNAMICS_NUMPY_ALIAS.infer_libs is called to infer the library. If all are + "numpy", then it returns "numpy", and if any are "jax", it returns "jax". + + Args: + *args: Positional arguments. + **kwargs: Keyword arguments. + Returns: + str + Raises: + QiskitError if none of the rules apply. + """ + args = list(args) + list(kwargs.values()) if len(args) == 1: return DYNAMICS_NUMPY_ALIAS.infer_libs(args[0]) @@ -51,4 +66,21 @@ def _preferred_lib(*args): elif lib0 == "jax" or lib1 == "jax": return "jax" - raise QiskitError("_preferred_lib could not resolve preferred library.") \ No newline at end of file + raise QiskitError("_preferred_lib could not resolve preferred library.") + + +def _numpy_multi_dispatch(*args, path, **kwargs): + """Multiple dispatching for NumPy. + + Given *args and **kwargs, dispatch the function specified by path, to the array library + specified by _preferred_lib. + + Args: + *args: Positional arguments to pass to function specified by path. + path: Path in numpy module structure. + **kwargs: Keyword arguments to pass to function specified by path. + Returns: + Result of evaluating the function at path on the arguments using the preferred library. + """ + lib = _preferred_lib(*args, **kwargs) + return DYNAMICS_NUMPY_ALIAS(like=lib, path=path)(*args, **kwargs) \ No newline at end of file diff --git a/qiskit_dynamics/models/operator_collections.py b/qiskit_dynamics/models/operator_collections.py index 8afa4f605..cd7ae52a1 100644 --- a/qiskit_dynamics/models/operator_collections.py +++ b/qiskit_dynamics/models/operator_collections.py @@ -21,7 +21,7 @@ from qiskit import QiskitError from qiskit.quantum_info.operators.operator import Operator from qiskit_dynamics.arraylias import DYNAMICS_NUMPY_ALIAS as numpy_alias -from qiskit_dynamics.arraylias.alias import _preferred_lib +from qiskit_dynamics.arraylias.alias import _numpy_multi_dispatch from qiskit_dynamics.array import Array, wrap from qiskit_dynamics.type_utils import to_array, to_csr, to_BCOO, vec_commutator, vec_dissipator @@ -1359,7 +1359,7 @@ def concatenate_signals( ) -> Array: """Concatenate hamiltonian and linblad signals.""" if self._hamiltonian_operators is not None and self._dissipator_operators is not None: - return numpy_alias(like=_preferred_lib(ham_sig_vals, dis_sig_vals)).append(ham_sig_vals, dis_sig_vals, axis=-1) + return _numpy_multi_dispatch(ham_sig_vals, dis_sig_vals, path="append", axis=-1) if self._hamiltonian_operators is not None and self._dissipator_operators is None: return ham_sig_vals if self._hamiltonian_operators is None and self._dissipator_operators is not None: diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 4ebf51a69..cbf17ca2c 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -28,7 +28,7 @@ from qiskit_dynamics.arraylias import ArrayLike from qiskit_dynamics.arraylias import DYNAMICS_NUMPY_ALIAS as numpy_alias from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp -from qiskit_dynamics.arraylias.alias import _preferred_lib +from qiskit_dynamics.arraylias.alias import _numpy_multi_dispatch class Signal: @@ -435,7 +435,7 @@ def add_samples(self, start_sample: int, samples: List): new_samples, unp.repeat(zero_pad, start_sample - len(self.samples)) ) - new_samples = numpy_alias(like=_preferred_lib(new_samples, samples)).append(new_samples, samples) + new_samples = _numpy_multi_dispatch(new_samples, samples, path="append") self._padded_samples = unp.append(new_samples, zero_pad, axis=0) def __str__(self) -> str: From 3a36f1c6bda57a3ed490a1c7f6dd904e943237cd Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 26 Oct 2023 07:34:45 +0900 Subject: [PATCH 22/38] change isinstance(sig, Array) --- qiskit_dynamics/signals/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index aa87b03b7..65a24eeee 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -544,7 +544,7 @@ def __init__(self, *signals, name: Optional[str] = None): elif isinstance(sig, Signal): components.append(sig) elif isinstance(sig, (int, float, complex)) or ( - isinstance(sig, ArrayLike) and sig.ndim == 0 + not isinstance(sig, (int, float, complex)) and sig.ndim == 0 ): components.append(Signal(sig)) else: From 591b53389939e09d1da66f8e46f4c462aaf1535f Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 26 Oct 2023 07:44:20 +0900 Subject: [PATCH 23/38] change isinstance(sig, Array) --- qiskit_dynamics/signals/signals.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 65a24eeee..9771757b1 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -1094,7 +1094,9 @@ def to_SignalSum(sig: Union[ArrayLike, Signal]) -> SignalSum: QiskitError: If the input type is incompatible with SignalSum. """ - if isinstance(sig, (int, float, complex)) or (isinstance(sig, ArrayLike) and sig.ndim == 0): + if isinstance(sig, (int, float, complex)) or ( + not isinstance(sig, (int, float, complex, list, Signal)) and sig.ndim == 0 + ): return SignalSum(Signal(sig)) elif isinstance(sig, DiscreteSignal) and not isinstance(sig, DiscreteSignalSum): if sig.samples.shape == (0,): From fdb82e3bf44760ac0d0dc5d3c1c898e51712d2ba Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 26 Oct 2023 14:07:45 +0900 Subject: [PATCH 24/38] modify Array to unp to pass tests --- test/dynamics/solvers/test_dyson_magnus_solvers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/dynamics/solvers/test_dyson_magnus_solvers.py b/test/dynamics/solvers/test_dyson_magnus_solvers.py index dbfa83a12..1155623c9 100644 --- a/test/dynamics/solvers/test_dyson_magnus_solvers.py +++ b/test/dynamics/solvers/test_dyson_magnus_solvers.py @@ -21,6 +21,7 @@ from qiskit_dynamics import Signal, Solver, DysonSolver, MagnusSolver from qiskit_dynamics.array import Array +from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp from qiskit_dynamics.solvers.perturbative_solvers.expansion_model import ( _construct_DCT, @@ -85,7 +86,7 @@ def build_testing_objects(obj, integration_method="DOP853"): r = 0.2 def gaussian(amp, sig, t0, t): - return amp * np.exp(-((t - t0) ** 2) / (2 * sig**2)) + return amp * unp.exp(-((t - t0) ** 2) / (2 * sig**2)) # specifications for generating envelope amp = 1.0 # amplitude @@ -94,7 +95,9 @@ def gaussian(amp, sig, t0, t): T = 7 * sig # end of signal # Function to define gaussian envelope, using gaussian wave function - gaussian_envelope = lambda t: gaussian(Array(amp), Array(sig), Array(t0), Array(t)).data + gaussian_envelope = lambda t: gaussian( + unp.asarray(amp), unp.asarray(sig), unp.asarray(t0), unp.asarray(t) + ) obj.gauss_signal = Signal(gaussian_envelope, carrier_freq=5.0) From 3fcec23febc60139742d7433c4807b83c3d10f9a Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 26 Oct 2023 15:25:37 +0900 Subject: [PATCH 25/38] pulse_to_signals --- qiskit_dynamics/pulse/pulse_to_signals.py | 28 ++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index 9ac451295..11b3448ff 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -37,8 +37,9 @@ from qiskit.pulse.exceptions import PulseError from qiskit.pulse.library import SymbolicPulse from qiskit import QiskitError +from qiskit_dynamics.arraylias import ArrayLike +from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp -from qiskit_dynamics.array import Array from qiskit_dynamics.signals import DiscreteSignal try: @@ -185,9 +186,9 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: inst_samples = get_samples(inst.pulse) # build sample array to append to signal - times = self._dt * (start_sample + np.arange(len(inst_samples))) - samples = inst_samples * np.exp( - Array( + times = self._dt * (start_sample + unp.arange(len(inst_samples))) + samples = inst_samples * unp.exp( + unp.asarray( 2.0j * np.pi * frequency_shifts[chan] * times + 1.0j * phases[chan] + 2.0j * np.pi * phase_accumulations[chan] @@ -202,7 +203,7 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: phases[chan] = inst.phase if isinstance(inst, ShiftFrequency): - frequency_shifts[chan] = frequency_shifts[chan] + Array(inst.frequency) + frequency_shifts[chan] = frequency_shifts[chan] + unp.asarray(inst.frequency) phase_accumulations[chan] = ( phase_accumulations[chan] - inst.frequency * start_sample * self._dt ) @@ -226,7 +227,7 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: if sig.duration < max_duration: sig.add_samples( start_sample=sig.duration, - samples=np.zeros(max_duration - sig.duration, dtype=complex), + samples=unp.zeros(max_duration - sig.duration, dtype=complex), ) # filter the channels @@ -273,7 +274,7 @@ def get_awg_signals( new_freq = sig.carrier_freq + if_modulation samples_i = sig.samples - samples_q = np.imag(samples_i) - 1.0j * np.real(samples_i) + samples_q = unp.imag(samples_i) - 1.0j * unp.real(samples_i) sig_i = DiscreteSignal( sig.dt, @@ -325,7 +326,7 @@ def _get_channel(self, channel_name: str): ) from error -def get_samples(pulse: SymbolicPulse) -> np.ndarray: +def get_samples(pulse: SymbolicPulse) -> ArrayLike: """Return samples filled according to the formula that the pulse represents and the parameter values it contains. @@ -349,8 +350,8 @@ def get_samples(pulse: SymbolicPulse) -> np.ndarray: args = [] for symbol in sorted(envelope.free_symbols, key=lambda s: s.name): if symbol.name == "t": - times = Array(np.arange(0, pulse_params["duration"]) + 1 / 2) - args.insert(0, times.data) + times = unp.arange(0, pulse_params["duration"]) + 1 / 2 + args.insert(0, times) continue try: args.append(pulse_params[symbol.name]) @@ -359,7 +360,7 @@ def get_samples(pulse: SymbolicPulse) -> np.ndarray: f"Pulse parameter '{symbol.name}' is not defined for this instance. " "Please check your waveform expression is correct." ) from ex - return _lru_cache_expr(envelope, Array.default_backend())(*args) + return _lru_cache_expr(envelope, DEFAULT_BACKEND)(*args) @functools.lru_cache(maxsize=None) @@ -381,11 +382,12 @@ def _lru_cache_expr(expr: sym.Expr, backend) -> Callable: return sym.lambdify(params, expr, modules=backend) -def _nyquist_warn(frequency_shift: Array, dt: float, channel: str): +def _nyquist_warn(frequency_shift: ArrayLike, dt: float, channel: str): """Raise a warning if the frequency shift is above the Nyquist frequency given by ``dt``.""" if ( - Array(frequency_shift).backend != "jax" or not isinstance(jnp.array(0), jax.core.Tracer) + isinstance(frequency_shift, (list, np.array)) + or not isinstance(jnp.array(0), jax.core.Tracer) ) and np.abs(frequency_shift) > 0.5 / dt: warn( "Due to SetFrequency and ShiftFrequency instructions, the digital carrier frequency " From facd132734931d826de041b4397d2dac5340d2d7 Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 26 Oct 2023 15:36:08 +0900 Subject: [PATCH 26/38] lint --- qiskit_dynamics/pulse/pulse_to_signals.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index 11b3448ff..992ad9930 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -37,6 +37,7 @@ from qiskit.pulse.exceptions import PulseError from qiskit.pulse.library import SymbolicPulse from qiskit import QiskitError +from qiskit_dynamics.array import Array from qiskit_dynamics.arraylias import ArrayLike from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp @@ -360,7 +361,7 @@ def get_samples(pulse: SymbolicPulse) -> ArrayLike: f"Pulse parameter '{symbol.name}' is not defined for this instance. " "Please check your waveform expression is correct." ) from ex - return _lru_cache_expr(envelope, DEFAULT_BACKEND)(*args) + return _lru_cache_expr(envelope, Array.default_backend())(*args) @functools.lru_cache(maxsize=None) @@ -386,7 +387,7 @@ def _nyquist_warn(frequency_shift: ArrayLike, dt: float, channel: str): """Raise a warning if the frequency shift is above the Nyquist frequency given by ``dt``.""" if ( - isinstance(frequency_shift, (list, np.array)) + isinstance(frequency_shift, (list, np.ndarray)) or not isinstance(jnp.array(0), jax.core.Tracer) ) and np.abs(frequency_shift) > 0.5 / dt: warn( From 2d2321c724330f155f73741f12498514ef263e8e Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 26 Oct 2023 15:54:00 +0900 Subject: [PATCH 27/38] modify if statement in _nyquist_warn --- qiskit_dynamics/pulse/pulse_to_signals.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index 992ad9930..9c6afa198 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -385,9 +385,8 @@ def _lru_cache_expr(expr: sym.Expr, backend) -> Callable: def _nyquist_warn(frequency_shift: ArrayLike, dt: float, channel: str): """Raise a warning if the frequency shift is above the Nyquist frequency given by ``dt``.""" - if ( - isinstance(frequency_shift, (list, np.ndarray)) + isinstance(frequency_shift, (int, float, list, np.ndarray)) or not isinstance(jnp.array(0), jax.core.Tracer) ) and np.abs(frequency_shift) > 0.5 / dt: warn( From 54cf5142736217466caf3d0e0ce7985d9d3d5c57 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Thu, 26 Oct 2023 12:53:25 -0700 Subject: [PATCH 28/38] getting docs to ubild --- docs/tutorials/optimizing_pulse_sequence.rst | 8 ++++---- docs/userguide/how_to_configure_simulations.rst | 6 +++--- docs/userguide/how_to_use_jax.rst | 1 - docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst | 2 +- docs/userguide/perturbative_solvers.rst | 4 ++-- qiskit_dynamics/signals/signals.py | 4 ++-- qiskit_dynamics/signals/transfer_functions.py | 5 +++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/tutorials/optimizing_pulse_sequence.rst b/docs/tutorials/optimizing_pulse_sequence.rst index 6ad577253..757465c3a 100644 --- a/docs/tutorials/optimizing_pulse_sequence.rst +++ b/docs/tutorials/optimizing_pulse_sequence.rst @@ -115,9 +115,10 @@ example of one. .. jupyter-execute:: from qiskit_dynamics import DiscreteSignal - from qiskit_dynamics.array import Array from qiskit_dynamics.signals import Convolution + import jax.numpy as jnp + # define convolution filter def gaus(t): sigma = 15 @@ -128,13 +129,12 @@ example of one. # define function mapping parameters to signals def signal_mapping(params): - samples = Array(params) # map samples into [-1, 1] - bounded_samples = np.arctan(samples) / (np.pi / 2) + bounded_samples = jnp.arctan(params) / (np.pi / 2) # pad with 0 at beginning - padded_samples = np.append(Array([0], dtype=complex), bounded_samples) + padded_samples = jnp.append(jnp.array([0], dtype=complex), bounded_samples) # apply filter output_signal = convolution(DiscreteSignal(dt=1., samples=padded_samples)) diff --git a/docs/userguide/how_to_configure_simulations.rst b/docs/userguide/how_to_configure_simulations.rst index 64b0babb6..21ffa965b 100644 --- a/docs/userguide/how_to_configure_simulations.rst +++ b/docs/userguide/how_to_configure_simulations.rst @@ -246,7 +246,7 @@ further highlight the benefits of the sparse representation. static_hamiltonian = 2 * np.pi * v * N + np.pi * anharm * N * (N - np.eye(dim)) drive_hamiltonian = 2 * np.pi * r * (a + adag) - drive_signal = Signal(Array(1.), carrier_freq=v) + drive_signal = Signal(1., carrier_freq=v) y0 = np.zeros(dim, dtype=complex) y0[1] = 1. @@ -266,7 +266,7 @@ amplitude, and just-in-time compile it using JAX. ) def dense_func(amp): - drive_signal = Signal(Array(amp), carrier_freq=v) + drive_signal = Signal(amp, carrier_freq=v) res = solver.solve( t_span=[0., T], y0=y0, @@ -292,7 +292,7 @@ diagonal, but we explicitly highlight the need for this. evaluation_mode='sparse') def sparse_func(amp): - drive_signal = Signal(Array(amp), carrier_freq=v) + drive_signal = Signal(amp, carrier_freq=v) res = sparse_solver.solve( t_span=[0., T], y0=y0, diff --git a/docs/userguide/how_to_use_jax.rst b/docs/userguide/how_to_use_jax.rst index 905013e0a..4efec44e6 100644 --- a/docs/userguide/how_to_use_jax.rst +++ b/docs/userguide/how_to_use_jax.rst @@ -138,7 +138,6 @@ before setting the signals, to ensure the simulation function remains pure. def sim_function(amp): # define a constant signal - amp = Array(amp) signals = [Signal(amp, carrier_freq=w)] # simulate and return results diff --git a/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst b/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst index 35a66c717..01914f19f 100644 --- a/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst +++ b/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst @@ -137,6 +137,6 @@ JAX-compiled (or more generally, JAX-transformed). # convert from a pulse schedule to a list of signals converter = InstructionToSignals(dt, carriers={"d0": w}) - return converter.get_signals(schedule)[0].samples.data + return converter.get_signals(schedule)[0].samples jax.jit(jit_func)(0.4) diff --git a/docs/userguide/perturbative_solvers.rst b/docs/userguide/perturbative_solvers.rst index df9c15417..a1d388bbb 100644 --- a/docs/userguide/perturbative_solvers.rst +++ b/docs/userguide/perturbative_solvers.rst @@ -179,7 +179,7 @@ these functions gives a sense of the speeds attainable by these solvers. """For a given envelope amplitude, simulate the final unitary using the Dyson solver. """ - drive_signal = Signal(lambda t: Array(amp) * envelope_func(t), carrier_freq=v) + drive_signal = Signal(lambda t: amp * envelope_func(t), carrier_freq=v) return dyson_solver.solve( signals=[drive_signal], y0=np.eye(dim, dtype=complex), @@ -220,7 +220,7 @@ accuracy and simulation speed. # specify tolerance as an argument to run the simulation at different tolerances def ode_sim(amp, tol): - drive_signal = Signal(lambda t: Array(amp) * envelope_func(t), carrier_freq=v) + drive_signal = Signal(lambda t: amp * envelope_func(t), carrier_freq=v) res = solver.solve( t_span=[0., int(T // dt) * dt], y0=np.eye(dim, dtype=complex), diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index cbf17ca2c..0624ef9c9 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -28,7 +28,7 @@ from qiskit_dynamics.arraylias import ArrayLike from qiskit_dynamics.arraylias import DYNAMICS_NUMPY_ALIAS as numpy_alias from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp -from qiskit_dynamics.arraylias.alias import _numpy_multi_dispatch +from qiskit_dynamics.arraylias.alias import _numpy_multi_dispatch, _preferred_lib class Signal: @@ -308,7 +308,7 @@ def envelope(t): -1, len(self.samples), ) - return numpy_alias(like=idx).asarray(self._padded_samples)[idx] + return numpy_alias(like=_preferred_lib(self._padded_samples, idx)).asarray(self._padded_samples)[idx] Signal.__init__(self, envelope=envelope, carrier_freq=carrier_freq, phase=phase, name=name) diff --git a/qiskit_dynamics/signals/transfer_functions.py b/qiskit_dynamics/signals/transfer_functions.py index 46550fefc..c8cf32c73 100644 --- a/qiskit_dynamics/signals/transfer_functions.py +++ b/qiskit_dynamics/signals/transfer_functions.py @@ -23,6 +23,7 @@ from qiskit import QiskitError from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp +from qiskit_dynamics.arraylias.alias import _numpy_multi_dispatch from .signals import Signal, DiscreteSignal @@ -118,10 +119,10 @@ def _apply(self, signal: Signal) -> Signal: # Perform a discrete time convolution. dt = signal.dt func_samples = unp.asarray([self._func(dt * i) for i in range(signal.duration)]) - func_samples = func_samples / sum(func_samples) + func_samples = func_samples / unp.sum(func_samples) sig_samples = signal(dt * unp.arange(signal.duration)) - convoluted_samples = list(unp.convolve(func_samples, sig_samples)) + convoluted_samples = _numpy_multi_dispatch(func_samples, sig_samples, path="convolve")#unp.convolve(func_samples, sig_samples) return DiscreteSignal(dt, convoluted_samples, carrier_freq=0.0, phase=0.0) else: From 787f0f834d4b4e32b333194c385b5c5ead352f30 Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 27 Oct 2023 15:14:23 +0900 Subject: [PATCH 29/38] black --- qiskit_dynamics/arraylias/alias.py | 10 +++++----- qiskit_dynamics/signals/signals.py | 4 +++- qiskit_dynamics/signals/transfer_functions.py | 4 +++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/qiskit_dynamics/arraylias/alias.py b/qiskit_dynamics/arraylias/alias.py index 9f1e567e4..1f5d70257 100644 --- a/qiskit_dynamics/arraylias/alias.py +++ b/qiskit_dynamics/arraylias/alias.py @@ -42,7 +42,7 @@ def _preferred_lib(*args, **kwargs): """Given a list of args and kwargs with potentially mixed array types, determine the appropriate library to dispatch to. - + For each argument, DYNAMICS_NUMPY_ALIAS.infer_libs is called to infer the library. If all are "numpy", then it returns "numpy", and if any are "jax", it returns "jax". @@ -57,7 +57,7 @@ def _preferred_lib(*args, **kwargs): args = list(args) + list(kwargs.values()) if len(args) == 1: return DYNAMICS_NUMPY_ALIAS.infer_libs(args[0]) - + lib0 = DYNAMICS_NUMPY_ALIAS.infer_libs(args[0])[0] lib1 = _preferred_lib(args[1:])[0] @@ -65,13 +65,13 @@ def _preferred_lib(*args, **kwargs): return "numpy" elif lib0 == "jax" or lib1 == "jax": return "jax" - + raise QiskitError("_preferred_lib could not resolve preferred library.") def _numpy_multi_dispatch(*args, path, **kwargs): """Multiple dispatching for NumPy. - + Given *args and **kwargs, dispatch the function specified by path, to the array library specified by _preferred_lib. @@ -83,4 +83,4 @@ def _numpy_multi_dispatch(*args, path, **kwargs): Result of evaluating the function at path on the arguments using the preferred library. """ lib = _preferred_lib(*args, **kwargs) - return DYNAMICS_NUMPY_ALIAS(like=lib, path=path)(*args, **kwargs) \ No newline at end of file + return DYNAMICS_NUMPY_ALIAS(like=lib, path=path)(*args, **kwargs) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index b4b08ce7f..751313020 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -308,7 +308,9 @@ def envelope(t): -1, len(self.samples), ) - return numpy_alias(like=_preferred_lib(self._padded_samples, idx)).asarray(self._padded_samples)[idx] + return numpy_alias(like=_preferred_lib(self._padded_samples, idx)).asarray( + self._padded_samples + )[idx] Signal.__init__(self, envelope=envelope, carrier_freq=carrier_freq, phase=phase, name=name) diff --git a/qiskit_dynamics/signals/transfer_functions.py b/qiskit_dynamics/signals/transfer_functions.py index c8cf32c73..317d86c62 100644 --- a/qiskit_dynamics/signals/transfer_functions.py +++ b/qiskit_dynamics/signals/transfer_functions.py @@ -122,7 +122,9 @@ def _apply(self, signal: Signal) -> Signal: func_samples = func_samples / unp.sum(func_samples) sig_samples = signal(dt * unp.arange(signal.duration)) - convoluted_samples = _numpy_multi_dispatch(func_samples, sig_samples, path="convolve")#unp.convolve(func_samples, sig_samples) + convoluted_samples = _numpy_multi_dispatch( + func_samples, sig_samples, path="convolve" + ) # unp.convolve(func_samples, sig_samples) return DiscreteSignal(dt, convoluted_samples, carrier_freq=0.0, phase=0.0) else: From de6aff072c354ac23a59beb25cf803ef12ba4a4e Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 27 Oct 2023 15:33:10 +0900 Subject: [PATCH 30/38] reorganize import --- qiskit_dynamics/__init__.py | 1 + qiskit_dynamics/arraylias/alias.py | 2 +- qiskit_dynamics/models/operator_collections.py | 1 - qiskit_dynamics/pulse/pulse_to_signals.py | 2 +- qiskit_dynamics/signals/signals.py | 6 +++--- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit_dynamics/__init__.py b/qiskit_dynamics/__init__.py index 1e7ee2bd6..b707d440d 100644 --- a/qiskit_dynamics/__init__.py +++ b/qiskit_dynamics/__init__.py @@ -28,6 +28,7 @@ DYNAMICS_SCIPY_ALIAS, DYNAMICS_NUMPY, DYNAMICS_SCIPY, + ArrayLike, ) from .models.rotating_frame import RotatingFrame diff --git a/qiskit_dynamics/arraylias/alias.py b/qiskit_dynamics/arraylias/alias.py index 1f5d70257..6115d1e72 100644 --- a/qiskit_dynamics/arraylias/alias.py +++ b/qiskit_dynamics/arraylias/alias.py @@ -52,7 +52,7 @@ def _preferred_lib(*args, **kwargs): Returns: str Raises: - QiskitError if none of the rules apply. + QiskitError: if none of the rules apply. """ args = list(args) + list(kwargs.values()) if len(args) == 1: diff --git a/qiskit_dynamics/models/operator_collections.py b/qiskit_dynamics/models/operator_collections.py index cd7ae52a1..d85fb539d 100644 --- a/qiskit_dynamics/models/operator_collections.py +++ b/qiskit_dynamics/models/operator_collections.py @@ -20,7 +20,6 @@ from qiskit import QiskitError from qiskit.quantum_info.operators.operator import Operator -from qiskit_dynamics.arraylias import DYNAMICS_NUMPY_ALIAS as numpy_alias from qiskit_dynamics.arraylias.alias import _numpy_multi_dispatch from qiskit_dynamics.array import Array, wrap from qiskit_dynamics.type_utils import to_array, to_csr, to_BCOO, vec_commutator, vec_dissipator diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index e506849e3..d9f045695 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -39,8 +39,8 @@ from qiskit import QiskitError from qiskit_dynamics.array import Array -from qiskit_dynamics import ArrayLike from qiskit_dynamics import DYNAMICS_NUMPY as unp +from qiskit_dynamics import ArrayLike from qiskit_dynamics.signals import DiscreteSignal diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 751313020..3a38c680d 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -25,9 +25,9 @@ from matplotlib import pyplot as plt from qiskit import QiskitError -from qiskit_dynamics.arraylias import ArrayLike -from qiskit_dynamics.arraylias import DYNAMICS_NUMPY_ALIAS as numpy_alias -from qiskit_dynamics.arraylias import DYNAMICS_NUMPY as unp +from qiskit_dynamics import ArrayLike +from qiskit_dynamics import DYNAMICS_NUMPY_ALIAS as numpy_alias +from qiskit_dynamics import DYNAMICS_NUMPY as unp from qiskit_dynamics.arraylias.alias import _numpy_multi_dispatch, _preferred_lib From 3a4d2b18e2277f41e4a3cce8a3c66a5611c9a963 Mon Sep 17 00:00:00 2001 From: Kento Ueda <38037695+to24toro@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:49:58 +0900 Subject: [PATCH 31/38] Update qiskit_dynamics/signals/signals.py Co-authored-by: Daniel Puzzuoli --- qiskit_dynamics/signals/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 3a38c680d..2d49e365f 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -295,7 +295,7 @@ def __init__( if len(samples) == 0: zero_pad = unp.asarray([0]) else: - zero_pad = unp.expand_dims(unp.zeros_like(unp.asarray(samples[0])), 0) + zero_pad = unp.expand_dims(unp.zeros_like(samples[0]), 0) self._padded_samples = unp.append(samples, zero_pad, axis=0) self._start_time = start_time From 94480ea6cef511a4f0c9592ddcf19460b8a24c5e Mon Sep 17 00:00:00 2001 From: Kento Ueda <38037695+to24toro@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:50:15 +0900 Subject: [PATCH 32/38] Update qiskit_dynamics/signals/signals.py Co-authored-by: Daniel Puzzuoli --- qiskit_dynamics/signals/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 2d49e365f..280f4a985 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -429,7 +429,7 @@ def add_samples(self, start_sample: int, samples: List): if start_sample < len(self.samples): raise QiskitError("Samples can only be added afer the last sample.") - zero_pad = unp.expand_dims(unp.zeros_like(unp.asarray(samples[0])), 0) + zero_pad = unp.expand_dims(unp.zeros_like(samples[0]), 0) new_samples = self.samples if len(self.samples) < start_sample: From 5a6b474a74f7bdf69a6401363eadd2dcb3b23cb6 Mon Sep 17 00:00:00 2001 From: Kento Ueda <38037695+to24toro@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:50:51 +0900 Subject: [PATCH 33/38] Update qiskit_dynamics/signals/signals.py Co-authored-by: Daniel Puzzuoli --- qiskit_dynamics/signals/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 280f4a985..e33b211c1 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -473,7 +473,7 @@ def __len__(self): def __getitem__(self, idx: Union[ArrayLike, slice]) -> Union[Signal, "SignalCollection"]: """Get item with NumPy-style subscripting, as if this class were a 1d array.""" - if type(idx) != int and type(idx) != slice and type(idx) != list and idx.ndim > 0: + if type(idx) != slice and unp.asarray(idx).ndim > 0: idx = list(idx) # get a list of the subcomponents From dcbd902ff537cdc061b81d0931914b3a2a30c91e Mon Sep 17 00:00:00 2001 From: Kento Ueda <38037695+to24toro@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:51:12 +0900 Subject: [PATCH 34/38] Update qiskit_dynamics/signals/signals.py Co-authored-by: Daniel Puzzuoli --- qiskit_dynamics/signals/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index e33b211c1..4b58c38e3 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -644,7 +644,7 @@ def __init__( samples = unp.asarray(samples) if carrier_freq is None: - carrier_freq = unp.zeros(samples.shape[-1], dtype=float) + carrier_freq = np.zeros(samples.shape[-1], dtype=float) if phase is None: phase = unp.zeros(samples.shape[-1], dtype=float) From 0d77f8b0db15121327a537d808069676726a91cb Mon Sep 17 00:00:00 2001 From: Kento Ueda <38037695+to24toro@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:51:29 +0900 Subject: [PATCH 35/38] Update qiskit_dynamics/signals/signals.py Co-authored-by: Daniel Puzzuoli --- qiskit_dynamics/signals/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 4b58c38e3..48d58bbfb 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -647,7 +647,7 @@ def __init__( carrier_freq = np.zeros(samples.shape[-1], dtype=float) if phase is None: - phase = unp.zeros(samples.shape[-1], dtype=float) + phase = np.zeros(samples.shape[-1], dtype=float) DiscreteSignal.__init__( self, From 2e1436e838dec1acbb5dd478b39a773d353741b4 Mon Sep 17 00:00:00 2001 From: to24toro Date: Mon, 30 Oct 2023 17:15:06 +0900 Subject: [PATCH 36/38] reflect on comments --- qiskit_dynamics/pulse/pulse_to_signals.py | 2 +- qiskit_dynamics/signals/signals.py | 45 ++++++++++++----------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index d9f045695..39968ba2d 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -227,7 +227,7 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: if sig.duration < max_duration: sig.add_samples( start_sample=sig.duration, - samples=unp.zeros(max_duration - sig.duration, dtype=complex), + samples=np.zeros(max_duration - sig.duration, dtype=complex), ) # filter the channels diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index 48d58bbfb..acd9d301b 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -293,7 +293,7 @@ def __init__( samples = unp.asarray(samples) if len(samples) == 0: - zero_pad = unp.asarray([0]) + zero_pad = np.asarray([0]) else: zero_pad = unp.expand_dims(unp.zeros_like(samples[0]), 0) self._padded_samples = unp.append(samples, zero_pad, axis=0) @@ -546,14 +546,15 @@ def __init__(self, *signals, name: Optional[str] = None): components += sig.components elif isinstance(sig, Signal): components.append(sig) - elif isinstance(sig, (int, float, complex)) or ( - not isinstance(sig, (int, float, complex)) and sig.ndim == 0 - ): - components.append(Signal(sig)) else: - raise QiskitError( - "Components of a SignalSum must be instances of a Signal subclass." - ) + try: + if unp.asarray(sig).ndim == 0: + components.append(Signal(sig)) + except QiskitError as qe: + raise QiskitError( + "Components of a SignalSum must be instances " + "of a Signal subclass or a scalar." + ) from qe SignalCollection.__init__(self, components) @@ -574,9 +575,7 @@ def envelope(t): def complex_value(self, t: ArrayLike) -> ArrayLike: """Return the sum of the complex values of each component.""" - exp_phases = unp.exp( - unp.expand_dims(unp.asarray(t), -1) * self._carrier_arg + self._phase_arg - ) + exp_phases = unp.exp(unp.expand_dims(t, -1) * self._carrier_arg + self._phase_arg) return unp.sum(self.envelope(t) * exp_phases, axis=-1) def __str__(self): @@ -606,9 +605,7 @@ def flatten(self) -> Signal: shifted_arg = self._carrier_arg - (1j * 2 * np.pi * ave_freq) def merged_env(t): - exp_phases = unp.exp( - unp.expand_dims(unp.asarray(t), -1) * shifted_arg + self._phase_arg - ) + exp_phases = unp.exp(unp.expand_dims(t, -1) * shifted_arg + self._phase_arg) return unp.sum(self.envelope(t) * exp_phases, axis=-1) return Signal(envelope=merged_env, carrier_freq=ave_freq, name=str(self)) @@ -717,7 +714,7 @@ def from_SignalSum( if sample_carrier: freq = 0.0 * freq - exp_phases = unp.exp(unp.expand_dims(unp.asarray(times), -1) * signal_sum._carrier_arg) + exp_phases = unp.exp(unp.expand_dims(times, -1) * signal_sum._carrier_arg) samples = signal_sum.envelope(times) * exp_phases else: samples = signal_sum.envelope(times) @@ -858,9 +855,11 @@ def signal_add(sig1: Signal, sig2: Signal) -> SignalSum: and sig1.start_time == sig2.start_time and sig1.duration == sig2.duration ): - samples = unp.append(sig1.samples, sig2.samples, axis=1) - carrier_freq = unp.append(sig1.carrier_freq, sig2.carrier_freq) - phase = unp.append(sig1.phase, sig2.phase) + samples = _numpy_multi_dispatch(sig1.samples, sig2.samples, axis=1, path="append") + carrier_freq = _numpy_multi_dispatch( + sig1.carrier_freq, sig2.carrier_freq, path="append" + ) + phase = _numpy_multi_dispatch(sig1.phase, sig2.phase, path="append") return DiscreteSignalSum( dt=sig1.dt, samples=samples, @@ -928,15 +927,19 @@ def signal_multiply(sig1: Signal, sig2: Signal) -> SignalSum: order="C", ) ) - samples = unp.append(new_samples, new_samples_conj, axis=1) + samples = _numpy_multi_dispatch(new_samples, new_samples_conj, axis=1, path="append") new_freqs = sig1.carrier_freq + sig2.carrier_freq new_freqs_conj = sig1.carrier_freq - sig2.carrier_freq - freqs = unp.append(unp.asarray(new_freqs), unp.asarray(new_freqs_conj)) + freqs = _numpy_multi_dispatch( + unp.asarray(new_freqs), unp.asarray(new_freqs_conj), path="append" + ) new_phases = sig1.phase + sig2.phase new_phases_conj = sig1.phase - sig2.phase - phases = unp.append(unp.asarray(new_phases), unp.asarray(new_phases_conj)) + phases = _numpy_multi_dispatch( + unp.asarray(new_phases), unp.asarray(new_phases_conj), path="append" + ) return DiscreteSignalSum( dt=sig1.dt, From 619581b396ffeb2129ce4d36cb5d913ecda41f20 Mon Sep 17 00:00:00 2001 From: Daniel Puzzuoli Date: Mon, 30 Oct 2023 07:39:13 -0700 Subject: [PATCH 37/38] Update qiskit_dynamics/signals/transfer_functions.py --- qiskit_dynamics/signals/transfer_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_dynamics/signals/transfer_functions.py b/qiskit_dynamics/signals/transfer_functions.py index 317d86c62..0a1459786 100644 --- a/qiskit_dynamics/signals/transfer_functions.py +++ b/qiskit_dynamics/signals/transfer_functions.py @@ -124,7 +124,7 @@ def _apply(self, signal: Signal) -> Signal: convoluted_samples = _numpy_multi_dispatch( func_samples, sig_samples, path="convolve" - ) # unp.convolve(func_samples, sig_samples) + ) return DiscreteSignal(dt, convoluted_samples, carrier_freq=0.0, phase=0.0) else: From 12c36da0677f74b065940d64aaa5063b20306f73 Mon Sep 17 00:00:00 2001 From: Daniel Puzzuoli Date: Mon, 30 Oct 2023 07:52:19 -0700 Subject: [PATCH 38/38] fixing formatting --- qiskit_dynamics/signals/transfer_functions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qiskit_dynamics/signals/transfer_functions.py b/qiskit_dynamics/signals/transfer_functions.py index 0a1459786..da9188b65 100644 --- a/qiskit_dynamics/signals/transfer_functions.py +++ b/qiskit_dynamics/signals/transfer_functions.py @@ -122,9 +122,7 @@ def _apply(self, signal: Signal) -> Signal: func_samples = func_samples / unp.sum(func_samples) sig_samples = signal(dt * unp.arange(signal.duration)) - convoluted_samples = _numpy_multi_dispatch( - func_samples, sig_samples, path="convolve" - ) + convoluted_samples = _numpy_multi_dispatch(func_samples, sig_samples, path="convolve") return DiscreteSignal(dt, convoluted_samples, carrier_freq=0.0, phase=0.0) else: