Ejemplo n.º 1
0
    def test_append_and_remove(self) -> None:
        dimension = 2
        identity = Unitary.identity(dimension)

        sequence = UnitarySequence(dimension)
        assert sequence.get_length() == 0
        assert sequence.product().close_to(identity)

        sequence.append_first(
            UnitarySequenceEntry(UnitaryDefinitions.sigmax(), [0]))
        assert sequence.get_length() == 1
        assert sequence.product().close_to(UnitaryDefinitions.sigmax())

        sequence.append_last(
            UnitarySequenceEntry(UnitaryDefinitions.sigmay(), [0]))
        assert sequence.get_length() == 2
        assert sequence.product().close_to(UnitaryDefinitions.sigmaz())

        sequence.append_first(
            UnitarySequenceEntry(UnitaryDefinitions.sigmaz(), [0]))
        assert sequence.get_length() == 3
        assert sequence.product().close_to(identity)

        sequence.remove_last()
        assert sequence.get_length() == 2
        assert sequence.product().close_to(UnitaryDefinitions.sigmay())

        sequence.remove_first()
        assert sequence.get_length() == 1
        assert sequence.product().close_to(UnitaryDefinitions.sigmax())

        sequence.remove_first()
        assert sequence.get_length() == 0
        assert sequence.product().close_to(identity)
Ejemplo n.º 2
0
 def test_sequence_output_formats(self) -> None:
     dimension = 2
     rx_entry = UnitarySequenceEntry(UnitaryDefinitions.rx(np.pi / 3), [0])
     ry_entry = UnitarySequenceEntry(UnitaryDefinitions.ry(np.pi / 3), [0])
     sequence = UnitarySequence(dimension, [rx_entry, ry_entry])
     assert sequence.get_qasm()
     assert sequence.get_jaqal()
     assert sequence.get_display_output()
Ejemplo n.º 3
0
    def test_identity(self) -> None:
        dimension = 2
        entry = UnitarySequenceEntry(Unitary.identity(dimension), [0])
        assert entry.get_dimension() == dimension
        assert np.array_equal(entry.get_apply_to(), [0])

        for system_dimension in [2, 4, 8, 16]:
            full_unitary = entry.get_full_unitary(system_dimension)
            assert full_unitary.close_to(Unitary.identity(system_dimension))
Ejemplo n.º 4
0
    def test_cnot_swapped(self) -> None:
        dimension = 4
        entry = UnitarySequenceEntry(UnitaryDefinitions.cnot(), [1, 0])

        system_dimension = 4
        full_unitary = entry.get_full_unitary(system_dimension)
        assert full_unitary.close_to(
            Unitary(
                dimension,
                np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0],
                          [0, 1, 0, 0]])))
Ejemplo n.º 5
0
    def test_inverse(self) -> None:
        dimension = 2
        rx_entry = UnitarySequenceEntry(UnitaryDefinitions.rx(np.pi / 3), [0])
        ry_entry = UnitarySequenceEntry(UnitaryDefinitions.ry(np.pi / 3), [0])
        sequence = UnitarySequence(dimension, [rx_entry, ry_entry])
        product = sequence.product()

        inverse_sequence = sequence.inverse()
        inverse_product = inverse_sequence.product()
        assert inverse_product.close_to(product.inverse())

        inverse_sequence.sequence_product = None
        inverse_product = inverse_sequence.product()
        assert inverse_product.close_to(product.inverse())
Ejemplo n.º 6
0
    def test_combine(self) -> None:
        dimension = 2
        t = Unitary(dimension, np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]))
        t_entry = UnitarySequenceEntry(t, [0])
        sequence_1 = UnitarySequence(dimension, np.repeat(t_entry, 3))
        sequence_2 = UnitarySequence(
            dimension,
            [UnitarySequenceEntry(UnitaryDefinitions.sigmay(), [0])])

        combined_sequence = UnitarySequence.combine(sequence_1, sequence_2)
        assert (combined_sequence.get_length() == sequence_1.get_length() +
                sequence_2.get_length())
        assert combined_sequence.product().close_to(
            sequence_1.product().left_multiply(sequence_2.product()))
