def __init__( self, envelope: Callable, carrier_freqs: Array, phases: Array, drift_array: Optional[Array] = None, ): """Initialize with vector-valued envelope, carrier frequencies for each entry, and a drift_array, which corresponds to the value of the signal when the "time-dependent terms" are "off". Args: envelope: function of a single float returning an array. carrier_freqs: list of carrier frequencies for each component of the envelope. phases: list of carrier phases for each component of the envelope. drift_array: a default array meant to be the value of the envelope when all "time-dependent terms" are off. """ carrier_freqs = Array(carrier_freqs) phases = Array(phases) self.envelope = envelope self.carrier_freqs = carrier_freqs self.phases = phases self._im_angular_freqs = 1j * 2 * np.pi * carrier_freqs # if not supplied nothing is assumed, constant array is taken as all # zeros if drift_array is None: self.drift_array = Array(np.zeros(len(self.carrier_freqs))) else: self.drift_array = Array(drift_array)
def __init__( self, envelope: Union[Callable, complex, float, int], carrier_freq: float = 0.0, phase: float = 0.0, name: str = None, ): """ Initializes a signal given by an envelop and an optional carrier. Args: envelope: Envelope function of the signal. carrier_freq: Frequency of the carrier. phase: The phase of the carrier. name: name of signal. """ super().__init__(name) if isinstance(envelope, (float, int)): envelope = complex(envelope) if isinstance(envelope, complex): self.envelope = lambda t: envelope else: self.envelope = envelope self._carrier_freq = Array(carrier_freq) self._phase = Array(phase)
def __init__( self, dt: float, samples: Union[Array, List], start_time: float = 0.0, duration: int = None, carrier_freq: float = 0.0, phase: float = 0.0, name: str = None, ): """Initialize a piecewise constant signal. Args: dt: The duration of each sample. samples: The array of samples. start_time: The time at which the signal starts. duration: The duration of the signal in samples. carrier_freq: The frequency of the carrier. phase: The phase of the carrier. name: name of the signal. """ super().__init__(name) self._dt = dt if samples is not None: self._samples = Array(samples) else: self._samples = Array([0.0] * duration) self._start_time = start_time self._carrier_freq = Array(carrier_freq) self._phase = Array(phase)
def test_diag_frame_operator_basic_model(self): """Test setting a diagonal frame operator for the internally set up basic model. """ self._basic_frame_evaluate_test(Array([1j, -1j]), 1.123) self._basic_frame_evaluate_test(Array([1j, -1j]), np.pi)
def jax_odeint( rhs: Callable, t_span: Array, y0: Array, t_eval: Optional[Union[Tuple, List, Array]] = None, **kwargs, ): """Routine for calling `jax.experimental.ode.odeint` Args: rhs: Callable of the form :math:`f(t, y)` t_span: Interval to solve over. y0: Initial state. t_eval: Optional list of time points at which to return the solution. kwargs: Optional arguments to be passed to ``odeint``. Returns: OdeResult: Results object. """ t_list = merge_t_args(t_span, t_eval) # determine direction of integration t_direction = np.sign(Array(t_list[-1] - t_list[0], backend="jax")).data results = odeint( lambda y, t: t_direction * rhs(t_direction * t, y), y0=y0, t=t_direction * t_list.data, **kwargs, ) results = OdeResult(t=t_list, y=Array(results, backend="jax")) return trim_t_results(results, t_span, t_eval)
def setUp(self): """Set up a basic parameterized simulation.""" self.w = 5.0 self.r = 0.1 operators = [ 2 * np.pi * self.w * Array(Operator.from_label("Z").data) / 2, 2 * np.pi * self.r * Array(Operator.from_label("X").data) / 2, ] ham = HamiltonianModel(operators=operators) self.ham = ham def param_sim(amp, drive_freq): signals = [ Constant(1.0), Signal(lambda t: amp, carrier_freq=drive_freq) ] ham_copy = ham.copy() ham_copy.signals = signals results = solve_lmde( ham_copy, t_span=[0.0, 1 / self.r], y0=Array([0.0, 1.0], dtype=complex), method="jax_odeint", atol=1e-10, rtol=1e-10, ) return results.y[-1] self.param_sim = param_sim
def _apply(self, signal: BaseSignal) -> BaseSignal: """ Applies a transformation on a signal, such as a convolution, low pass filter, etc. Once a convolution is applied the signal can longer have a carrier as the carrier is part of the signal value and gets convolved. Args: signal: A signal or list of signals to which the transfer function will be applied. Returns: signal: The transformed signal or list of signals. Raises: QiskitError: if the signal is not pwc. """ if isinstance(signal, PiecewiseConstant): # Perform a discrete time convolution. dt = signal.dt func_samples = Array([self._func(dt * i) for i in range(signal.duration)]) func_samples = func_samples / sum(func_samples) sig_samples = Array([signal.value(dt * i) for i in range(signal.duration)]) convoluted_samples = list(np.convolve(func_samples, sig_samples)) return PiecewiseConstant(dt, convoluted_samples, carrier_freq=0.0, phase=0.0) else: raise QiskitError("Transfer function not defined on input.")
def _variable_step_method_standard_tests(self, method): """tests to run on a variable step solver.""" results = solve_ode(self.basic_rhs, t_span=self.t_span, y0=self.y0, method=method, atol=1e-10, rtol=1e-10) expected = expm(-1j * np.pi * self.X.data) self.assertAllClose(results.y[-1], expected) # pylint: disable=unused-argument def quad_rhs(t, y): return Array([t**2], dtype=float) results = solve_ode(quad_rhs, t_span=[0.0, 1.0], y0=Array([0.0]), method=method, atol=1e-10, rtol=1e-10) expected = Array([1.0 / 3]) self.assertAllClose(results.y[-1], expected)
def trim_t_results( results: OdeResult, t_span: Union[List, Tuple, Array], t_eval: Optional[Union[List, Tuple, Array]] = None, ) -> OdeResult: """Trim ``OdeResult`` object based on value of ``t_span`` and ``t_eval``. Args: results: Result object, assumed to contain solution at time points from the output of ``validate_and_merge_t_span_t_eval(t_span, t_eval)``. t_span: Interval to solve over. t_eval: Time points to include in returned results. Returns: OdeResult: Results with only times/solutions in ``t_eval``. If ``t_eval`` is ``None``, does nothing, returning solver default output. """ if t_eval is None: return results t_span = Array(t_span, backend="numpy") # remove endpoints if not included in t_eval if t_eval[0] != t_span[0]: results.t = results.t[1:] results.y = Array(results.y[1:]) if t_eval[-1] != t_span[1]: results.t = results.t[:-1] results.y = Array(results.y[:-1]) return results
def setUp(self): self.t_span = [0.0, 1.0] self.y0 = Array(np.eye(2, dtype=complex)) self.X = Array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) self.Y = Array([[0.0, -1j], [1j, 0.0]], dtype=complex) self.Z = Array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) # simple generator and rhs # pylint: disable=unused-argument def generator(t): return -1j * 2 * np.pi * self.X / 2 def rhs(t, y): return generator(t) @ y self.basic_generator = generator self.basic_rhs = rhs # define simple model self.w = 2.0 self.r = 0.1 signals = [Constant(self.w), Signal(lambda t: 1.0, self.w)] operators = [ -1j * 2 * np.pi * self.Z / 2, -1j * 2 * np.pi * self.r * self.X / 2 ] self.basic_model = GeneratorModel(operators=operators, signals=signals)
def initial_state_converter( obj: Any, return_class: bool = False) -> Union[Array, Tuple[Array, Type]]: """Convert initial state object to an Array. Args: obj: An initial state. return_class: Optional. If True return the class to use for converting the output y Array. Returns: Array: the converted initial state if ``return_class=False``. tuple: (Array, class) if ``return_class=True``. """ # pylint: disable=invalid-name y0_cls = None if isinstance(obj, Array): y0, y0_cls = obj, None if isinstance(obj, QuantumState): y0, y0_cls = Array(obj.data), obj.__class__ elif isinstance(obj, QuantumChannel): y0, y0_cls = Array(SuperOp(obj).data), SuperOp elif isinstance(obj, (BaseOperator, Gate, QuantumCircuit)): y0, y0_cls = Array(Operator(obj.data)), Operator else: y0, y0_cls = Array(obj), None if return_class: return y0, y0_cls return y0
def fixed_step_solver_template( take_step: Callable, rhs_func: Callable, t_span: Array, y0: Array, max_dt: float, t_eval: Optional[Union[Tuple, List, Array]] = None, ): """Helper function for implementing fixed-step solvers supporting both ``t_span`` and ``max_dt`` arguments. ``take_step`` is assumed to be a function implementing a single step of size h of a fixed-step method. The signature of ``take_step`` is assumed to be: - rhs_func: Either a generator :math:`G(t)` or RHS function :math:`f(t,y)`. - t0: The current time. - y0: The current state. - h: The size of the step to take. It returns: - y: The state of the DE at time t0 + h. ``take_step`` is used to integrate the DE specified by ``rhs_func`` through all points in ``t_eval``, taking steps no larger than ``max_dt``. Each interval in ``t_eval`` is divided into the least number of sub-intervals of equal length so that the sub-intervals are smaller than ``max_dt``. Args: take_step: Callable for fixed step integration. rhs_func: Callable, either a generator or rhs function. t_span: Interval to solve over. y0: Initial state. max_dt: Maximum step size. t_eval: Optional list of time points at which to return the solution. Returns: OdeResult: Results object. """ # ensure the output of rhs_func is a raw array def wrapped_rhs_func(*args): return Array(rhs_func(*args)).data y0 = Array(y0).data t_list, h_list, n_steps_list = get_fixed_step_sizes(t_span, t_eval, max_dt) ys = [y0] for current_t, h, n_steps in zip(t_list, h_list, n_steps_list): y = ys[-1] inner_t = current_t for _ in range(n_steps): y = take_step(wrapped_rhs_func, inner_t, y, h) inner_t = inner_t + h ys.append(y) ys = Array(ys) results = OdeResult(t=t_list, y=ys) return trim_t_results(results, t_span, t_eval)
def __init__( self, frame_operator: Union[BaseFrame, Operator, Array], atol: float = 1e-10, rtol: float = 1e-10, ): """Initialize with a frame operator. Args: frame_operator: the frame operator, must be either Hermitian or anti-Hermitian. atol: absolute tolerance when verifying that the frame_operator is Hermitian or anti-Hermitian. rtol: relative tolerance when verifying that the frame_operator is Hermitian or anti-Hermitian. """ if issubclass(type(frame_operator), BaseFrame): frame_operator = frame_operator.frame_operator self._frame_operator = frame_operator frame_operator = to_array(frame_operator) if frame_operator is None: self._dim = None self._frame_diag = None self._frame_basis = None self._frame_basis_adjoint = None # if frame_operator is a 1d array, assume already diagonalized elif frame_operator.ndim == 1: # verify Hermitian or anti-Hermitian # if Hermitian convert to anti-Hermitian frame_operator = _is_herm_or_anti_herm(frame_operator, atol=atol, rtol=rtol) self._frame_diag = Array(frame_operator) self._frame_basis = Array(np.eye(len(frame_operator))) self._frame_basis_adjoint = self.frame_basis self._dim = len(self._frame_diag) # if not, diagonalize it else: # verify Hermitian or anti-Hermitian # if Hermitian convert to anti-Hermitian frame_operator = _is_herm_or_anti_herm(frame_operator, atol=atol, rtol=rtol) # diagonalize with eigh, utilizing assumption of anti-hermiticity frame_diag, frame_basis = np.linalg.eigh(1j * frame_operator) self._frame_diag = Array(-1j * frame_diag) self._frame_basis = Array(frame_basis) self._frame_basis_adjoint = frame_basis.conj().transpose() self._dim = len(self._frame_diag)
def merge_t_args( t_span: Union[List, Tuple, Array], t_eval: Optional[Union[List, Tuple, Array]] = None ) -> Array: """Merge ``t_span`` and ``t_eval`` into a single array without duplicates. Validity of the passed ``t_span`` and ``t_eval`` follow scipy ``solve_ivp`` validation logic: ``t_eval`` must be contained in ``t_span``, and be strictly increasing if ``t_span[1] > t_span[0]`` or strictly decreasing if ``t_span[1] < t_span[0]``. Note: this is done explicitly with ``numpy``, and hence this is not differentiable or compilable using jax. Args: t_span: Interval to solve over. t_eval: Time points to include in returned results. Returns: Array: Combined list of times. Raises: ValueError: If one of several validation checks fail. """ if t_eval is None: return Array(t_span) t_span = Array(t_span, backend="numpy") t_min = np.min(t_span) t_max = np.max(t_span) t_direction = np.sign(t_span[1] - t_span[0]) t_eval = Array(t_eval, backend="numpy") if t_eval.ndim > 1: raise ValueError("t_eval must be 1 dimensional.") if np.min(t_eval) < t_min or np.max(t_eval) > t_max: raise ValueError("t_eval entries must lie in t_span.") diff = np.diff(t_eval) if np.any(t_direction * diff <= 0.0): raise ValueError("t_eval must be ordered according to the direction of integration.") # if endpoints are not included in t_span, add them if t_eval[0] != t_span[0]: t_eval = np.append(t_span[0], t_eval) if t_span[1] != t_eval[-1]: t_eval = np.append(t_eval, t_span[1]) return Array(t_eval, backend="numpy")
def test_basic_lindblad_lmult(self): """Test lmult method of Lindblad generator OperatorModel.""" A = Array([[1.0, 2.0], [3.0, 4.0]]) t = 1.123 ham = (2 * np.pi * self.w * self.Z.data / 2 + 2 * np.pi * self.r * np.cos(2 * np.pi * self.w * t) * self.X.data / 2) sm = Array([[0.0, 0.0], [1.0, 0.0]]) expected = self._evaluate_lindblad_rhs(A, ham, [sm]) value = self.basic_lindblad.lmult(t, A.flatten(order="F")) self.assertAllClose(expected, value.reshape(2, 2, order="F"))
def setUp(self): self.t_span = [0.0, 1.0] self.y0 = Array(np.eye(2, dtype=complex)) self.X = Array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) self.Y = Array([[0.0, -1j], [1j, 0.0]], dtype=complex) self.Z = Array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) # simple generator and rhs # pylint: disable=unused-argument def generator(t): return -1j * 2 * np.pi * self.X / 2 self.basic_generator = generator