Exemplo n.º 1
0
    def test_set_meas_levels(self):
        """Test setting of meas_levels."""

        athens_backend = FakeAthens()
        athens_sim = PulseSimulator.from_backend(athens_backend)

        # test that a warning is thrown when meas_level 0 is attempted to be set
        with warnings.catch_warnings(record=True) as w:
            # Cause all warnings to always be triggered.
            warnings.simplefilter("always")

            athens_sim.set_options(meas_levels=[0, 1, 2])

            self.assertEqual(len(w), 1)
            self.assertTrue(
                'Measurement level 0 not supported' in str(w[-1].message))
            self.assertEqual(athens_sim.configuration().meas_levels, [1, 2])

        self.assertTrue(athens_sim.configuration().meas_levels == [1, 2])

        athens_sim.set_options(meas_levels=[2])
        self.assertTrue(athens_sim.configuration().meas_levels == [2])
Exemplo n.º 2
0
    def test_from_backend(self):
        """Test that configuration, defaults, and properties are correclty imported."""

        athens_backend = FakeAthens()
        athens_sim = PulseSimulator.from_backend(athens_backend)

        self.assertEqual(athens_backend.properties(), athens_sim.properties())
        # check that configuration is correctly imported
        backend_dict = athens_backend.configuration().to_dict()
        sim_dict = athens_sim.configuration().to_dict()
        for key in sim_dict:
            if key == 'backend_name':
                self.assertEqual(sim_dict[key], 'pulse_simulator(fake_athens)')
            elif key == 'description':
                desc = 'A Pulse-based simulator configured from the backend: fake_athens'
                self.assertEqual(sim_dict[key], desc)
            elif key == 'simulator':
                self.assertTrue(sim_dict[key])
            elif key == 'local':
                self.assertTrue(sim_dict[key])
            elif key == 'parametric_pulses':
                self.assertEqual(sim_dict[key], [])
            else:
                self.assertEqual(sim_dict[key], backend_dict[key])

        backend_dict = athens_backend.defaults().to_dict()
        sim_dict = athens_sim.defaults().to_dict()
        for key in sim_dict:
            if key == 'pulse_library':
                # need to compare pulse libraries directly due to containing dictionaries
                for idx, entry in enumerate(sim_dict[key]):
                    for entry_key in entry:
                        if entry_key == 'samples':
                            self.assertTrue(np.array_equal(entry[entry_key], backend_dict[key][idx][entry_key]))
                        else:
                            self.assertTrue(entry[entry_key] == backend_dict[key][idx][entry_key])
            else:
                self.assertEqual(sim_dict[key], backend_dict[key])
Exemplo n.º 3
0
    def test_validation_num_acquires(self):
        """Test that validation fails if 0 or >1 acquire is given in a schedule."""

        test_sim = PulseSimulator.from_backend(FakeArmonk())
        test_sim.set_options(meas_level=2,
                             qubit_lo_freq=test_sim.defaults().qubit_freq_est,
                             meas_return='single',
                             shots=256)

        # check that too many acquires results in an error
        sched = self._1Q_schedule(num_acquires=2)
        try:
            test_sim.run(sched, validate=True).result()
        except AerError as error:
            self.assertTrue(
                'does not support multiple Acquire' in error.message)

        # check that no acquires results in an error
        sched = self._1Q_schedule(num_acquires=0)
        try:
            test_sim.run(sched, validate=True).result()
        except AerError as error:
            self.assertTrue('requires at least one Acquire' in error.message)
    def test_run_simulation_from_backend(self):
        """Construct from a backend and run a simulation."""
        armonk_backend = FakeArmonk()

        # manually override parameters to insulate from future changes to FakeArmonk
        freq_est = 4.97e9
        drive_est = 6.35e7
        armonk_backend.defaults().qubit_freq_est = [freq_est]
        armonk_backend.configuration().hamiltonian['h_str'] = [
            'wq0*0.5*(I0-Z0)', 'omegad0*X0||D0'
        ]
        armonk_backend.configuration().hamiltonian['vars'] = {
            'wq0': 2 * np.pi * freq_est,
            'omegad0': drive_est
        }
        armonk_backend.configuration().hamiltonian['qub'] = {'0': 2}
        dt = 2.2222222222222221e-10
        armonk_backend.configuration().dt = dt

        armonk_sim = PulseSimulator.from_backend(armonk_backend)

        total_samples = 250
        amp = np.pi / (drive_est * dt * total_samples)

        sched = self._1Q_schedule(total_samples, amp)
        qobj = assemble([sched],
                        backend=armonk_sim,
                        meas_level=2,
                        meas_return='single',
                        shots=1)
        # run and verify that a pi pulse had been done
        result = armonk_sim.run(qobj).result()
        final_vec = result.get_statevector()
        probabilities = np.abs(final_vec)**2
        self.assertTrue(probabilities[0] < 1e-5)
        self.assertTrue(probabilities[1] > 1 - 1e-5)