Ejemplo n.º 7
0
    def get_ideal_sequence(self, time: float,
                           num_steps: int) -> UnitarySequence:
        '''
        Returns a sequence of identical unitaries, where each unitary is the
        time-evolution operator under this Hamiltonian for time / num_steps,
        and the length of the sequence is num_steps.

        :param time: The total time to evolve the system.
        :type time: float
        :param num_steps: The number of steps in which to break up the
            time evolution of the system.
        :type num_steps: int
        :return: A sequence of num_steps identical unitaries implementing
            the time evolution of the system.
        :rtype: UnitarySequence
        '''
        sequence_entries = []
        time_per_step = time / num_steps
        u = self.get_time_evolution_operator(time_per_step)
        apply_to = list(range(self.get_qubit_count()))
        for _ in range(num_steps):
            entry = UnitarySequenceEntry(u, apply_to)
            sequence_entries.append(entry)

        return UnitarySequence(self.get_dimension(), sequence_entries)
Ejemplo n.º 8
0
    def get_qdrift_sequence(self, time: float,
                            num_repetitions: int) -> UnitarySequence:
        '''
        Returns a sequence of unitaries using a QDRIFT decomposition
        of the time-evolution under this Hamiltonian, as per
        Campbell, PRL 123, 070503 (2019). The sequence approximately
        implements the ideal time evolution of the system.

        :param time: The total time to evolve the system.
        :type time: float
        :param num_repetitions: The number of QDRIFT repetitions to use.
        :type num_repetitions: int
        :return: [description]
        :return: A sequence of unitaries implementing the QDRIFT
            decomposition of the time evolution of the system.
        :rtype: UnitarySequence
        '''
        sequence_entries = []
        coefficients = [term.get_coefficient() for term in self.terms]
        sum_coefficients = np.sum(coefficients)
        prob_coefficients = coefficients / sum_coefficients
        time_per_step = sum_coefficients * time / num_repetitions
        apply_to = list(range(self.get_qubit_count()))
        for _ in range(num_repetitions):
            term = np.random.choice(self.terms, p=prob_coefficients)
            display_suffix = str(self.terms.index(term)) + ' λ/N'
            u = UnitaryDefinitions.time_evolution(term.get_normalized_matrix(),
                                                  time_per_step,
                                                  display_suffix)
            entry = UnitarySequenceEntry(u, apply_to)
            sequence_entries.append(entry)

        return UnitarySequence(self.get_dimension(), sequence_entries)
Ejemplo n.º 9
0
    def compile_rav_sequence(
        self,
        time: float,
        max_t_step: float,
        threshold: float,
        allow_simultaneous_terms: bool = False
    ) -> CompilerResult:
        '''
        Returns a randomized analog verification (RAV) sequence as
        per Shaffer et al., arXiv:2003.04500 (2020). The sequence of
        unitaries is built from terms of this Hamiltonian by first
        generating a random sequence and then using STOQ to compile
        the inverse such that the full sequence approximately
        implements the identity operation.

        :param time: The total time to evolve the system in the
            initial randomly-generated sequence.
        :type time: float
        :param max_t_step: The maximum time to use for a single Hamiltonian
            term at each step of the sequence.
        :type max_t_step: float
        :param threshold: The overlap with the target unitary at which to
            stop the STOQ compilation, defaults to None. A value of 1.0
            implies an exact compilation.
        :type threshold: float
        :param allow_simultaneous_terms: Whether to allow multiple
            Hamiltonian terms to be executed simultaneously in the resulting
            sequence, defaults to False.
        :type allow_simultaneous_terms: bool, optional
        :return: A sequence of unitaries implementing RAV.
        :rtype: CompilerResult
        '''
        # Generate a random sequence, mostly forward in time
        forward_probability = 0.8
        unitary_primitives = self._get_unitary_primitives(
            max_t_step, allow_simultaneous_terms)
        apply_to = list(range(self.get_qubit_count()))
        random_sequence = UnitarySequence(self.get_dimension())
        total_time = 0.0
        while total_time < time:
            t_step = (max_t_step * np.random.random_sample()) * (
                1 if np.random.random_sample() < forward_probability else -1)
            u_step = np.random.choice(
                unitary_primitives).get_unitary().as_unitary([t_step])
            random_sequence.append_last(UnitarySequenceEntry(u_step, apply_to))
            total_time += np.abs(t_step)

        # Calculate the product of this sequence and invert it
        target_unitary = random_sequence.product().inverse()

        # Call _compile_stoq_sequence_from_unitary to compile a new sequence
        # implementing the inverse
        result = self._compile_stoq_sequence_for_target_unitary(
            target_unitary, max_t_step, threshold, allow_simultaneous_terms)

        # Return the CompilerResult with the combined sequence
        result.compiled_sequence = UnitarySequence.combine(
            random_sequence, result.compiled_sequence)
        return result
