Example #1
0
    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)
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
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
Example #7
0
    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.")
Example #8
0
    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)
Example #9
0
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
Example #10
0
    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)
Example #11
0
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
Example #12
0
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)
Example #13
0
    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)
Example #14
0
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")
Example #15
0
    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"))
Example #16
0
    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