class TestPulseSimulator(common.QiskitAerTestCase):
    r"""PulseSimulator tests.

    Mathematical expressions are formulated in latex in docstrings for this class.

    # pylint: disable=anomalous backslash in string
    Uses single qubit Hamiltonian `H = -\frac{1}{2} \omega_0 \sigma_z + \frac{1}{2} \omega_a
    e^{i(\omega_{d0} t+\phi)} \sigma_x`. We make sure H is Hermitian by taking the complex conjugate
    of the lower triangular piece (as done by the simulator). To find the closed form, we move
    to a rotating frame via the unitary `Urot = e^{-i \omega t \sigma_z/2}
    (\ket{psi_{rot}}=Urot \ket{psi_{rot}})`. In this frame, the Hamiltonian becomes
    `Hrot = \frac{1}{2} \omega_a (\cos(\phi) \sigma_x - \sin(\phi) \sigma_y)
    + \frac{\omega_{d0}-\omega_0}{2} \sigma_z`.
    """
    def setUp(self):
        """ Set configuration settings for pulse simulator
        WARNING: We do not support Python 3.5 because the digest algorithm relies on dictionary insertion order.
        This "feature" was introduced later on Python 3.6 and there's no official support for OrderedDict in the C API so
        Python 3.5 support has been disabled while looking for a propper fix.
        """
        if sys.version_info.major == 3 and sys.version_info.minor == 5:
            self.skipTest("We don't support Python 3.5 for Pulse simulator")

        # Get pulse simulator backend
        self.backend_sim = PulseSimulator()

    # ---------------------------------------------------------------------
    # Test single qubit gates (using meas level 2 and square drive)
    # ---------------------------------------------------------------------

    def test_x_gate(self):
        """
        Test x gate. Set omega_d0=omega_0 (drive on resonance), phi=0, omega_a = pi/time
        """

        # setup system model
        total_samples = 100
        omega_0 = 2 * np.pi
        omega_d0 = omega_0
        omega_a = np.pi / total_samples
        system_model = self._system_model_1Q(omega_0, omega_a)

        # set up schedule and qobj
        schedule = self._simple_1Q_schedule(0, total_samples)
        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d0 / (2 * np.pi)],
                        memory_slots=2,
                        shots=256)

        # set backend backend_options
        backend_options = {'seed': 9000}

        # run simulation
        result = self.backend_sim.run(
            qobj, system_model=system_model,
            backend_options=backend_options).result()

        # test results
        counts = result.get_counts()
        exp_counts = {'1': 256}
        self.assertDictAlmostEqual(counts, exp_counts)

    def test_1Q_noise(self):
        """
        Tests simulation of noise operators. Uses the same schedule as test_x_gate, but
        with a high level of amplitude damping noise.
        """

        # setup system model
        total_samples = 100
        omega_0 = 2 * np.pi
        omega_d0 = omega_0
        omega_a = np.pi / total_samples
        system_model = self._system_model_1Q(omega_0, omega_a)

        # set up schedule and qobj
        schedule = self._simple_1Q_schedule(0, total_samples)
        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d0 / (2 * np.pi)],
                        memory_slots=2,
                        shots=256)

        # set seed for simulation, and set noise
        backend_options = {'seed': 9000}
        backend_options['noise_model'] = {"qubit": {"0": {"Sm": 1}}}

        # run simulation
        result = self.backend_sim.run(
            qobj, system_model=system_model,
            backend_options=backend_options).result()

        # test results
        # This level of noise is high enough that all counts should yield 0,
        # whereas in the noiseless simulation (in test_x_gate) all counts yield 1
        counts = result.get_counts()
        exp_counts = {'0': 256}
        self.assertDictAlmostEqual(counts, exp_counts)

    def test_dt_scaling_x_gate(self):
        """
        Test that dt is being used correctly by the solver.
        """

        total_samples = 100

        # do the same thing as test_x_gate, but scale dt and all frequency parameters
        # define test case for a single scaling
        def scale_test(scale):
            # set omega_0, omega_d0 equal (use qubit frequency) -> drive on resonance
            # Require omega_a*time = pi to implement pi pulse (x gate)
            omega_0 = 2 * np.pi / scale
            omega_d0 = omega_0
            omega_a = np.pi / total_samples / scale

            # set up system model
            system_model = self._system_model_1Q(omega_0, omega_a)
            system_model.dt = system_model.dt * scale

            # set up schedule and qobj
            schedule = self._simple_1Q_schedule(0, total_samples)
            qobj = assemble([schedule],
                            backend=self.backend_sim,
                            meas_level=2,
                            meas_return='single',
                            meas_map=[[0]],
                            qubit_lo_freq=[omega_d0 / (2 * np.pi)],
                            memory_slots=2,
                            shots=256)

            # set backend backend_options
            backend_options = {'seed': 9000}

            # run simulation
            result = self.backend_sim.run(
                qobj,
                system_model=system_model,
                backend_options=backend_options).result()
            counts = result.get_counts()
            exp_counts = {'1': 256}

            self.assertDictAlmostEqual(counts, exp_counts)

        # set scales and run tests
        scales = [2., 0.1234, 10.**5, 10**-5]
        for scale in scales:
            scale_test(scale)

    def test_hadamard_gate(self):
        """Test Hadamard. Is a rotation of pi/2 about the y-axis. Set omega_d0=omega_0
        (drive on resonance), phi=-pi/2, omega_a = pi/2/time
        """

        # set variables
        shots = 100000  # large number of shots so get good proportions
        total_samples = 100

        # set omega_0, omega_d0 equal (use qubit frequency) -> drive on resonance
        omega_0 = 2 * np.pi
        omega_d0 = omega_0

        # Require omega_a*time = pi/2 to implement pi/2 rotation pulse
        # num of samples gives time
        omega_a = np.pi / 2 / total_samples

        system_model = self._system_model_1Q(omega_0, omega_a)

        phi = -np.pi / 2
        schedule = self._simple_1Q_schedule(phi, total_samples)

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d0 / (2 * np.pi)],
                        memory_slots=2,
                        shots=shots)

        # set backend backend_options
        backend_options = {'seed': 9000}

        # run simulation
        result = self.backend_sim.run(
            qobj, system_model=system_model,
            backend_options=backend_options).result()
        counts = result.get_counts()

        # compare prop
        prop = {}
        for key in counts.keys():
            prop[key] = counts[key] / shots

        exp_prop = {'0': 0.5, '1': 0.5}

        self.assertDictAlmostEqual(prop, exp_prop, delta=0.01)

    def test_arbitrary_gate(self):
        """Test a few examples w/ arbitary drive, phase and amplitude. """
        shots = 10000  # large number of shots so get good proportions
        total_samples = 100
        num_tests = 3
        # set variables for each test
        omega_0 = 2 * np.pi
        omega_d0_vals = [omega_0 + 1, omega_0 + 0.02, omega_0 + 0.005]
        omega_a_vals = [
            2 * np.pi / 3 / total_samples, 7 * np.pi / 5 / total_samples, 0.1
        ]
        phi_vals = [5 * np.pi / 7, 19 * np.pi / 14, np.pi / 4]

        for i in range(num_tests):
            with self.subTest(i=i):

                system_model = self._system_model_1Q(omega_0, omega_a_vals[i])
                schedule = self._simple_1Q_schedule(phi_vals[i], total_samples)

                qobj = assemble([schedule],
                                backend=self.backend_sim,
                                meas_level=2,
                                meas_return='single',
                                meas_map=[[0]],
                                qubit_lo_freq=[omega_d0_vals[i] / (2 * np.pi)],
                                memory_slots=2,
                                shots=shots)

                # Run qobj and compare prop to expected result
                backend_options = {'seed': 9000}
                result = self.backend_sim.run(qobj, system_model,
                                              backend_options).result()
                counts = result.get_counts()

                prop = {}
                for key in counts.keys():
                    prop[key] = counts[key] / shots

                exp_prop = self._analytic_prop_1q_gates(
                    total_samples=total_samples,
                    omega_0=omega_0,
                    omega_a=omega_a_vals[i],
                    omega_d0=omega_d0_vals[i],
                    phi=phi_vals[i])

                self.assertDictAlmostEqual(prop, exp_prop, delta=0.01)

    def test_meas_level_1(self):
        """Test measurement level 1. """

        shots = 10000  # run large number of shots for good proportions
        total_samples = 100
        # perform hadamard setup (so get some 0's and some 1's), but use meas_level = 1

        # set omega_0, omega_d0 equal (use qubit frequency) -> drive on resonance
        omega_0 = 2 * np.pi
        omega_d0 = omega_0

        # Require omega_a*time = pi/2 to implement pi/2 rotation pulse
        # num of samples gives time
        omega_a = np.pi / 2 / total_samples

        phi = -np.pi / 2

        system_model = self._system_model_1Q(omega_0, omega_a)

        phi = -np.pi / 2
        schedule = self._simple_1Q_schedule(phi, total_samples)

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=1,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d0 / (2 * np.pi)],
                        memory_slots=2,
                        shots=shots)

        # set backend backend_options
        backend_options = {'seed': 9000}

        result = self.backend_sim.run(qobj, system_model,
                                      backend_options).result()

        # Verify that (about) half the IQ vals have abs val 1 and half have abs val 0
        # (use prop for easier comparison)
        mem = np.abs(result.get_memory()[:, 0])

        iq_prop = {'0': 0, '1': 0}
        for i in mem:
            if i == 0:
                iq_prop['0'] += 1 / shots
            else:
                iq_prop['1'] += 1 / shots

        exp_prop = {'0': 0.5, '1': 0.5}

        self.assertDictAlmostEqual(iq_prop, exp_prop, delta=0.01)

    def test_gaussian_drive(self):
        """Test gaussian drive pulse using meas_level_2. Set omega_d0=omega_0 (drive on resonance),
        phi=0, omega_a = pi/time
        """

        # set variables

        # set omega_0, omega_d0 equal (use qubit frequency) -> drive on resonance
        total_samples = 100
        omega_0 = 2 * np.pi
        omega_d0 = omega_0

        # Require omega_a*time = pi to implement pi pulse (x gate)
        # num of samples gives time
        omega_a = np.pi / total_samples

        phi = 0

        # Test gaussian drive results for a few different sigma
        gauss_sigmas = {total_samples / 6, total_samples / 3, total_samples}

        system_model = self._system_model_1Q(omega_0, omega_a)

        for gauss_sigma in gauss_sigmas:
            with self.subTest(gauss_sigma=gauss_sigma):
                schedule = self._simple_1Q_schedule(phi, total_samples,
                                                    "gaussian", gauss_sigma)

                qobj = assemble([schedule],
                                backend=self.backend_sim,
                                meas_level=2,
                                meas_return='single',
                                meas_map=[[0]],
                                qubit_lo_freq=[omega_d0 / (2 * np.pi)],
                                memory_slots=2,
                                shots=1000)
                backend_options = {'seed': 9000}

                result = self.backend_sim.run(qobj, system_model,
                                              backend_options).result()
                statevector = result.get_statevector()
                exp_statevector = self._analytic_gaussian_statevector(
                    total_samples, gauss_sigma=gauss_sigma, omega_a=omega_a)

                # Check fidelity of statevectors
                self.assertGreaterEqual(
                    state_fidelity(statevector, exp_statevector), 0.99)

    def test_frame_change(self):
        """Test frame change command. """
        shots = 10000
        total_samples = 100
        # set omega_0, omega_d0 equal (use qubit frequency) -> drive on resonance
        omega_0 = 2 * np.pi
        omega_d0 = omega_0

        # set phi = 0
        phi = 0

        dur_drive1 = total_samples  # first pulse duration
        fc_phi = np.pi

        # Test frame change where no shift in state results
        # specfically: do pi/2 pulse, then pi frame change, then another pi/2 pulse.
        # Verify left in |0> state
        dur_drive2 = dur_drive1  # same duration for both pulses
        omega_a = np.pi / 2 / dur_drive1  # pi/2 pulse amplitude

        system_model = self._system_model_1Q(omega_0, omega_a)
        schedule = self._1Q_frame_change_schedule(phi, fc_phi, total_samples,
                                                  dur_drive1, dur_drive2)
        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d0 / (2 * np.pi)],
                        memory_slots=2,
                        shots=shots)

        backend_options = {'seed': 9000}
        result = self.backend_sim.run(qobj, system_model,
                                      backend_options).result()
        counts = result.get_counts()
        exp_counts = {'0': shots}

        self.assertDictAlmostEqual(counts, exp_counts)

        # Test frame change where a shift does result
        # specifically: do pi/4 pulse, then pi phase change, then do pi/8 pulse.
        # check that a net rotation of pi/4-pi/8 has occured on the Bloch sphere
        dur_drive2 = int(dur_drive1 /
                         2)  # half time for second pulse (halves angle)
        omega_a = np.pi / 4 / dur_drive1  # pi/4 pulse amplitude

        system_model = self._system_model_1Q(omega_0, omega_a)
        schedule = self._1Q_frame_change_schedule(phi, fc_phi, total_samples,
                                                  dur_drive1, dur_drive2)
        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d0 / (2 * np.pi)],
                        memory_slots=2,
                        shots=shots)

        backend_options = {'seed': 9000}
        result = self.backend_sim.run(qobj, system_model,
                                      backend_options).result()
        counts = result.get_counts()

        # verify props
        prop_shift = {}
        for key in counts.keys():
            prop_shift[key] = counts[key] / shots

        # net angle is given by pi/4-pi/8
        prop0 = np.cos((np.pi / 4 - np.pi / 8) / 2)**2
        exp_prop = {'0': prop0, '1': 1 - prop0}
        self.assertDictAlmostEqual(prop_shift, exp_prop, delta=0.01)

    def test_three_level(self):
        r"""Test 3 level system. Compare statevectors as counts only use bitstrings. Analytic form
        given in _analytic_statevector_3level function docstring.
        """
        def analytic_state_vector(omega_a, total_samples):
            r"""Returns analytically computed statevector for 3 level system with our Hamiltonian.
            Is given by `(\frac{1}{3} (2+\cos(\frac{\sqrt{3}}{2} \omega_a t)),
            -\frac{i}{\sqrt{3}} \sin(\frac{\sqrt{3}}{2} \omega_a t),
            -\frac{2\sqrt{2}}{3} \sin(\frac{\sqrt{3}}{4} \omega_a t)^2)`.
            Args:
                omega_a (float): Q0 drive amplitude
                total_samples (int): number of samples to use in pulses_idx
            Returns:
                exp_statevector (list): analytically computed statevector with Hamiltonian from
                    above (Returned in the rotating frame)
            """
            time = total_samples
            arg1 = np.sqrt(
                3) * omega_a * time / 2  # cos arg for first component
            arg2 = arg1  # sin arg for first component
            arg3 = arg1 / 2  # sin arg for 3rd component
            exp_statevector = np.array(
                [(2 + np.cos(arg1)) / 3, -1j * np.sin(arg2) / np.sqrt(3),
                 -2 * np.sqrt(2) * np.sin(arg3)**2 / 3],
                dtype=complex)
            return exp_statevector

        shots = 1000
        total_samples = 100
        # Set omega_0,omega_d0 (use qubit frequency) -> drive on resonance
        omega_0 = 2 * np.pi
        omega_d0 = omega_0

        # Set phi = 0 for simplicity
        phi = 0

        # Test pi pulse
        omega_a = np.pi / total_samples

        system_model = self._system_model_1Q(omega_0, omega_a, qubit_dim=3)
        schedule = self._simple_1Q_schedule(phi, total_samples)

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d0 / (2 * np.pi)],
                        memory_slots=2,
                        shots=shots)
        backend_options = {'seed': 9000}

        result = self.backend_sim.run(qobj, system_model,
                                      backend_options).result()
        statevector = result.get_statevector()

        exp_statevector = analytic_state_vector(omega_a, total_samples)

        # Check fidelity of statevectors
        self.assertGreaterEqual(state_fidelity(statevector, exp_statevector),
                                0.99)

        # Test 2*pi pulse
        omega_a = 2 * np.pi / total_samples

        system_model = self._system_model_1Q(omega_0, omega_a, qubit_dim=3)
        schedule = self._simple_1Q_schedule(phi, total_samples)

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d0 / (2 * np.pi)],
                        memory_slots=2,
                        shots=shots)
        backend_options = {'seed': 9000}

        result = self.backend_sim.run(qobj, system_model,
                                      backend_options).result()
        statevector = result.get_statevector()

        exp_statevector = analytic_state_vector(omega_a, total_samples)

        # Check fidelity of vectors
        self.assertGreaterEqual(state_fidelity(statevector, exp_statevector),
                                0.99)

    def test_interaction(self):
        r"""Test 2 qubit interaction via swap gates."""

        shots = 100000
        total_samples = 100
        # Do a standard SWAP gate

        # Interaction amp (any non-zero creates the swap gate)
        omega_i_swap = np.pi / 2 / total_samples
        # set omega_d0=omega_0 (resonance)
        omega_0 = 2 * np.pi
        omega_d0 = omega_0

        # For swapping, set omega_d1 = 0 (drive on Q0 resonance)
        # Note: confused by this as there is no d1 term
        omega_d1 = 0

        # do pi pulse on Q0 and verify state swaps from '01' to '10' (reverse bit order)

        # Q0 drive amp -> pi pulse
        omega_a_pi_swap = np.pi / total_samples

        system_model = self._system_model_2Q(omega_0, omega_a_pi_swap,
                                             omega_i_swap)

        schedule = self._schedule_2Q_interaction(total_samples)
        qobj = assemble(
            [schedule],
            backend=self.backend_sim,
            meas_level=2,
            meas_return='single',
            meas_map=[[0, 1]],
            qubit_lo_freq=[omega_d0 / (2 * np.pi), omega_d1 / (2 * np.pi)],
            memory_slots=2,
            shots=shots)
        backend_options = {'seed': 12387}

        result_pi_swap = self.backend_sim.run(qobj, system_model,
                                              backend_options).result()
        counts_pi_swap = result_pi_swap.get_counts()

        exp_counts_pi_swap = {
            '10': shots
        }  # reverse bit order (qiskit convention)
        self.assertDictAlmostEqual(counts_pi_swap, exp_counts_pi_swap, delta=2)

        # do pi/2 pulse on Q0 and verify half the counts are '00' and half are swapped state '10'

        # Q0 drive amp -> pi/2 pulse
        omega_a_pi2_swap = np.pi / 2 / total_samples

        system_model = self._system_model_2Q(omega_0, omega_a_pi2_swap,
                                             omega_i_swap)

        result_pi2_swap = self.backend_sim.run(qobj, system_model,
                                               backend_options).result()
        counts_pi2_swap = result_pi2_swap.get_counts()

        # compare proportions for improved accuracy
        prop_pi2_swap = {}
        for key in counts_pi2_swap.keys():
            prop_pi2_swap[key] = counts_pi2_swap[key] / shots

        exp_prop_pi2_swap = {'00': 0.5, '10': 0.5}  # reverse bit order

        self.assertDictAlmostEqual(prop_pi2_swap,
                                   exp_prop_pi2_swap,
                                   delta=0.01)

        # Test that no SWAP occurs when omega_i=0 (no interaction)
        omega_i_no_swap = 0

        # Q0 drive amp -> pi pulse
        omega_a_no_swap = np.pi / total_samples
        system_model = self._system_model_2Q(omega_0, omega_a_no_swap,
                                             omega_i_no_swap)

        result_no_swap = self.backend_sim.run(qobj, system_model,
                                              backend_options).result()
        counts_no_swap = result_no_swap.get_counts()

        exp_counts_no_swap = {
            '01': shots
        }  # non-swapped state (reverse bit order)
        self.assertDictAlmostEqual(counts_no_swap, exp_counts_no_swap)

    def _system_model_1Q(self, omega_0, omega_a, qubit_dim=2):
        """Constructs a simple 1 qubit system model.

        Args:
            omega_0 (float): frequency of qubit
            omega_a (float): strength of drive term
            qubit_dim (int): dimension of qubit
        Returns:
            PulseSystemModel: model for qubit system
        """
        # make Hamiltonian
        hamiltonian = {}
        hamiltonian['h_str'] = ['-0.5*omega0*Z0', '0.5*omegaa*X0||D0']
        hamiltonian['vars'] = {'omega0': omega_0, 'omegaa': omega_a}
        hamiltonian['qub'] = {'0': qubit_dim}
        ham_model = HamiltonianModel.from_dict(hamiltonian)

        u_channel_lo = []
        subsystem_list = [0]
        dt = 1.

        return PulseSystemModel(hamiltonian=ham_model,
                                u_channel_lo=u_channel_lo,
                                subsystem_list=subsystem_list,
                                dt=dt)

    def _system_model_2Q(self, omega_0, omega_a, omega_i, qubit_dim=2):
        """Constructs a simple 1 qubit system model.

        Args:
            omega_0 (float): frequency of qubit
            omega_a (float): strength of drive term
            omega_i (float): strength of interaction
            qubit_dim (int): dimension of qubit
        Returns:
            PulseSystemModel: model for qubit system
        """

        # make Hamiltonian
        hamiltonian = {}
        # qubit 0 terms
        hamiltonian['h_str'] = ['-0.5*omega0*Z0', '0.5*omegaa*X0||D0']
        # interaction term
        hamiltonian['h_str'].append('omegai*(Sp0*Sm1+Sm0*Sp1)||U1')
        hamiltonian['vars'] = {
            'omega0': omega_0,
            'omegaa': omega_a,
            'omegai': omega_i
        }
        hamiltonian['qub'] = {'0': qubit_dim, '1': qubit_dim}
        ham_model = HamiltonianModel.from_dict(hamiltonian)

        u_channel_lo = [[{
            'q': 0,
            'scale': [1.0, 0.0]
        }], [{
            'q': 0,
            'scale': [-1.0, 0.0]
        }, {
            'q': 1,
            'scale': [1.0, 0.0]
        }]]
        subsystem_list = [0, 1]
        dt = 1.

        return PulseSystemModel(hamiltonian=ham_model,
                                u_channel_lo=u_channel_lo,
                                subsystem_list=subsystem_list,
                                dt=dt)

    def _simple_1Q_schedule(self,
                            phi,
                            total_samples,
                            shape="square",
                            gauss_sigma=0):
        """Creates schedule for single pulse test
        Args:
            phi (float): drive phase (phi in Hamiltonian)
            total_samples (int): length of pulses
            shape (str): shape of the pulse; defaults to square pulse
            gauss_sigma (float): std dev for gaussian pulse if shape=="gaussian"
        Returns:
            schedule (pulse schedule): schedule for this test
        """

        # set up pulse command
        phase = np.exp(1j * phi)
        drive_pulse = None
        if shape == "square":
            const_pulse = np.ones(total_samples)
            drive_pulse = SamplePulse(phase * const_pulse, name='drive_pulse')
        if shape == "gaussian":
            times = 1.0 * np.arange(total_samples)
            gaussian = np.exp(-times**2 / 2 / gauss_sigma**2)
            drive_pulse = SamplePulse(phase * gaussian, name='drive_pulse')

        # add commands into a schedule for first qubit
        schedule = Schedule(name='drive_pulse')
        schedule |= Play(drive_pulse, DriveChannel(0))
        schedule |= Acquire(total_samples, AcquireChannel(0),
                            MemorySlot(0)) << schedule.duration

        return schedule

    def _1Q_frame_change_schedule(self, phi, fc_phi, total_samples, dur_drive1,
                                  dur_drive2):
        """Creates schedule for frame change test. Does a pulse w/ phase phi of duration dur_drive1,
        then frame change of phase fc_phi, then another pulse of phase phi of duration dur_drive2.
        The different durations for the pulses allow manipulation of rotation angles on Bloch sphere

        Args:
            phi (float): drive phase (phi in Hamiltonian)
            fc_phi (float): phase for frame change
            total_samples (int): length of pulses
            dur_drive1 (int): duration of first pulse
            dur_drive2 (int): duration of second pulse

        Returns:
            schedule (pulse schedule): schedule for frame change test
        """
        phase = np.exp(1j * phi)
        drive_pulse_1 = SamplePulse(phase * np.ones(dur_drive1),
                                    name='drive_pulse_1')
        drive_pulse_2 = SamplePulse(phase * np.ones(dur_drive2),
                                    name='drive_pulse_2')

        # add commands to schedule
        schedule = Schedule(name='fc_schedule')
        schedule |= Play(drive_pulse_1, DriveChannel(0))
        schedule += ShiftPhase(fc_phi, DriveChannel(0))
        schedule += Play(drive_pulse_2, DriveChannel(0))
        schedule |= Acquire(total_samples, AcquireChannel(0),
                            MemorySlot(0)) << schedule.duration

        return schedule

    def _analytic_prop_1q_gates(self, total_samples, omega_0, omega_a,
                                omega_d0, phi):
        """Compute proportion for 0 and 1 states analytically for single qubit gates.
        Args:
            total_samples (int): length of pulses
            omega_0 (float): Q0 freq
            omega_a (float): Q0 drive amplitude
            omega_d0 (flaot): Q0 drive frequency
            phi (float): drive phase
        Returns:
            exp_prop (dict): expected value of 0 and 1 proportions from analytic computation
            """
        time = total_samples
        # write Hrot analytically
        h_rot = np.array([[
            (omega_d0 - omega_0) / 2,
            np.exp(1j * phi) * omega_a / 2
        ], [np.exp(-1j * phi) * omega_a / 2, -(omega_d0 - omega_0) / 2]])
        # exponentiate
        u_rot = expm(-1j * h_rot * time)
        state0 = np.array([1, 0])

        # compute analytic prob (proportion) of 0 state
        mat_elem0 = np.vdot(state0, np.dot(u_rot, state0))
        prop0 = np.abs(mat_elem0)**2

        # return expected proportion
        exp_prop = {'0': prop0, '1': 1 - prop0}
        return exp_prop

    def _analytic_gaussian_statevector(self, total_samples, gauss_sigma,
                                       omega_a):
        r"""Computes analytic statevector for gaussian drive. Solving the Schrodinger equation in
        the rotating frame leads to the analytic solution `(\cos(x), -i\sin(x)) with
        `x = \frac{1}{2}\sqrt{\frac{\pi}{2}}\sigma\omega_a erf(\frac{t}{\sqrt{2}\sigma}).

        Args:
            total_samples (int): length of pulses
            gauss_sigma (float): std dev for the gaussian drive
            omega_a (float): Q0 drive amplitude
        Returns:
            exp_statevector (list): analytic form of the statevector computed for gaussian drive
                (Returned in the rotating frame)
        """
        time = total_samples
        arg = 1 / 2 * np.sqrt(np.pi / 2) * gauss_sigma * omega_a * erf(
            time / np.sqrt(2) / gauss_sigma)
        exp_statevector = [np.cos(arg), -1j * np.sin(arg)]
        return exp_statevector

    def _schedule_2Q_interaction(self, total_samples):
        """Creates schedule for testing two qubit interaction. Specifically, do a pi pulse on qub 0
        so it starts in the `1` state (drive channel) and then apply constant pulses to each
        qubit (on control channel 1). This will allow us to test a swap gate.

        Args:
            total_samples (int): length of pulses
        Returns:
            schedule (pulse schedule): schedule for 2q experiment
        """

        # create acquire schedule
        acq_sched = Schedule(name='acq_sched')
        acq_sched |= Acquire(total_samples, AcquireChannel(0), MemorySlot(0))
        acq_sched += Acquire(total_samples, AcquireChannel(1), MemorySlot(1))

        # set up const pulse
        const_pulse = SamplePulse(np.ones(total_samples), name='const_pulse')

        # add commands to schedule
        schedule = Schedule(name='2q_schedule')
        schedule |= Play(const_pulse, DriveChannel(0))
        schedule += Play(const_pulse, ControlChannel(1)) << schedule.duration
        schedule |= acq_sched << schedule.duration

        return schedule
    def test_shift_phase(self):
        """Test ShiftPhase command."""

        omega_0 = 1.123
        r = 1.

        system_model = self._system_model_1Q(omega_0, r)

        # run a schedule in which a shifted phase causes a pulse to cancel itself.
        # Also do it in multiple phase shifts to test accumulation
        sched = Schedule()
        amp1 = 0.12
        sched += Play(Waveform([amp1]), DriveChannel(0))
        phi1 = 0.12374 * np.pi
        sched += ShiftPhase(phi1, DriveChannel(0))
        amp2 = 0.492
        sched += Play(Waveform([amp2]), DriveChannel(0))
        phi2 = 0.5839 * np.pi
        sched += ShiftPhase(phi2, DriveChannel(0))
        amp3 = 0.12 + 0.21 * 1j
        sched += Play(Waveform([amp3]), DriveChannel(0))

        sched |= Acquire(1, AcquireChannel(0), MemorySlot(0)) << sched.duration

        y0 = np.array([1., 0])

        pulse_sim = PulseSimulator(system_model=system_model,
                                   initial_state=y0)
        qobj = assemble([sched],
                        backend=pulse_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_0],
                        memory_slots=2,
                        shots=1)
        results = pulse_sim.run(qobj).result()
        pulse_sim_yf = results.get_statevector()

        #run independent simulation
        samples = np.array([[amp1], [amp2 * np.exp(1j * phi1)],
                            [amp3 * np.exp(1j * (phi1 + phi2))]])
        indep_yf = simulate_1q_model(y0, omega_0, r, np.array([omega_0]),
                                     samples, 1.)

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf),
                                1 - (10**-5))

        # run another schedule with only a single shift phase to verify
        sched = Schedule()
        amp1 = 0.12
        sched += Play(Waveform([amp1]), DriveChannel(0))
        phi1 = 0.12374 * np.pi
        sched += ShiftPhase(phi1, DriveChannel(0))
        amp2 = 0.492
        sched += Play(Waveform([amp2]), DriveChannel(0))
        sched |= Acquire(1, AcquireChannel(0), MemorySlot(0)) << sched.duration

        qobj = assemble([sched],
                        backend=pulse_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_0],
                        memory_slots=2,
                        shots=1)

        results = pulse_sim.run(qobj).result()
        pulse_sim_yf = results.get_statevector()

        #run independent simulation
        samples = np.array([[amp1], [amp2 * np.exp(1j * phi1)]])
        indep_yf = simulate_1q_model(y0, omega_0, r, np.array([omega_0]),
                                     samples, 1.)

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf),
                                1 - (10**-5))
    def test_delay_instruction(self):
        """Test for delay instruction."""

        # construct system model specifically for this
        hamiltonian = {}
        hamiltonian['h_str'] = ['0.5*r*X0||D0', '0.5*r*Y0||D1']
        hamiltonian['vars'] = {'r': np.pi}
        hamiltonian['qub'] = {'0': 2}
        ham_model = HamiltonianModel.from_dict(hamiltonian)

        u_channel_lo = []
        subsystem_list = [0]
        dt = 1.

        system_model = PulseSystemModel(hamiltonian=ham_model,
                                        u_channel_lo=u_channel_lo,
                                        subsystem_list=subsystem_list,
                                        dt=dt)

        # construct a schedule that should result in a unitary -Z if delays are correctly handled
        # i.e. do a pi rotation about x, sandwiched by pi/2 rotations about y in opposite directions
        # so that the x rotation is transformed into a z rotation.
        # if delays are not handled correctly this process should fail
        sched = Schedule()
        sched += Play(Waveform([0.5]), DriveChannel(1))
        sched += Delay(1, DriveChannel(1))
        sched += Play(Waveform([-0.5]), DriveChannel(1))

        sched += Delay(1, DriveChannel(0))
        sched += Play(Waveform([1.]), DriveChannel(0))

        sched |= Acquire(1, AcquireChannel(0), MemorySlot(0)) << sched.duration

        # Result of schedule should be the unitary -1j*Z, so check rotation of an X eigenstate
        pulse_sim = PulseSimulator(system_model=system_model,
                                   initial_state=np.array([1., 1.]) / np.sqrt(2))

        qobj = assemble([sched],
                        backend=pulse_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[0., 0.],
                        memory_slots=2,
                        shots=1)

        results = pulse_sim.run(qobj).result()

        statevector = results.get_statevector()
        expected_vector = np.array([-1j, 1j]) / np.sqrt(2)

        self.assertGreaterEqual(state_fidelity(statevector, expected_vector),
                                1 - (10**-5))

        # verify validity of simulation when no delays included
        sched = Schedule()
        sched += Play(Waveform([0.5]), DriveChannel(1))
        sched += Play(Waveform([-0.5]), DriveChannel(1))

        sched += Play(Waveform([1.]), DriveChannel(0))

        sched |= Acquire(1, AcquireChannel(0), MemorySlot(0)) << sched.duration

        qobj = assemble([sched],
                        backend=pulse_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[0., 0.],
                        memory_slots=2,
                        shots=1)

        results = pulse_sim.run(qobj).result()

        statevector = results.get_statevector()
        U = expm(1j * np.pi * self.Y / 4) @ expm(-1j * np.pi *
                                                 (self.Y / 4 + self.X / 2))
        expected_vector = U @ np.array([1., 1.]) / np.sqrt(2)

        self.assertGreaterEqual(state_fidelity(statevector, expected_vector),
                                1 - (10**-5))
    def test_2Q_exchange(self):
        r"""Test a more complicated 2q simulation"""

        q_freqs = [5., 5.1]
        r = 0.02
        j = 0.02
        total_samples = 25

        hamiltonian = {}
        hamiltonian['h_str'] = [
            '2*np.pi*v0*0.5*Z0', '2*np.pi*v1*0.5*Z1', '2*np.pi*r*0.5*X0||D0',
            '2*np.pi*r*0.5*X1||D1', '2*np.pi*j*0.5*I0*I1',
            '2*np.pi*j*0.5*X0*X1', '2*np.pi*j*0.5*Y0*Y1', '2*np.pi*j*0.5*Z0*Z1'
        ]
        hamiltonian['vars'] = {
            'v0': q_freqs[0],
            'v1': q_freqs[1],
            'r': r,
            'j': j
        }
        hamiltonian['qub'] = {'0': 2, '1': 2}
        ham_model = HamiltonianModel.from_dict(hamiltonian)

        # set the U0 to have frequency of drive channel 0
        u_channel_lo = []
        subsystem_list = [0, 1]
        dt = 1.

        system_model = PulseSystemModel(hamiltonian=ham_model,
                                        u_channel_lo=u_channel_lo,
                                        subsystem_list=subsystem_list,
                                        dt=dt)

        # try some random schedule
        schedule = Schedule()
        drive_pulse = Waveform(np.ones(total_samples))
        schedule += Play(drive_pulse, DriveChannel(0))
        schedule |= Play(drive_pulse, DriveChannel(1)) << 2 * total_samples

        schedule |= Acquire(total_samples, AcquireChannel(0),
                            MemorySlot(0)) << 3 * total_samples
        schedule |= Acquire(total_samples, AcquireChannel(1),
                            MemorySlot(1)) << 3 * total_samples

        y0 = np.array([1., 0., 0., 0.])
        pulse_sim = PulseSimulator(system_model=system_model,
                                   initial_state=y0,
                                   seed=9000)

        qobj = assemble([schedule],
                        backend=pulse_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=q_freqs,
                        memory_slots=2,
                        shots=1000)
        result = pulse_sim.run(qobj).result()
        pulse_sim_yf = result.get_statevector()

        # set up and run independent simulation
        d0_samps = np.concatenate(
            (np.ones(total_samples), np.zeros(2 * total_samples)))
        d1_samps = np.concatenate(
            (np.zeros(2 * total_samples), np.ones(total_samples)))
        samples = np.array([d0_samps, d1_samps]).transpose()
        q_freqs = np.array(q_freqs)
        yf = simulate_2q_exchange_model(y0, q_freqs, r, j, q_freqs, samples,
                                        1.)

        # Check fidelity of statevectors
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1 - (10**-5))
    def test_subsystem_restriction(self):
        r"""Test behavior of subsystem_list subsystem restriction"""

        total_samples = 100

        # set coupling term and drive channels to 0 frequency
        j = 0.5 / total_samples
        omega_d = 0.

        subsystem_list = [0, 2]
        y0 = np.kron(np.array([1., 0.]), np.array([0., 1.]))

        system_model = self._system_model_3Q(j, subsystem_list=subsystem_list)
        pulse_sim = PulseSimulator(system_model=system_model)

        schedule = self._3Q_constant_sched(total_samples,
                                           u_idx=0,
                                           subsystem_list=subsystem_list)
        qobj = assemble([schedule],
                        backend=pulse_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d, omega_d, omega_d],
                        memory_slots=2,
                        shots=1)

        result = pulse_sim.run(qobj, initial_state=y0).result()

        pulse_sim_yf = result.get_statevector()

        yf = expm(-1j * 0.5 * 2 * np.pi * np.kron(self.X, self.Z) / 4) @ y0

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1 - (10**-5))

        y0 = np.kron(np.array([1., 0.]), np.array([1., 0.]))

        result = pulse_sim.run(qobj, initial_state=y0).result()
        pulse_sim_yf = result.get_statevector()

        yf = expm(-1j * 0.5 * 2 * np.pi * np.kron(self.X, self.Z) / 4) @ y0

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1 - (10**-5))

        subsystem_list = [1, 2]
        system_model = self._system_model_3Q(j, subsystem_list=subsystem_list)

        y0 = np.kron(np.array([1., 0.]), np.array([0., 1.]))
        pulse_sim.set_options(system_model=system_model)
        schedule = self._3Q_constant_sched(total_samples,
                                           u_idx=1,
                                           subsystem_list=subsystem_list)
        qobj = assemble([schedule],
                        backend=pulse_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d, omega_d, omega_d],
                        memory_slots=2,
                        shots=1)

        result = pulse_sim.run(qobj, initial_state=y0).result()
        pulse_sim_yf = result.get_statevector()

        yf = expm(-1j * 0.5 * 2 * np.pi * np.kron(self.X, self.Z) / 4) @ y0

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1 - (10**-5))

        y0 = np.kron(np.array([1., 0.]), np.array([1., 0.]))
        pulse_sim.set_options(initial_state=y0)

        result = pulse_sim.run(qobj).result()
        pulse_sim_yf = result.get_statevector()

        yf = expm(-1j * 0.5 * 2 * np.pi * np.kron(self.X, self.Z) / 4) @ y0

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1 - (10**-5))
    def test_3d_oscillator(self):
        """Test simulation of a duffing oscillator truncated to 3 dimensions."""

        total_samples = 100

        freq = 5.
        anharm = -0.33

        # Test pi pulse
        r = 0.5 / total_samples

        # set up simulator
        system_model = system_model = self._system_model_3d_oscillator(freq, anharm, r)
        pulse_sim = PulseSimulator(system_model=system_model)

        schedule = self._1Q_constant_sched(total_samples)
        qobj = assemble([schedule],
                        backend=pulse_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[freq],
                        shots=1)

        result = pulse_sim.run(qobj).result()
        pulse_sim_yf = result.get_statevector()

        # set up and run independent simulation
        y0 = np.array([1., 0., 0.])
        samples = np.ones((total_samples, 1))
        indep_yf = simulate_3d_oscillator_model(y0, freq, anharm, r,
                                                np.array([freq]), samples, 1.)

        # test final state
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1 - 10**-5)

        # test with different input state
        y0 = np.array([0., 0., 1.])

        # Test some irregular value
        r = 1.49815 / total_samples
        system_model = self._system_model_3d_oscillator(freq, anharm, r)
        pulse_sim.set_options(system_model=system_model)

        schedule = self._1Q_constant_sched(total_samples)
        qobj = assemble([schedule],
                        backend=pulse_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[freq],
                        shots=1)
        result = pulse_sim.run(qobj, initial_state=y0).result()
        pulse_sim_yf = result.get_statevector()

        samples = np.ones((total_samples, 1))
        indep_yf = simulate_3d_oscillator_model(y0, freq, anharm, r,
                                                np.array([freq]), samples, 1.)

        # test final state
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1 - 10**-5)
Exemplo n.º 11
0
class TestPulseSimulator(common.QiskitAerTestCase):
    r"""PulseSimulator tests."""

    def setUp(self):
        """ Set configuration settings for pulse simulator
        WARNING: We do not support Python 3.5 because the digest algorithm relies on dictionary insertion order.
        This "feature" was introduced later on Python 3.6 and there's no official support for OrderedDict in the C API so
        Python 3.5 support has been disabled while looking for a propper fix.
        """
        if sys.version_info.major == 3 and sys.version_info.minor == 5:
           self.skipTest("We don't support Python 3.5 for Pulse simulator")

        # Get pulse simulator backend
        self.backend_sim = PulseSimulator()

        self.X = np.array([[0., 1.], [1., 0.]])
        self.Y = np.array([[0., -1j], [1j, 0.]])
        self.Z = np.array([[1., 0.], [0., -1.]])

    # ---------------------------------------------------------------------
    # Test single qubit gates
    # ---------------------------------------------------------------------

    def test_x_gate(self):
        """Test a schedule for a pi pulse on a 2 level system."""

        # qubit frequency and drive frequency
        omega_0 = 1.1329824
        omega_d = omega_0

        # drive strength and length of pulse
        r = 0.01
        total_samples = 100

        system_model = self._system_model_1Q(omega_0, r)

        # set up constant pulse for doing a pi pulse
        schedule = self._1Q_constant_sched(total_samples)

        # set up schedule and qobj
        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d],
                        memory_slots=1,
                        shots=256)

        # set backend backend_options including initial state
        y0 = np.array([1.0, 0.0])
        backend_options = {'seed' : 9000, 'initial_state' : y0}

        # run simulation
        result = self.backend_sim.run(qobj,
                                      system_model=system_model,
                                      backend_options=backend_options).result()
        pulse_sim_yf = result.get_statevector()

        # set up and run independent simulation
        samples = np.ones((total_samples, 1))

        indep_yf = simulate_1q_model(y0, omega_0, r, np.array([omega_0]), samples, 1.)

        # approximate analytic solution
        phases = np.exp(-1j * 2 * np.pi * omega_0 * total_samples * np.array([1., -1.]) / 2)
        approx_yf = phases * np.array([0., -1j])

        # test final state
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1-10**-5)
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, approx_yf), 0.99)

        # test counts
        counts = result.get_counts()
        exp_counts = {'1': 256}
        self.assertDictAlmostEqual(counts, exp_counts)

    def test_x_gate_rwa(self):
        """Test a schedule for a pi pulse on a 2 level system in the rotating frame with a
        the rotating wave approximation."""

        # qubit frequency and drive frequency
        omega_0 = 0.
        omega_d = omega_0

        # drive strength and length of pulse
        # in rotating wave with RWA the drive strength is halved
        r = 0.01 / 2
        total_samples = 100

        system_model = self._system_model_1Q(omega_0, r)

        # set up constant pulse for doing a pi pulse
        schedule = self._1Q_constant_sched(total_samples)

        # set up schedule and qobj
        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d],
                        memory_slots=1,
                        shots=1)

        # set backend backend_options including initial state
        y0 = np.array([1.0, 0.0])
        backend_options = {'seed' : 9000, 'initial_state' : y0}

        # run simulation
        result = self.backend_sim.run(qobj,
                                      system_model=system_model,
                                      backend_options=backend_options).result()
        pulse_sim_yf = result.get_statevector()

        # expected final state
        yf = np.array([0., -1j])

        # test final state
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1-10**-5)


    def test_x_half_gate(self):
        """Test a schedule for a pi/2 pulse on a 2 level system. Same setup as test_x_gate but
        with half the time."""

        # qubit frequency and drive frequency
        omega_0 = 1.1329824
        omega_d = omega_0

        # drive strength and length of pulse
        r = 0.01
        total_samples = 50

        system_model = self._system_model_1Q(omega_0, r)

        # set up constant pulse for doing a pi pulse
        schedule = self._1Q_constant_sched(total_samples)

        # set up schedule and qobj
        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d],
                        memory_slots=1,
                        shots=256)

        # set backend backend_options
        y0 = np.array([1.0, 0.0])
        backend_options = {'seed' : 9000, 'initial_state' : y0}

        # run simulation
        result = self.backend_sim.run(qobj,
                                      system_model=system_model,
                                      backend_options=backend_options).result()
        pulse_sim_yf = result.get_statevector()

        # set up and run independent simulation
        samples = np.ones((total_samples, 1))

        indep_yf = simulate_1q_model(y0, omega_0, r, np.array([omega_d]), samples, 1.)

        # approximate analytic solution
        phases = np.exp(-1j * 2 * np.pi * omega_0 * total_samples * np.array([1., -1.]) / 2)
        approx_yf = phases * (expm(-1j * (np.pi / 4) * self.X) @ y0)

        # test final state
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1-10**-5)
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, approx_yf), 0.99)

        # test counts
        counts = result.get_counts()
        exp_counts = {'1': 132, '0': 124}
        self.assertDictAlmostEqual(counts, exp_counts)

    def test_y_half_gate(self):
        """Test a schedule for a pi/2 pulse about the y axis on a 2 level system.
        Same setup as test_x_half_gate but with amplitude of pulse 1j."""

        # qubit frequency and drive frequency
        omega_0 = 1.1329824
        omega_d = omega_0

        # drive strength and length of pulse
        r = 0.01
        total_samples = 50

        system_model = self._system_model_1Q(omega_0, r)

        # set up constant pulse for doing a pi pulse
        schedule = self._1Q_constant_sched(total_samples, amp=1j)

        # set up schedule and qobj
        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d],
                        memory_slots=1,
                        shots=256)

        # set backend backend_options
        y0 = np.array([1.0, 0.0])
        backend_options = {'seed' : 9000, 'initial_state' : y0}

        # run simulation
        result = self.backend_sim.run(qobj,
                                      system_model=system_model,
                                      backend_options=backend_options).result()
        pulse_sim_yf = result.get_statevector()

        # set up and run independent simulation
        samples = 1j * np.ones((total_samples, 1))

        indep_yf = simulate_1q_model(y0, omega_0, r, np.array([omega_d]), samples, 1.)

        # approximate analytic solution
        phases = np.exp(-1j * 2 * np.pi * omega_0 * total_samples * np.array([1., -1.]) / 2)
        approx_yf = phases * (expm(-1j * (np.pi / 4) * self.Y) @ y0)

        # test final state
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1-10**-5)
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, approx_yf), 0.99)

        # test counts
        counts = result.get_counts()
        exp_counts = {'1': 131, '0': 125}
        self.assertDictAlmostEqual(counts, exp_counts)

    def test_1Q_noise(self):
        """Tests simulation of noise operators. Uses the same schedule as test_x_gate, but
        with a high level of amplitude damping noise.
        """

        # qubit frequency and drive frequency
        omega_0 = 1.1329824
        omega_d = omega_0

        # drive strength and length of pulse
        r = 0.01
        total_samples = 100

        system_model = self._system_model_1Q(omega_0, r)

        # set up constant pulse for doing a pi pulse
        schedule = self._1Q_constant_sched(total_samples)

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d],
                        memory_slots=2,
                        shots=10)

        # set seed for simulation, and set noise
        y0 = np.array([1., 0.])
        backend_options = {'seed' : 9000, 'initial_state' : y0}
        backend_options['noise_model'] = {"qubit": {"0": {"Sm": 1.}}}

        # run simulation
        result = self.backend_sim.run(qobj, system_model=system_model,
                                      backend_options=backend_options).result()

        # test results
        # This level of noise is high enough that all counts should yield 0,
        # whereas in the noiseless simulation (in test_x_gate) all counts yield 1
        counts = result.get_counts()
        exp_counts = {'0': 10}
        self.assertDictAlmostEqual(counts, exp_counts)

    def test_unitary_parallel(self):
        """Test for parallel solving in unitary simulation. Uses same schedule as test_x_gate but
        runs it twice to trigger parallel execution.
        """
        # qubit frequency and drive frequency
        omega_0 = 1.
        omega_d = omega_0

        # drive strength and length of pulse
        r = 0.01
        total_samples = 50

        system_model = self._system_model_1Q(omega_0, r)

        # set up constant pulse for doing a pi pulse
        schedule = self._1Q_constant_sched(total_samples)

        # set up schedule and qobj
        qobj = assemble([schedule, schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d],
                        memory_slots=1,
                        shots=256)

        # set backend backend_options
        y0 = np.array([1., 0.])
        backend_options = backend_options = {'seed' : 9000, 'initial_state' : y0}

        # run simulation
        result = self.backend_sim.run(qobj, system_model=system_model,
                                      backend_options=backend_options).result()

        # test results, checking both runs in parallel
        counts = result.get_counts()
        exp_counts0 = {'1': 132, '0': 124}
        exp_counts1 = {'0': 147, '1': 109}
        self.assertDictAlmostEqual(counts[0], exp_counts0)
        self.assertDictAlmostEqual(counts[1], exp_counts1)


    def test_dt_scaling_x_gate(self):
        """Test that dt is being used correctly by the solver."""

        total_samples = 100
        # do the same thing as test_x_gate, but scale dt and all frequency parameters
        # define test case for a single scaling
        def scale_test(scale):

            # qubit frequency and drive frequency
            omega_0 = 1. / scale
            omega_d = omega_0

            # drive strength and length of pulse
            r = 0.01 / scale
            total_samples = 100

            # set up system model and scale time
            system_model = self._system_model_1Q(omega_0, r)
            system_model.dt = system_model.dt * scale

            # set up constant pulse for doing a pi pulse
            schedule = self._1Q_constant_sched(total_samples)

            qobj = assemble([schedule],
                            backend=self.backend_sim,
                            meas_level=2,
                            meas_return='single',
                            meas_map=[[0]],
                            qubit_lo_freq=[omega_d],
                            memory_slots=2,
                            shots=256)

            # set backend backend_options
            y0 = np.array([1., 0.])
            backend_options = {'seed' : 9000, 'initial_state': y0}

            # run simulation
            result = self.backend_sim.run(qobj, system_model=system_model,
                                          backend_options=backend_options).result()

            pulse_sim_yf = result.get_statevector()

            # set up and run independent simulation
            samples = np.ones((total_samples, 1))

            indep_yf = simulate_1q_model(y0, omega_0, r, np.array([omega_0]), samples, scale)

            # approximate analytic solution
            phases = np.exp(-1j * 2 * np.pi * omega_0 * total_samples * np.array([1., -1.]) / 2)
            approx_yf = phases * np.array([0., -1j])

            # test final state
            self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1-10**-5)
            self.assertGreaterEqual(state_fidelity(pulse_sim_yf, approx_yf), 0.99)

            counts = result.get_counts()
            exp_counts = {'1': 256}

            self.assertDictAlmostEqual(counts, exp_counts)

        # set scales and run tests
        scales = [2., 0.1234, 10.**5, 10**-5]
        for scale in scales:
            scale_test(scale)

    def test_arbitrary_constant_drive(self):
        """Test a few examples w/ arbitary drive, phase and amplitude. """

        total_samples = 100
        num_tests = 3

        omega_0 = 1.
        omega_d_vals = [omega_0 + 1., omega_0 + 0.02, omega_0 + 0.005]
        r_vals = [3 / total_samples, 5 / total_samples, 0.1]
        phase_vals = [5 * np.pi / 7, 19 * np.pi / 14, np.pi / 4]

        for i in range(num_tests):
            with self.subTest(i=i):

                system_model = self._system_model_1Q(omega_0, r_vals[i])
                schedule = self._1Q_constant_sched(total_samples, amp=np.exp(-1j * phase_vals[i]))

                qobj = assemble([schedule],
                                backend=self.backend_sim,
                                meas_level=2,
                                meas_return='single',
                                meas_map=[[0]],
                                qubit_lo_freq=[omega_d_vals[i]],
                                memory_slots=2,
                                shots=1)

                # Run qobj and compare prop to expected result
                y0 = np.array([1., 0.])
                backend_options = {'seed' : 9000, 'initial_state' : y0}
                result = self.backend_sim.run(qobj, system_model, backend_options).result()

                pulse_sim_yf = result.get_statevector()

                # set up and run independent simulation
                samples = np.exp(-1j * phase_vals[i]) * np.ones((total_samples, 1))

                indep_yf = simulate_1q_model(y0, omega_0, r_vals[i], np.array([omega_d_vals[i]]), samples, 1.)

                # approximate analytic solution
                phases = np.exp(-1j * 2 * np.pi * omega_d_vals[i] * total_samples * np.array([1., -1.]) / 2)
                detuning = omega_0 - omega_d_vals[i]
                amp = np.exp(-1j * phase_vals[i])
                rwa_ham = 2 * np.pi * (detuning * self.Z / 2 + r_vals[i] * np.array([[0, amp.conj()], [amp, 0.]]) / 4)
                approx_yf = phases * (expm(-1j * rwa_ham * total_samples) @ y0)

                # test final state
                self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1-10**-5)
                self.assertGreaterEqual(state_fidelity(pulse_sim_yf, approx_yf), 0.99)

    def test_3d_oscillator(self):
        """Test simulation of a duffing oscillator truncated to 3 dimensions."""

        total_samples = 100

        freq = 5.
        anharm = -0.33

        # Test pi pulse
        r = 0.5 / total_samples

        system_model = self._system_model_3d_oscillator(freq, anharm, r)
        schedule = self._1Q_constant_sched(total_samples)

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[freq],
                        shots=1)
        backend_options = {'seed' : 9000}

        result = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = result.get_statevector()


        # set up and run independent simulation
        y0 = np.array([1., 0., 0.])
        samples = np.ones((total_samples, 1))
        indep_yf = simulate_3d_oscillator_model(y0, freq, anharm, r, np.array([freq]), samples, 1.)

        # test final state
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1-10**-5)


        # Test some irregular value
        r = 1.49815 / total_samples

        system_model = self._system_model_3d_oscillator(freq, anharm, r)
        schedule = self._1Q_constant_sched(total_samples)

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[freq],
                        shots=1)

        y0 = np.array([0., 0., 1.])
        backend_options = {'seed' : 9000, 'initial_state' : y0}

        result = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = result.get_statevector()

        samples = np.ones((total_samples, 1))
        indep_yf = simulate_3d_oscillator_model(y0, freq, anharm, r, np.array([freq]), samples, 1.)

        # test final state
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1-10**-5)

    def test_2Q_interaction(self):
        r"""Test 2 qubit interaction via controlled operations using u channels."""

        total_samples = 100

        # set coupling term and drive channels to 0 frequency
        j = 0.5 / total_samples
        omega_d0 = 0.
        omega_d1 = 0.

        system_model = self._system_model_2Q(j)

        schedule = self._2Q_constant_sched(total_samples)

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d0, omega_d1],
                        memory_slots=2,
                        shots=1)

        y0 = np.kron(np.array([1., 0.]), np.array([0., 1.]))
        backend_options = {'seed' : 9000, 'initial_state': y0}

        result = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = result.get_statevector()

        # exact analytic solution
        yf = expm(-1j * 0.5 * 2 * np.pi * np.kron(self.X, self.Z) / 4) @ y0

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1 - (10**-5))

        # run with different initial state
        y0 = np.kron(np.array([1., 0.]), np.array([1., 0.]))
        backend_options = {'seed' : 9000, 'initial_state': y0}

        result = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = result.get_statevector()

        # exact analytic solution
        yf = expm(-1j * 0.5 * 2 * np.pi * np.kron(self.X, self.Z) / 4) @ y0

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1 - (10**-5))


    def test_subsystem_restriction(self):
        r"""Test behavior of subsystem_list subsystem restriction"""

        total_samples = 100

        # set coupling term and drive channels to 0 frequency
        j = 0.5 / total_samples
        omega_d = 0.

        subsystem_list = [0, 2]
        system_model = self._system_model_3Q(j, subsystem_list=subsystem_list)

        schedule = self._3Q_constant_sched(total_samples, u_idx=0, subsystem_list=subsystem_list)

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d, omega_d, omega_d],
                        memory_slots=2,
                        shots=1)

        y0 = np.kron(np.array([1., 0.]), np.array([0., 1.]))
        backend_options = {'seed' : 9000, 'initial_state': y0}

        result = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = result.get_statevector()

        yf = expm(-1j * 0.5 * 2 * np.pi * np.kron(self.X, self.Z) / 4) @ y0

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1 - (10**-5))

        y0 = np.kron(np.array([1., 0.]), np.array([1., 0.]))
        backend_options = {'seed' : 9000, 'initial_state': y0}

        result = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = result.get_statevector()

        yf = expm(-1j * 0.5 * 2 * np.pi * np.kron(self.X, self.Z) / 4) @ y0

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1 - (10**-5))

        subsystem_list = [1, 2]
        system_model = self._system_model_3Q(j, subsystem_list=subsystem_list)

        schedule = self._3Q_constant_sched(total_samples, u_idx=1, subsystem_list=subsystem_list)

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_d, omega_d, omega_d],
                        memory_slots=2,
                        shots=1)

        y0 = np.kron(np.array([1., 0.]), np.array([0., 1.]))
        backend_options = {'seed' : 9000, 'initial_state': y0}

        result = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = result.get_statevector()

        yf = expm(-1j * 0.5 * 2 * np.pi * np.kron(self.X, self.Z) / 4) @ y0

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1 - (10**-5))

        y0 = np.kron(np.array([1., 0.]), np.array([1., 0.]))
        backend_options = {'seed' : 9000, 'initial_state': y0}

        result = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = result.get_statevector()

        yf = expm(-1j * 0.5 * 2 * np.pi * np.kron(self.X, self.Z) / 4) @ y0

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1 - (10**-5))

    def test_simulation_without_variables(self):
        r"""Test behavior of subsystem_list subsystem restriction.
        Same setup as test_x_gate, but with explicit Hamiltonian construction without
        variables
        """

        ham_dict = {'h_str': ['np.pi*Z0', '0.02*np.pi*X0||D0'], 'qub': {'0': 2}}
        ham_model = HamiltonianModel.from_dict(ham_dict)

        u_channel_lo = []
        subsystem_list = [0]
        dt = 1.

        system_model = PulseSystemModel(hamiltonian=ham_model,
                                        u_channel_lo=u_channel_lo,
                                        subsystem_list=subsystem_list,
                                        dt=dt)

        # set up schedule and qobj
        total_samples = 50
        schedule = self._1Q_constant_sched(total_samples)
        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[1.],
                        memory_slots=2,
                        shots=256)

        # set backend backend_options
        backend_options = {'seed' : 9000, 'initial_state' : np.array([1., 0.])}

        # run simulation
        result = self.backend_sim.run(qobj, system_model=system_model,
                                      backend_options=backend_options).result()

        # test results
        counts = result.get_counts()
        exp_counts = {'1': 256}
        self.assertDictAlmostEqual(counts, exp_counts)

    def test_meas_level_1(self):
        """Test measurement level 1. """

        shots = 10000  # run large number of shots for good proportions

        total_samples = 100
        omega_0 = 1.
        omega_d = omega_0

        # Require omega_a*time = pi to implement pi pulse (x gate)
        # num of samples gives time
        r = 1. /  (2 * total_samples)

        system_model = self._system_model_1Q(omega_0, r)

        amp = np.exp(-1j * np.pi / 2)
        schedule = self._1Q_constant_sched(total_samples, amp=amp)

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=1,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[1.],
                        memory_slots=2,
                        shots=shots)

        # set backend backend_options
        y0 = np.array([1.0, 0.0])
        backend_options = {'seed' : 9000, 'initial_state' : y0}
        result = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = result.get_statevector()

        samples = amp * np.ones((total_samples, 1))
        indep_yf = simulate_1q_model(y0, omega_0, r, np.array([omega_d]), samples, 1.)

        # test final state
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1-10**-5)

        # Verify that (about) half the IQ vals have abs val 1 and half have abs val 0
        # (use prop for easier comparison)
        mem = np.abs(result.get_memory()[:, 0])

        iq_prop = {'0': 0, '1': 0}
        for i in mem:
            if i == 0:
                iq_prop['0'] += 1 / shots
            else:
                iq_prop['1'] += 1 / shots

        exp_prop = {'0': 0.5, '1': 0.5}

        self.assertDictAlmostEqual(iq_prop, exp_prop, delta=0.01)

    def test_gaussian_drive(self):
        """Test gaussian drive pulse using meas_level_2. Set omega_d0=omega_0 (drive on resonance),
        phi=0, omega_a = pi/time
        """

        # set omega_0, omega_d0 equal (use qubit frequency) -> drive on resonance
        total_samples = 100
        omega_0 = 1.
        omega_d = omega_0

        # Require omega_a*time = pi to implement pi pulse (x gate)
        # num of samples gives time
        r = np.pi / total_samples

        # Test gaussian drive results for a few different sigma
        gauss_sigmas = [total_samples / 6, total_samples / 3, total_samples]

        system_model = self._system_model_1Q(omega_0, r)

        for gauss_sigma in gauss_sigmas:
            with self.subTest(gauss_sigma=gauss_sigma):
                times = 1.0 * np.arange(total_samples)
                gaussian_samples = np.exp(-times**2 / 2 / gauss_sigma**2)
                drive_pulse = SamplePulse(gaussian_samples, name='drive_pulse')

                # construct schedule
                schedule = Schedule()
                schedule |= Play(drive_pulse, DriveChannel(0))
                schedule |= Acquire(1, AcquireChannel(0), MemorySlot(0)) << schedule.duration

                qobj = assemble([schedule],
                                backend=self.backend_sim,
                                meas_level=2,
                                meas_return='single',
                                meas_map=[[0]],
                                qubit_lo_freq=[omega_d],
                                memory_slots=2,
                                shots=1)
                y0 = np.array([1., 0.])
                backend_options = {'seed' : 9000, 'initial_state' : y0}

                result = self.backend_sim.run(qobj, system_model, backend_options).result()
                pulse_sim_yf = result.get_statevector()

                # run independent simulation
                yf = simulate_1q_model(y0, omega_0, r, np.array([omega_d]), gaussian_samples, 1.)

                # Check fidelity of statevectors
                self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1-(10**-5))

    def test_2Q_exchange(self):
        r"""Test a more complicated 2q simulation"""

        q_freqs = [5., 5.1]
        r = 0.02
        j = 0.02
        total_samples = 25

        hamiltonian = {}
        hamiltonian['h_str'] = ['2*np.pi*v0*0.5*Z0',
                                '2*np.pi*v1*0.5*Z1',
                                '2*np.pi*r*0.5*X0||D0',
                                '2*np.pi*r*0.5*X1||D1',
                                '2*np.pi*j*0.5*I0*I1',
                                '2*np.pi*j*0.5*X0*X1',
                                '2*np.pi*j*0.5*Y0*Y1',
                                '2*np.pi*j*0.5*Z0*Z1']
        hamiltonian['vars'] = {'v0': q_freqs[0],
                               'v1': q_freqs[1],
                               'r': r,
                               'j': j}
        hamiltonian['qub'] = {'0': 2, '1': 2}
        ham_model = HamiltonianModel.from_dict(hamiltonian)

        # set the U0 to have frequency of drive channel 0
        u_channel_lo = []
        subsystem_list = [0, 1]
        dt = 1.

        system_model = PulseSystemModel(hamiltonian=ham_model,
                                        u_channel_lo=u_channel_lo,
                                        subsystem_list=subsystem_list,
                                        dt=dt)

        # try some random schedule
        schedule = Schedule()
        drive_pulse = SamplePulse(np.ones(total_samples))
        schedule += Play(drive_pulse, DriveChannel(0))
        schedule |= Play(drive_pulse, DriveChannel(1)) << 2 * total_samples

        schedule |= Acquire(total_samples,
                            AcquireChannel(0),
                            MemorySlot(0)) << 3 * total_samples
        schedule |= Acquire(total_samples,
                            AcquireChannel(1),
                            MemorySlot(1)) << 3 * total_samples

        qobj = assemble([schedule],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=q_freqs,
                        memory_slots=2,
                        shots=1000)
        y0 = np.array([1., 0., 0., 0.])
        backend_options = {'seed' : 9000, 'initial_state' : y0}

        result = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = result.get_statevector()

        # set up and run independent simulation
        d0_samps = np.concatenate((np.ones(total_samples), np.zeros(2 * total_samples)))
        d1_samps = np.concatenate((np.zeros(2 * total_samples), np.ones(total_samples)))
        samples = np.array([d0_samps, d1_samps]).transpose()
        q_freqs = np.array(q_freqs)
        yf = simulate_2q_exchange_model(y0, q_freqs, r, j, q_freqs, samples, 1.)

        # Check fidelity of statevectors
        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, yf), 1-(10**-5))

    def test_delay_instruction(self):
        """Test for delay instruction."""

        # construct system model specifically for this
        hamiltonian = {}
        hamiltonian['h_str'] = ['0.5*r*X0||D0', '0.5*r*Y0||D1']
        hamiltonian['vars'] = {'r': np.pi}
        hamiltonian['qub'] = {'0': 2}
        ham_model = HamiltonianModel.from_dict(hamiltonian)

        u_channel_lo = []
        subsystem_list = [0]
        dt = 1.

        system_model = PulseSystemModel(hamiltonian=ham_model,
                                        u_channel_lo=u_channel_lo,
                                        subsystem_list=subsystem_list,
                                        dt=dt)

        # construct a schedule that should result in a unitary -Z if delays are correctly handled
        # i.e. do a pi rotation about x, sandwiched by pi/2 rotations about y in opposite directions
        # so that the x rotation is transformed into a z rotation.
        # if delays are not handled correctly this process should fail
        sched = Schedule()
        sched += Play(SamplePulse([0.5]), DriveChannel(1))
        sched += Delay(1, DriveChannel(1))
        sched += Play(SamplePulse([-0.5]), DriveChannel(1))

        sched += Delay(1, DriveChannel(0))
        sched += Play(SamplePulse([1.]), DriveChannel(0))

        sched |= Acquire(1, AcquireChannel(0), MemorySlot(0)) << sched.duration

        qobj = assemble([sched],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[0., 0.],
                        memory_slots=2,
                        shots=1)


        # Result of schedule should be the unitary -1j*Z, so check rotation of an X eigenstate
        backend_options = {'initial_state': np.array([1., 1.]) / np.sqrt(2)}

        results = self.backend_sim.run(qobj, system_model, backend_options).result()

        statevector = results.get_statevector()
        expected_vector = np.array([-1j, 1j]) / np.sqrt(2)

        self.assertGreaterEqual(state_fidelity(statevector, expected_vector), 1 - (10**-5))

        # verify validity of simulation when no delays included
        sched = Schedule()
        sched += Play(SamplePulse([0.5]), DriveChannel(1))
        sched += Play(SamplePulse([-0.5]), DriveChannel(1))

        sched += Play(SamplePulse([1.]), DriveChannel(0))

        sched |= Acquire(1, AcquireChannel(0), MemorySlot(0)) << sched.duration

        qobj = assemble([sched],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[0., 0.],
                        memory_slots=2,
                        shots=1)

        backend_options = {'initial_state': np.array([1., 1.]) / np.sqrt(2)}

        results = self.backend_sim.run(qobj, system_model, backend_options).result()

        statevector = results.get_statevector()
        U = expm(1j * np.pi * self.Y /4) @ expm(-1j * np.pi * (self.Y / 4 + self.X / 2))
        expected_vector = U @ np.array([1., 1.]) / np.sqrt(2)

        self.assertGreaterEqual(state_fidelity(statevector, expected_vector), 1 - (10**-5))

    def test_shift_phase(self):
        """Test ShiftPhase command."""

        omega_0 = 1.123
        r = 1.

        system_model = self._system_model_1Q(omega_0, r)

        # run a schedule in which a shifted phase causes a pulse to cancel itself.
        # Also do it in multiple phase shifts to test accumulation
        sched = Schedule()
        amp1 = 0.12
        sched += Play(SamplePulse([amp1]), DriveChannel(0))
        phi1 = 0.12374 * np.pi
        sched += ShiftPhase(phi1, DriveChannel(0))
        amp2 = 0.492
        sched += Play(SamplePulse([amp2]), DriveChannel(0))
        phi2 = 0.5839 * np.pi
        sched += ShiftPhase(phi2, DriveChannel(0))
        amp3 = 0.12 + 0.21 * 1j
        sched += Play(SamplePulse([amp3]), DriveChannel(0))

        sched |= Acquire(1, AcquireChannel(0), MemorySlot(0)) << sched.duration

        qobj = assemble([sched],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_0],
                        memory_slots=2,
                        shots=1)

        y0 = np.array([1., 0])
        backend_options = {'initial_state': y0}

        results = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = results.get_statevector()

        #run independent simulation
        samples = np.array([[amp1],
                            [amp2 * np.exp(1j * phi1)],
                            [amp3 * np.exp(1j * (phi1 + phi2))]])
        indep_yf = simulate_1q_model(y0, omega_0, r, np.array([omega_0]), samples, 1.)

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1 - (10**-5))

        # run another schedule with only a single shift phase to verify
        sched = Schedule()
        amp1 = 0.12
        sched += Play(SamplePulse([amp1]), DriveChannel(0))
        phi1 = 0.12374 * np.pi
        sched += ShiftPhase(phi1, DriveChannel(0))
        amp2 = 0.492
        sched += Play(SamplePulse([amp2]), DriveChannel(0))
        sched |= Acquire(1, AcquireChannel(0), MemorySlot(0)) << sched.duration

        qobj = assemble([sched],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_0],
                        memory_slots=2,
                        shots=1)

        y0 = np.array([1., 0])
        backend_options = {'initial_state': y0}

        results = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = results.get_statevector()

        #run independent simulation
        samples = np.array([[amp1], [amp2 * np.exp(1j * phi1)]])
        indep_yf = simulate_1q_model(y0, omega_0, r, np.array([omega_0]), samples, 1.)

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1 - (10**-5))

    def test_set_phase(self):
        """Test SetPhase command. Similar to the ShiftPhase test but includes a mixing of
        ShiftPhase and SetPhase instructions to test relative vs absolute changes"""

        omega_0 = 1.3981
        r = 1.

        system_model = self._system_model_1Q(omega_0, r)

        # intermix shift and set phase instructions to verify absolute v.s. relative changes
        sched = Schedule()
        amp1 = 0.12
        sched += Play(SamplePulse([amp1]), DriveChannel(0))
        phi1 = 0.12374 * np.pi
        sched += ShiftPhase(phi1, DriveChannel(0))
        amp2 = 0.492
        sched += Play(SamplePulse([amp2]), DriveChannel(0))
        phi2 = 0.5839 * np.pi
        sched += SetPhase(phi2, DriveChannel(0))
        amp3 = 0.12 + 0.21 * 1j
        sched += Play(SamplePulse([amp3]), DriveChannel(0))
        phi3 = 0.1 * np.pi
        sched += ShiftPhase(phi3, DriveChannel(0))
        amp4 = 0.2 + 0.3 * 1j
        sched += Play(SamplePulse([amp4]), DriveChannel(0))

        sched |= Acquire(1, AcquireChannel(0), MemorySlot(0)) << sched.duration

        qobj = assemble([sched],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_0],
                        memory_slots=2,
                        shots=1)

        y0 = np.array([1., 0.])
        backend_options = {'initial_state': y0}

        results = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = results.get_statevector()

        #run independent simulation
        samples = np.array([[amp1],
                            [amp2 * np.exp(1j * phi1)],
                            [amp3 * np.exp(1j * phi2)],
                            [amp4 * np.exp(1j * (phi2 + phi3))]])
        indep_yf = simulate_1q_model(y0, omega_0, r, np.array([omega_0]), samples, 1.)

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, indep_yf), 1 - (10**-5))

    def test_set_phase_rwa(self):
        """Test SetPhase command using an RWA approximate solution."""
        omega_0 = 5.123
        r = 0.01

        system_model = self._system_model_1Q(omega_0, r)

        sched = Schedule()
        sched += SetPhase(np.pi / 2, DriveChannel(0))
        sched += Play(SamplePulse(np.ones(100)), DriveChannel(0))

        sched |= Acquire(1, AcquireChannel(0), MemorySlot(0)) << sched.duration

        qobj = assemble([sched],
                        backend=self.backend_sim,
                        meas_level=2,
                        meas_return='single',
                        meas_map=[[0]],
                        qubit_lo_freq=[omega_0],
                        memory_slots=2,
                        shots=1)

        y0 = np.array([1., 1.]) / np.sqrt(2)
        backend_options = {'initial_state': y0}

        results = self.backend_sim.run(qobj, system_model, backend_options).result()
        pulse_sim_yf = results.get_statevector()

        #run independent simulation
        phases = np.exp((-1j * 2 * np.pi * omega_0 * np.array([1, -1]) / 2) * 100)
        approx_yf = phases * (expm(-1j * (np.pi / 2) * self.Y) @ y0)

        self.assertGreaterEqual(state_fidelity(pulse_sim_yf, approx_yf), 0.99)

    def _system_model_1Q(self, omega_0, r):
        """Constructs a standard model for a 1 qubit system.

        Args:
            omega_0 (float): qubit frequency
            r (float): drive strength

        Returns:
            PulseSystemModel: model for qubit system
        """

        hamiltonian = {}
        hamiltonian['h_str'] = ['2*np.pi*omega0*0.5*Z0', '2*np.pi*r*0.5*X0||D0']
        hamiltonian['vars'] = {'omega0': omega_0, 'r': r}
        hamiltonian['qub'] = {'0': 2}
        ham_model = HamiltonianModel.from_dict(hamiltonian)

        u_channel_lo = []
        subsystem_list = [0]
        dt = 1.

        return PulseSystemModel(hamiltonian=ham_model,
                                u_channel_lo=u_channel_lo,
                                subsystem_list=subsystem_list,
                                dt=dt)

    def _1Q_constant_sched(self, total_samples, amp=1.):
        """Creates a runnable schedule for 1Q with a constant drive pulse of a given length.

        Args:
            total_samples (int): length of pulse
            amp (float): amplitude of constant pulse (can be complex)

        Returns:
            schedule (pulse schedule): schedule with a drive pulse followed by an acquire
        """

        # set up constant pulse for doing a pi pulse
        drive_pulse = SamplePulse(amp * np.ones(total_samples))
        schedule = Schedule()
        schedule |= Play(drive_pulse, DriveChannel(0))
        schedule |= Acquire(total_samples, AcquireChannel(0), MemorySlot(0)) << schedule.duration

        return schedule

    def _system_model_2Q(self, j):
        """Constructs a model for a 2 qubit system with a U channel controlling coupling and
        no other Hamiltonian terms.

        Args:
            j (float): coupling strength

        Returns:
            PulseSystemModel: model for qubit system
        """

        hamiltonian = {}
        hamiltonian['h_str'] = ['a*X0||D0', 'a*X0||D1', '2*np.pi*j*0.25*(Z0*X1)||U0']
        hamiltonian['vars'] = {'a': 0, 'j': j}
        hamiltonian['qub'] = {'0': 2, '1': 2}
        ham_model = HamiltonianModel.from_dict(hamiltonian)

        # set the U0 to have frequency of drive channel 0
        u_channel_lo = [[UchannelLO(0, 1.0+0.0j)]]
        subsystem_list = [0, 1]
        dt = 1.

        return PulseSystemModel(hamiltonian=ham_model,
                                u_channel_lo=u_channel_lo,
                                subsystem_list=subsystem_list,
                                dt=dt)


    def _2Q_constant_sched(self, total_samples, amp=1., u_idx=0):
        """Creates a runnable schedule with a single pulse on a U channel for two qubits.

        Args:
            total_samples (int): length of pulse
            amp (float): amplitude of constant pulse (can be complex)
            u_idx (int): index of U channel

        Returns:
            schedule (pulse schedule): schedule with a drive pulse followed by an acquire
        """

        # set up constant pulse for doing a pi pulse
        drive_pulse = SamplePulse(amp * np.ones(total_samples))
        schedule = Schedule()
        schedule |= Play(drive_pulse, ControlChannel(u_idx))
        schedule |= Acquire(total_samples, AcquireChannel(0), MemorySlot(0)) << total_samples
        schedule |= Acquire(total_samples, AcquireChannel(1), MemorySlot(1)) << total_samples

        return schedule


    def _system_model_3Q(self, j, subsystem_list=[0, 2]):
        """Constructs a model for a 3 qubit system, with the goal that the restriction to
        [0, 2] and to qubits [1, 2] is the same as in _system_model_2Q

        Args:
            j (float): coupling strength
            subsystem_list (list): list of subsystems to include

        Returns:
            PulseSystemModel: model for qubit system
        """

        hamiltonian = {}
        hamiltonian['h_str'] = ['2*np.pi*j*0.25*(Z0*X2)||U0', '2*np.pi*j*0.25*(Z1*X2)||U1']
        hamiltonian['vars'] = {'j': j}
        hamiltonian['qub'] = {'0': 2, '1': 2, '2': 2}
        ham_model = HamiltonianModel.from_dict(hamiltonian, subsystem_list=subsystem_list)

        # set the U0 to have frequency of drive channel 0
        u_channel_lo = [[UchannelLO(0, 1.0 + 0.0j)], [UchannelLO(0, 1.0 + 0.0j)]]
        dt = 1.

        return PulseSystemModel(hamiltonian=ham_model,
                                u_channel_lo=u_channel_lo,
                                subsystem_list=subsystem_list,
                                dt=dt)

    def _3Q_constant_sched(self, total_samples, amp=1., u_idx=0, subsystem_list=[0, 2]):
        """Creates a runnable schedule for the 3Q system after the system is restricted to
        2 qubits.

        Args:
            total_samples (int): length of pulse
            amp (float): amplitude of constant pulse (can be complex)
            u_idx (int): index of U channel
            subsystem_list (list): list of qubits to restrict to

        Returns:
            schedule (pulse schedule): schedule with a drive pulse followed by an acquire
        """

        # set up constant pulse for doing a pi pulse
        drive_pulse = SamplePulse(amp * np.ones(total_samples))
        schedule = Schedule()
        schedule |= Play(drive_pulse, ControlChannel(u_idx))
        for idx in subsystem_list:
            schedule |= Acquire(total_samples,
                                AcquireChannel(idx),
                                MemorySlot(idx)) << total_samples

        return schedule

    def _system_model_3d_oscillator(self, freq, anharm, r):
        """Model for a duffing oscillator truncated to 3 dimensions.

        Args:
            freq (float): frequency of the oscillator
            anharm (float): anharmonicity of the oscillator
            r (float): drive strength

        Returns:
            PulseSystemModel: model for oscillator system
        """
        hamiltonian = {}
        hamiltonian['h_str'] = ['np.pi*(2*v-alpha)*O0',
                                'np.pi*alpha*O0*O0',
                                '2*np.pi*r*X0||D0']
        hamiltonian['vars'] = {'v' : freq, 'alpha': anharm, 'r': r}
        hamiltonian['qub'] = {'0': 3}
        ham_model = HamiltonianModel.from_dict(hamiltonian)

        u_channel_lo = []
        subsystem_list = [0]
        dt = 1.

        return PulseSystemModel(hamiltonian=ham_model,
                                u_channel_lo=u_channel_lo,
                                subsystem_list=subsystem_list,
                                dt=dt)