Ejemplo n.º 10
0
    def create_random_sequence_entry(
            dimension: int,
            unitary_primitives: List[UnitaryPrimitive],
            probabilities: Optional[List[float]] = None
    ) -> UnitarySequenceEntry:
        '''
        Creates and returns a randomly-generated unitary sequence entry.

        :param dimension: The dimension of the state space. For an n-qubit
            system, dimension should be set to 2**n.
        :type dimension: int
        :param unitary_primitives: The unitary primitives from which to
            choose when randomly generating a sequence entry.
        :type unitary_primitives: List[UnitaryPrimitive]
        :param probabilities: The probability for STOQ to
            choose each of the primitives specified in unitary_primitives when
            proposing new gates at each step of the compilation process,
            defaults to None. If not specified, each unitary primitive is
            chosen with uniform probability.
        :type probabilities: Optional[List[float]], optional
        :return: A sequence entry specifying a randomly-chosen unitary
            with randomly-chosen parameters and applied to a randomly-chosen
            set of qubits.
        :rtype: UnitarySequenceEntry
        '''
        # choose from unitary_primitives where the allowed_apply_to list
        # is not empty
        unitary_primitives = [
            u for u in unitary_primitives
            if (u.get_allowed_apply_to() is None
                or len(u.get_allowed_apply_to()) > 0)
        ]

        # randomly choose one of the unitary primitives and assign random
        # parameter values
        new_unitary_primitive = np.random.choice(unitary_primitives,
                                                 p=probabilities)
        new_unitary = new_unitary_primitive.get_unitary()
        if isinstance(new_unitary, ParameterizedUnitary):
            random_parameter_values = [
                p.random_value() for p in new_unitary.get_parameters()
            ]
            new_unitary = new_unitary.as_unitary(random_parameter_values)
        assert isinstance(new_unitary, Unitary)

        # randomly choose the qubit(s) to which to apply the unitary
        num_system_qubits = int(np.log2(dimension))
        if new_unitary_primitive.get_allowed_apply_to() is not None:
            allowed_apply_to = new_unitary_primitive.get_allowed_apply_to()
            apply_to = allowed_apply_to[np.random.choice(
                len(allowed_apply_to))]
        else:
            num_unitary_qubits = int(np.log2(new_unitary.get_dimension()))
            apply_to = np.random.choice(list(range(num_system_qubits)),
                                        num_unitary_qubits,
                                        replace=False)

        return UnitarySequenceEntry(new_unitary, apply_to)
Ejemplo n.º 11
0
    def test_identity_roots_incorrect(self) -> None:
        dimension = 2
        t = Unitary(dimension, np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]))
        t_entry = UnitarySequenceEntry(t, [0])
        sequence = UnitarySequence(dimension, np.repeat(t_entry, 7))

        assert sequence.get_dimension() == dimension
        assert sequence.get_length() == 7
        assert not sequence.product().close_to(np.identity(dimension))
Ejemplo n.º 12
0
    def test_undo(self) -> None:
        dimension = 2

        identity = Unitary.identity(dimension)

        sequence = UnitarySequence(dimension)
        assert sequence.get_length() == 0

        with pytest.raises(Exception):
            sequence.undo()

        sequence.append_first(
            UnitarySequenceEntry(UnitaryDefinitions.sigmax(), [0]))
        assert sequence.get_length() == 1
        assert sequence.product().close_to(UnitaryDefinitions.sigmax())

        sequence.undo()
        assert sequence.get_length() == 0
        assert sequence.product().close_to(identity)

        with pytest.raises(Exception):
            sequence.undo()

        sequence.append_first(
            UnitarySequenceEntry(UnitaryDefinitions.sigmay(), [0]))
        sequence.append_first(
            UnitarySequenceEntry(UnitaryDefinitions.sigmay(), [0]))
        assert sequence.get_length() == 2
        assert sequence.product().close_to(identity)

        sequence.remove_last()
        assert sequence.get_length() == 1
        assert sequence.product().close_to(UnitaryDefinitions.sigmay())

        sequence.undo()
        assert sequence.get_length() == 2
        assert sequence.product().close_to(identity)

        with pytest.raises(Exception):
            sequence.undo()
Ejemplo n.º 13
0
    def test_compile_two_qubits(self) -> None:
        num_qubits = 2
        system_dimension = qubit_dimension**num_qubits
        unitary_primitives = [
            UnitaryPrimitive(UnitaryDefinitions.rx(np.pi / 2)),
            UnitaryPrimitive(UnitaryDefinitions.cnot())
        ]
        compiler = Compiler(system_dimension, unitary_primitives)

        # Ensure determinism by setting the random seed
        np.random.seed(123456)

        target_unitary = UnitarySequence(system_dimension, [
            UnitarySequenceEntry(UnitaryDefinitions.cnot(), [0, 1]),
            UnitarySequenceEntry(UnitaryDefinitions.rx(np.pi), [0])
        ]).product()
        result = compiler.compile(target_unitary)

        assert result.compiled_sequence.product().close_to(target_unitary)
        assert result.compiled_sequence.get_qasm()
        assert result.compiled_sequence.get_display_output()
        assert isinstance(result.cost_by_step, list)
        assert result.total_elapsed_time >= 0.0
Ejemplo n.º 14
0
    def get_trotter_sequence(
        self,
        time: float,
        num_trotter_steps: int,
        randomize: bool = False
    ) -> UnitarySequence:
        '''
        Returns a sequence of unitaries using a Suzuki-Trotter decomposition
        of the time-evolution under this Hamiltonian. The sequence
        approximately implements the ideal time evolution of the system.

        :param time: The total time to evolve the system.
        :type time: float
        :param num_trotter_steps: The number of Trotter steps to use.
        :type num_trotter_steps: int
        :param randomize: Whether to randomize the order of Hamiltonian terms
            in each step of the Suzuki-Trotter decomposition, defaults
            to False.
        :type randomize: bool, optional
        :return: A sequence of unitaries implementing the Suzuki-Trotter
            decomposition of the time evolution of the system.
        :rtype: UnitarySequence
        '''
        sequence_entries = []
        time_per_step = time / num_trotter_steps
        apply_to = list(range(self.get_qubit_count()))
        term_indices = list(range(len(self.terms)))
        for _ in range(num_trotter_steps):
            if randomize:
                random.shuffle(term_indices)
            for term_index in term_indices:
                term = self.terms[term_index]
                u = UnitaryDefinitions.time_evolution(
                    term.get_matrix(), time_per_step, term_index)
                entry = UnitarySequenceEntry(u, apply_to)
                sequence_entries.append(entry)

        return UnitarySequence(self.get_dimension(), sequence_entries)
Ejemplo n.º 15
0
    def test_cnot(self) -> None:
        entry = UnitarySequenceEntry(UnitaryDefinitions.cnot(), [0, 1])

        with pytest.raises(Exception):
            system_dimension = 2
            full_unitary = entry.get_full_unitary(system_dimension)

        system_dimension = 4
        full_unitary = entry.get_full_unitary(system_dimension)
        assert full_unitary.close_to(UnitaryDefinitions.cnot())

        system_dimension = 8
        full_unitary = entry.get_full_unitary(system_dimension)
        assert full_unitary.close_to(UnitaryDefinitions.cnot().tensor(
            Unitary.identity(2)))

        system_dimension = 16
        full_unitary = entry.get_full_unitary(system_dimension)
        assert full_unitary.close_to(UnitaryDefinitions.cnot().tensor(
            Unitary.identity(4)))
        assert full_unitary.left_multiply(full_unitary).close_to(
            Unitary.identity(system_dimension))