Example #1
0
    def test_qft_mutability(self):
        """Test the mutability of the QFT circuit."""
        qft = QFT()

        with self.subTest(msg="empty initialization"):
            self.assertEqual(qft.num_qubits, 0)
            self.assertEqual(qft.data, [])

        with self.subTest(msg="changing number of qubits"):
            qft.num_qubits = 3
            self.assertQFTIsCorrect(qft, num_qubits=3)

        with self.subTest(msg="test diminishing the number of qubits"):
            qft.num_qubits = 1
            self.assertQFTIsCorrect(qft, num_qubits=1)

        with self.subTest(msg="test with swaps"):
            qft.num_qubits = 4
            qft.do_swaps = False
            self.assertQFTIsCorrect(qft, add_swaps_at_end=True)

        with self.subTest(msg="inverse"):
            qft = qft.inverse()
            qft.do_swaps = True
            self.assertQFTIsCorrect(qft, inverse=True)

        with self.subTest(msg="double inverse"):
            qft = qft.inverse()
            self.assertQFTIsCorrect(qft)

        with self.subTest(msg="set approximation"):
            qft.approximation_degree = 2
            qft.do_swaps = True
            with self.assertRaises(AssertionError):
                self.assertQFTIsCorrect(qft)
Example #2
0
    def test_qft_is_inverse(self):
        """Test the is_inverse() method."""
        qft = QFT(2)

        with self.subTest(msg="initial object is not inverse"):
            self.assertFalse(qft.is_inverse())

        qft = qft.inverse()
        with self.subTest(msg="inverted"):
            self.assertTrue(qft.is_inverse())

        qft = qft.inverse()
        with self.subTest(msg="re-inverted"):
            self.assertFalse(qft.is_inverse())
Example #3
0
 def test_qft_matrix(self, inverse):
     """Test the matrix representation of the QFT."""
     num_qubits = 5
     qft = QFT(num_qubits)
     if inverse:
         qft = qft.inverse()
     self.assertQFTIsCorrect(qft, inverse=inverse)
Example #4
0
    def _power_mod_N(self, n: int, N: int, a: int) -> Instruction:
        """Implements modular exponentiation a^x as an instruction."""
        up_qreg = QuantumRegister(2 * n, name="up")
        down_qreg = QuantumRegister(n, name="down")
        aux_qreg = QuantumRegister(n + 2, name="aux")

        circuit = QuantumCircuit(up_qreg,
                                 down_qreg,
                                 aux_qreg,
                                 name=f"{a}^x mod {N}")

        qft = QFT(n + 1, do_swaps=False).to_gate()
        iqft = qft.inverse()

        # Create gates to perform addition/subtraction by N in Fourier Space
        phi_add_N = self._phi_add_gate(self._get_angles(N, n + 1))
        iphi_add_N = phi_add_N.inverse()
        c_phi_add_N = phi_add_N.control(1)

        # Apply the multiplication gates as showed in
        # the report in order to create the exponentiation
        for i in range(2 * n):
            partial_a = pow(a, pow(2, i), N)
            modulo_multiplier = self._controlled_multiple_mod_N(
                n, N, partial_a, c_phi_add_N, iphi_add_N, qft, iqft)
            circuit.append(modulo_multiplier,
                           [up_qreg[i], *down_qreg, *aux_qreg])

        return circuit.to_instruction()
Example #5
0
class BeauregardShor(Shor):
    def _construct_circuit_with_semiclassical_QFT(self, a: int, N: int,
                                                  n: int) -> QuantumCircuit:
        self._qft = QFT(n + 1, do_swaps=False).to_gate()
        self._iqft = self._qft.inverse()

        phi_add_N = phi_constant_adder(get_angles(N, n + 1))
        self._iphi_add_N = phi_add_N.inverse()
        self._c_phi_add_N = phi_add_N.control(1)

        return super()._construct_circuit_with_semiclassical_QFT(a, N, n)

    def _get_aux_register_size(self, n: int) -> int:
        return n + 2

    @property
    def _prefix(self) -> str:
        return 'Beauregard'

    def _modular_exponentiation_gate(self, constant: int, N: int,
                                     n: int) -> Instruction:
        return modular_exponentiation_gate(constant, N, n)

    def _modular_multiplication_gate(self, constant: int, N: int,
                                     n: int) -> Instruction:
        return controlled_modular_multiplication_gate(constant, N, n,
                                                      self._c_phi_add_N,
                                                      self._iphi_add_N,
                                                      self._qft, self._iqft)
Example #6
0
    def test_name_after_inverting(self):
        """Test the name after inverting the QFT is IQFT and not QFT_dg."""
        iqft = QFT(1).inverse()
        i2qft = iqft.inverse()

        with self.subTest(msg="inverted once"):
            self.assertEqual(iqft.name, "IQFT")

        with self.subTest(msg="inverted twice"):
            self.assertEqual(i2qft.name, "QFT")

        with self.subTest(msg="inverse as kwarg"):
            self.assertEqual(QFT(1, inverse=True).name, "IQFT")
    def construct_circuit(circuit=None,
                          qubits=None,
                          inverse=False,
                          approximation_degree=0,
                          do_swaps=True):
        """Construct the circuit representing the desired state vector.

        Args:
            circuit (QuantumCircuit): The optional circuit to extend from.
            qubits (Union(QuantumRegister, list[Qubit])): The optional qubits to construct
                the circuit with.
            approximation_degree (int): degree of approximation for the desired circuit
            inverse (bool): Boolean flag to indicate Inverse Quantum Fourier Transform
            do_swaps (bool): Boolean flag to specify if swaps should be included to align
                the qubit order of
                input and output. The output qubits would be in reversed order without the swaps.

        Returns:
            QuantumCircuit: quantum circuit
        Raises:
            AquaError: invalid input
        """
        warnings.warn(
            'The class FourierTransformCircuits is deprecated and will be removed '
            'no earlier than 3 months after the release 0.7.0. You should use the '
            'qiskit.circuit.library.QFT class instead.',
            DeprecationWarning,
            stacklevel=2)

        if circuit is None:
            raise AquaError('Missing input QuantumCircuit.')

        if qubits is None:
            raise AquaError('Missing input qubits.')

        qft = QFT(len(qubits),
                  approximation_degree=approximation_degree,
                  do_swaps=do_swaps)
        if inverse:
            qft = qft.inverse()

        circuit.append(qft.to_instruction(), qubits)

        return circuit
def modular_exponentiation_gate(constant: int, N: int, n: int) -> Gate:
    x_qreg = QuantumRegister(2 * n, name='x')
    y_qreg = QuantumRegister(n, name='y')
    aux_qreg = QuantumRegister(n + 1, name='aux')

    circuit = QuantumCircuit(x_qreg,
                             y_qreg,
                             aux_qreg,
                             name=f'Exp({constant})_Mod_{N}')

    qft = QFT(n, do_swaps=False).to_gate()
    iqft = qft.inverse()

    for i in range(2 * n):
        partial_constant = pow(constant, pow(2, i), mod=N)
        circuit.append(
            controlled_modular_multiplication_gate(partial_constant, N, n, qft, iqft),
            list(chain([x_qreg[i]], y_qreg, aux_qreg))
        )

    return circuit.to_gate()
Example #9
0
class MixShor(Shor):
    def _construct_circuit_with_semiclassical_QFT(self, a: int, N: int,
                                                  n: int) -> QuantumCircuit:
        self._qft = QFT(n, do_swaps=False).to_gate()
        self._iqft = self._qft.inverse()

        return super()._construct_circuit_with_semiclassical_QFT(a, N, n)

    def _get_aux_register_size(self, n: int) -> int:
        return n + 1

    @property
    def _prefix(self) -> str:
        return 'Mix'

    def _modular_exponentiation_gate(self, constant: int, N: int,
                                     n: int) -> Instruction:
        return modular_exponentiation_gate(constant, N, n)

    def _modular_multiplication_gate(self, constant: int, N: int,
                                     n: int) -> Instruction:
        return controlled_modular_multiplication_gate(constant, N, n,
                                                      self._qft, self._iqft)
Example #10
0
def modular_exponentiation_gate(constant: int, N: int, n: int) -> Instruction:
    up_qreg = QuantumRegister(2 * n, name='up')
    down_qreg = QuantumRegister(n, name='down')
    aux_qreg = QuantumRegister(n + 2, name='aux')

    circuit = QuantumCircuit(up_qreg,
                             down_qreg,
                             aux_qreg,
                             name=f'{constant}^x mod {N}')

    qft = QFT(n + 1, do_swaps=False).to_gate()
    iqft = qft.inverse()

    phi_add_N = phi_constant_adder(get_angles(N, n + 1))
    iphi_add_N = phi_add_N.inverse()
    c_phi_add_N = phi_add_N.control(1)

    for i in range(2 * n):
        partial_constant = pow(constant, pow(2, i), mod=N)
        modulo_multiplier = controlled_modular_multiplication_gate(
            partial_constant, N, n, c_phi_add_N, iphi_add_N, qft, iqft)
        circuit.append(modulo_multiplier, [up_qreg[i], *down_qreg, *aux_qreg])

    return circuit.to_instruction()
Example #11
0
class Shor:
    """Shor's factoring algorithm.

    Shor's Factoring algorithm is one of the most well-known quantum algorithms and finds the
    prime factors for input integer :math:`N` in polynomial time.

    Adapted from https://github.com/ttlion/ShorAlgQiskit

    See also https://arxiv.org/abs/quant-ph/0205095
    """
    def __init__(
        self,
        quantum_instance: Optional[Union[QuantumInstance, BaseBackend,
                                         Backend]] = None
    ) -> None:
        """
        Args:
            quantum_instance: Quantum Instance or Backend

        """
        self._quantum_instance = None
        if quantum_instance:
            self.quantum_instance = quantum_instance

        self._n = None  # type: Optional[int]
        self._up_qreg = None
        self._down_qreg = None  # type: Optional[QuantumRegister]
        self._aux_qreg = None  # type: Optional[QuantumRegister]

        self._qft = QFT(do_swaps=False).to_instruction()
        self._iqft = self._qft.inverse()

        self._phi_add_N = None  # type: Optional[Gate]
        self._iphi_add_N = None

    @property
    def quantum_instance(self) -> Optional[QuantumInstance]:
        """ Returns quantum instance. """
        return self._quantum_instance

    @quantum_instance.setter
    def quantum_instance(
        self, quantum_instance: Union[QuantumInstance, BaseBackend,
                                      Backend]) -> None:
        """ Sets quantum instance. """
        if isinstance(quantum_instance, (BaseBackend, Backend)):
            quantum_instance = QuantumInstance(quantum_instance)
        self._quantum_instance = quantum_instance

    def _get_angles(self, a: int) -> np.ndarray:
        """Calculates the array of angles to be used in the addition in Fourier Space."""
        s = bin(int(a))[2:].zfill(self._n + 1)
        angles = np.zeros([self._n + 1])
        for i in range(0, self._n + 1):
            for j in range(i, self._n + 1):
                if s[j] == '1':
                    angles[self._n - i] += math.pow(2, -(j - i))
            angles[self._n - i] *= np.pi
        return angles[::-1]

    @staticmethod
    def _phi_add_gate(size: int, angles: Union[np.ndarray,
                                               ParameterVector]) -> Gate:
        """Gate that performs addition by a in Fourier Space."""
        circuit = QuantumCircuit(size, name="phi_add")
        for i, angle in enumerate(angles):
            circuit.p(angle, i)
        return circuit.to_gate()

    def _double_controlled_phi_add_mod_N(
            self, num_qubits: int,
            angles: Union[np.ndarray, ParameterVector]) -> QuantumCircuit:
        """Creates a circuit which implements double-controlled modular addition by a."""
        circuit = QuantumCircuit(num_qubits, name="phi_add")

        ctl_up = 0
        ctl_down = 1
        ctl_aux = 2

        # get qubits from aux register, omitting the control qubit
        qubits = range(3, num_qubits)

        # store the gates representing addition/subtraction by a in Fourier Space
        phi_add_a = self._phi_add_gate(len(qubits), angles)
        iphi_add_a = phi_add_a.inverse()

        circuit.append(phi_add_a.control(2), [ctl_up, ctl_down, *qubits])
        circuit.append(self._iphi_add_N, qubits)
        circuit.append(self._iqft, qubits)

        circuit.cx(qubits[0], ctl_aux)

        circuit.append(self._qft, qubits)
        circuit.append(self._phi_add_N, qubits)
        circuit.append(iphi_add_a.control(2), [ctl_up, ctl_down, *qubits])
        circuit.append(self._iqft, qubits)

        circuit.x(qubits[0])
        circuit.cx(qubits[0], ctl_aux)
        circuit.x(qubits[0])

        circuit.append(self._qft, qubits)
        circuit.append(phi_add_a.control(2), [ctl_up, ctl_down, *qubits])
        return circuit

    def _controlled_multiple_mod_N(self, num_qubits: int, N: int,
                                   a: int) -> Instruction:
        """Implements modular multiplication by a as an instruction."""
        circuit = QuantumCircuit(num_qubits,
                                 name="multiply_by_{}_mod_{}".format(a % N, N))
        down = circuit.qubits[1:self._n + 1]
        aux = circuit.qubits[self._n + 1:]
        qubits = [aux[i] for i in reversed(range(self._n + 1))]
        ctl_up = 0
        ctl_aux = aux[-1]

        angle_params = ParameterVector("angles", length=len(aux) - 1)
        double_controlled_phi_add = self._double_controlled_phi_add_mod_N(
            len(aux) + 2, angle_params)
        idouble_controlled_phi_add = double_controlled_phi_add.inverse()

        circuit.append(self._qft, qubits)

        # perform controlled addition by a on the aux register in Fourier space
        for i, ctl_down in enumerate(down):
            a_exp = (2**i) * a % N
            angles = self._get_angles(a_exp)
            bound = double_controlled_phi_add.assign_parameters(
                {angle_params: angles})
            circuit.append(bound, [ctl_up, ctl_down, ctl_aux, *qubits])

        circuit.append(self._iqft, qubits)

        # perform controlled subtraction by a in Fourier space on both the aux and down register
        for j in range(self._n):
            circuit.cswap(ctl_up, down[j], aux[j])
        circuit.append(self._qft, qubits)

        a_inv = self.modinv(a, N)
        for i in reversed(range(len(down))):
            a_exp = (2**i) * a_inv % N
            angles = self._get_angles(a_exp)
            bound = idouble_controlled_phi_add.assign_parameters(
                {angle_params: angles})
            circuit.append(bound, [ctl_up, down[i], ctl_aux, *qubits])

        circuit.append(self._iqft, qubits)
        return circuit.to_instruction()

    def construct_circuit(self,
                          N: int,
                          a: int = 2,
                          measurement: bool = False) -> QuantumCircuit:
        """Construct circuit.

        Args:
            N: The integer to be factored, has a min. value of 3.
            a: Any integer that satisfies 1 < a < N and gcd(a, N) = 1.
            measurement: Boolean flag to indicate if measurement should be included in the circuit.

        Returns:
            Quantum circuit.

        Raises:
            ValueError: Invalid N
        """
        validate_min('N', N, 3)
        validate_min('a', a, 2)

        # check the input integer
        if N < 1 or N % 2 == 0:
            raise ValueError(
                'The input needs to be an odd integer greater than 1.')

        if a >= N or math.gcd(a, N) != 1:
            raise ValueError(
                'The integer a needs to satisfy a < N and gcd(a, N) = 1.')

        # Get n value used in Shor's algorithm, to know how many qubits are used
        self._n = math.ceil(math.log(N, 2))
        self._qft.num_qubits = self._n + 1
        self._iqft.num_qubits = self._n + 1

        # quantum register where the sequential QFT is performed
        self._up_qreg = QuantumRegister(2 * self._n, name='up')
        # quantum register where the multiplications are made
        self._down_qreg = QuantumRegister(self._n, name='down')
        # auxiliary quantum register used in addition and multiplication
        self._aux_qreg = QuantumRegister(self._n + 2, name='aux')

        # Create Quantum Circuit
        circuit = QuantumCircuit(self._up_qreg,
                                 self._down_qreg,
                                 self._aux_qreg,
                                 name="Shor(N={}, a={})".format(N, a))

        # Create gates to perform addition/subtraction by N in Fourier Space
        self._phi_add_N = self._phi_add_gate(self._aux_qreg.size - 1,
                                             self._get_angles(N))
        self._iphi_add_N = self._phi_add_N.inverse()

        # Create maximal superposition in top register
        circuit.h(self._up_qreg)

        # Initialize down register to 1
        circuit.x(self._down_qreg[0])

        # Apply the multiplication gates as showed in
        # the report in order to create the exponentiation
        for i, ctl_up in enumerate(self._up_qreg):  # type: ignore
            a = int(pow(a, pow(2, i)))
            controlled_multiple_mod_N = self._controlled_multiple_mod_N(
                len(self._down_qreg) + len(self._aux_qreg) + 1,
                N,
                a,
            )
            circuit.append(controlled_multiple_mod_N,
                           [ctl_up, *self._down_qreg, *self._aux_qreg])

        # Apply inverse QFT
        iqft = QFT(len(self._up_qreg)).inverse().to_instruction()
        circuit.append(iqft, self._up_qreg)

        if measurement:
            up_cqreg = ClassicalRegister(2 * self._n, name='m')
            circuit.add_register(up_cqreg)
            circuit.measure(self._up_qreg, up_cqreg)

        logger.info(summarize_circuits(circuit))

        return circuit

    @staticmethod
    def modinv(a: int, m: int) -> int:
        """Returns the modular multiplicative inverse of a with respect to the modulus m."""
        def egcd(a: int, b: int) -> Tuple[int, int, int]:
            if a == 0:
                return b, 0, 1
            else:
                g, y, x = egcd(b % a, a)
                return g, x - (b // a) * y, y

        g, x, _ = egcd(a, m)
        if g != 1:
            raise ValueError(
                "The greatest common divisor of {} and {} is {}, so the "
                "modular inverse does not exist.".format(a, m, g))
        return x % m

    def _get_factors(self, N: int, a: int,
                     measurement: str) -> Optional[List[int]]:
        """Apply the continued fractions to find r and the gcd to find the desired factors."""
        x_final = int(measurement, 2)
        logger.info('In decimal, x_final value for this result is: %s.',
                    x_final)

        if x_final <= 0:
            fail_reason = 'x_final value is <= 0, there are no continued fractions.'
        else:
            fail_reason = None
            logger.debug('Running continued fractions for this case.')

        # Calculate T and x/T
        T_upper = len(measurement)
        T = pow(2, T_upper)
        x_over_T = x_final / T

        # Cycle in which each iteration corresponds to putting one more term in the
        # calculation of the Continued Fraction (CF) of x/T

        # Initialize the first values according to CF rule
        i = 0
        b = array.array('i')
        t = array.array('f')

        b.append(math.floor(x_over_T))
        t.append(x_over_T - b[i])

        exponential = 0.0
        while i < N and fail_reason is None:
            # From the 2nd iteration onwards, calculate the new terms of the CF based
            # on the previous terms as the rule suggests
            if i > 0:
                b.append(math.floor(1 / t[i - 1]))
                t.append((1 / t[i - 1]) - b[i])  # type: ignore

            # Calculate the denominator of the CF using the known terms
            denominator = self._calculate_continued_fraction(b)

            # Increment i for next iteration
            i += 1

            if denominator % 2 == 1:
                logger.debug(
                    'Odd denominator, will try next iteration of continued fractions.'
                )
                continue

            # Denominator is even, try to get factors of N
            # Get the exponential a^(r/2)

            if denominator < 1000:
                exponential = pow(a, denominator / 2)

            # Check if the value is too big or not
            if exponential > 1000000000:
                fail_reason = 'denominator of continued fraction is too big.'
            else:
                # The value is not too big,
                # get the right values and do the proper gcd()
                putting_plus = int(exponential + 1)
                putting_minus = int(exponential - 1)
                one_factor = math.gcd(putting_plus, N)
                other_factor = math.gcd(putting_minus, N)

                # Check if the factors found are trivial factors or are the desired factors
                if any([
                        factor in {1, N}
                        for factor in (one_factor, other_factor)
                ]):
                    logger.debug(
                        'Found just trivial factors, not good enough.')
                    # Check if the number has already been found,
                    # (use i - 1 because i was already incremented)
                    if t[i - 1] == 0:
                        fail_reason = 'the continued fractions found exactly x_final/(2^(2n)).'
                else:
                    # Successfully factorized N
                    return sorted((one_factor, other_factor))

        # Search for factors failed, write the reason for failure to the debug logs
        logger.debug('Cannot find factors from measurement %s because %s',
                     measurement, fail_reason or 'it took too many attempts.')
        return None

    @staticmethod
    def _calculate_continued_fraction(b: array.array) -> int:
        """Calculate the continued fraction of x/T from the current terms of expansion b."""

        x_over_T = 0

        for i in reversed(range(len(b) - 1)):
            x_over_T = 1 / (b[i + 1] + x_over_T)

        x_over_T += b[0]

        # Get the denominator from the value obtained
        frac = fractions.Fraction(x_over_T).limit_denominator()

        logger.debug('Approximation number %s of continued fractions:', len(b))
        logger.debug("Numerator:%s \t\t Denominator: %s.", frac.numerator,
                     frac.denominator)
        return frac.denominator

    def factor(
        self,
        N: int,
        a: int = 2,
    ) -> 'ShorResult':
        """Execute the algorithm.

        The input integer :math:`N` to be factored is expected to be odd and greater than 2.
        Even though this implementation is general, its capability will be limited by the
        capacity of the simulator/hardware. Another input integer :math:`a`  can also be supplied,
        which needs to be a co-prime smaller than :math:`N` .

        Args:
            N: The integer to be factored, has a min. value of 3.
            a: Any integer that satisfies 1 < a < N and gcd(a, N) = 1.

        Returns:
            ShorResult: results of the algorithm.

        Raises:
            ValueError: Invalid input
            AlgorithmError: If a quantum instance or backend has not been provided

        """
        validate_min('N', N, 3)
        validate_min('a', a, 2)

        # check the input integer
        if N < 1 or N % 2 == 0:
            raise ValueError(
                'The input needs to be an odd integer greater than 1.')

        if a >= N or math.gcd(a, N) != 1:
            raise ValueError(
                'The integer a needs to satisfy a < N and gcd(a, N) = 1.')

        if self.quantum_instance is None:
            raise AlgorithmError(
                "A QuantumInstance or Backend "
                "must be supplied to run the quantum algorithm.")

        result = ShorResult()

        # check if the input integer is a power
        tf, b, p = is_power(N, return_decomposition=True)
        if tf:
            logger.info('The input integer is a power: %s=%s^%s.', N, b, p)
            result.factors.append(b)

        if not result.factors:
            logger.debug('Running with N=%s and a=%s.', N, a)

            if self._quantum_instance.is_statevector:
                circuit = self.construct_circuit(N=N, a=a, measurement=False)
                logger.warning('The statevector_simulator might lead to '
                               'subsequent computation using too much memory.')
                result = self._quantum_instance.execute(circuit)
                complete_state_vec = result.get_statevector(circuit)
                # TODO: this uses too much memory
                up_qreg_density_mat = partial_trace(
                    complete_state_vec, range(2 * self._n, 4 * self._n + 2))
                up_qreg_density_mat_diag = np.diag(up_qreg_density_mat)

                counts = dict()
                for i, v in enumerate(up_qreg_density_mat_diag):
                    if not v == 0:
                        counts[bin(int(i))[2:].zfill(2 * self._n)] = v**2
            else:
                circuit = self.construct_circuit(N=N, a=a, measurement=True)
                counts = self._quantum_instance.execute(circuit).get_counts(
                    circuit)

            result.total_counts = len(counts)

            # For each simulation result, print proper info to user
            # and try to calculate the factors of N
            for measurement in list(counts.keys()):
                # Get the x_final value from the final state qubits
                logger.info("------> Analyzing result %s.", measurement)
                factors = self._get_factors(N, a, measurement)

                if factors:
                    logger.info('Found factors %s from measurement %s.',
                                factors, measurement)
                    result.successful_counts = result.successful_counts + 1
                    if factors not in result.factors:
                        result.factors.append(factors)

        return result
Example #12
0
class Shor(QuantumAlgorithm):
    def __init__(self,
                 N: int = 15,
                 a: int = 2,
                 quantum_instance: Optional[Union[QuantumInstance,
                                                  BaseBackend]] = None,
                 job_id=None) -> None:
        """
        Args:
            N: The integer to be factored, has a min. value of 3.
            a: A random integer that satisfies a < N and gcd(a, N) = 1, has a min. value of 2.
            quantum_instance: Quantum Instance or Backend
         Raises:
            ValueError: Invalid input
        """
        validate_min('N', N, 3)
        validate_min('a', a, 2)
        super().__init__(quantum_instance)
        self.job_id = job_id
        self._n = None
        self._up_qreg = None
        self._down_qreg = None
        self._aux_qreg = None

        # check the input integer
        if N < 1 or N % 2 == 0:
            raise ValueError(
                'The input needs to be an odd integer greater than 1.')

        self._N = N

        if a >= N or math.gcd(a, self._N) != 1:
            raise ValueError(
                'The integer a needs to satisfy a < N and gcd(a, N) = 1.')

        self._a = a

        self._ret = {'factors': []}

        # check if the input integer is a power
        tf, b, p = is_power(N, return_decomposition=True)
        if tf:
            logger.info('The input integer is a power: %s=%s^%s.', N, b, p)
            self._ret['factors'].append(b)

        self._qft = QFT(do_swaps=False)
        self._iqft = self._qft.inverse()

    def _get_angles(self, a):
        """Calculate the array of angles to be used in the addition in Fourier Space."""
        s = bin(int(a))[2:].zfill(self._n + 1)
        angles = np.zeros([self._n + 1])
        for i in range(0, self._n + 1):
            for j in range(i, self._n + 1):
                if s[j] == '1':
                    angles[self._n - i] += math.pow(2, -(j - i))
            angles[self._n - i] *= np.pi
        return angles

    def _phi_add(self, circuit, q, inverse=False):
        """Creation of the circuit that performs addition by a in Fourier Space.
        Can also be used for subtraction by setting the parameter ``inverse=True``.
        """
        angle = self._get_angles(self._N)
        for i in range(0, self._n + 1):
            circuit.u1(-angle[i] if inverse else angle[i], q[i])

    def _controlled_phi_add(self, circuit, q, ctl, inverse=False):
        """Single controlled version of the _phi_add circuit."""
        angles = self._get_angles(self._N)
        for i in range(0, self._n + 1):
            angle = (-angles[i] if inverse else angles[i]) / 2

            circuit.u1(angle, ctl)
            circuit.cx(ctl, q[i])
            circuit.u1(-angle, q[i])
            circuit.cx(ctl, q[i])
            circuit.u1(angle, q[i])

    def _controlled_controlled_phi_add(self,
                                       circuit,
                                       q,
                                       ctl1,
                                       ctl2,
                                       a,
                                       inverse=False):
        """Doubly controlled version of the _phi_add circuit."""
        angle = self._get_angles(a)
        for i in range(self._n + 1):
            # ccphase(circuit, -angle[i] if inverse else angle[i], ctl1, ctl2, q[i])
            circuit.mcu1(-angle[i] if inverse else angle[i], [ctl1, ctl2],
                         q[i])

    def _controlled_controlled_phi_add_mod_N_tdag(self, q, ctl1, ctl2, aux, a):
        """Circuit that implements doubly controlled modular addition by a."""
        qubits = [q[i] for i in reversed(range(self._n + 1))]

        tdags = []

        tmp0_ckt = QuantumCircuit(self._up_qreg, self._down_qreg,
                                  self._aux_qreg)
        self._controlled_controlled_phi_add(tmp0_ckt, q, ctl1, ctl2, a)
        self._phi_add(tmp0_ckt, q, inverse=True)
        tmp0_ckt_tdag = circuit_to_tdag(tmp0_ckt)
        tdags.append(tmp0_ckt_tdag)

        tdags.append(TaggedDAG(self._iqft_dag, qubits=qubits))

        tmp1_ckt = QuantumCircuit(self._up_qreg, self._down_qreg,
                                  self._aux_qreg)
        tmp1_ckt.cx(q[self._n], aux)
        tmp1_ckt_tdag = circuit_to_tdag(tmp1_ckt, qubits=qubits)
        tdags.append(tmp1_ckt_tdag)

        tdags.append(TaggedDAG(self._qft_dag, qubits=qubits))

        tmp2_ckt = QuantumCircuit(self._up_qreg, self._down_qreg,
                                  self._aux_qreg)
        self._controlled_phi_add(tmp2_ckt, q, aux)
        self._controlled_controlled_phi_add(tmp2_ckt,
                                            q,
                                            ctl1,
                                            ctl2,
                                            a,
                                            inverse=True)
        tmp2_ckt_tdag = circuit_to_tdag(tmp2_ckt)
        tdags.append(tmp2_ckt_tdag)

        tdags.append(TaggedDAG(self._iqft_dag, qubits=qubits))

        tmp3_ckt = QuantumCircuit(self._up_qreg, self._down_qreg,
                                  self._aux_qreg)
        tmp3_ckt.u3(np.pi, 0, np.pi, q[self._n])
        tmp3_ckt.cx(q[self._n], aux)
        tmp3_ckt.u3(np.pi, 0, np.pi, q[self._n])
        tmp3_ckt_tdag = circuit_to_tdag(tmp3_ckt, qubits=qubits)
        tdags.append(tmp3_ckt_tdag)

        tdags.append(TaggedDAG(self._qft_dag, qubits=qubits))

        tmp4_ckt = QuantumCircuit(self._up_qreg, self._down_qreg,
                                  self._aux_qreg)
        self._controlled_controlled_phi_add(tmp4_ckt, q, ctl1, ctl2, a)
        tmp4_ckt_tdag = circuit_to_tdag(tmp4_ckt)
        tdags.append(tmp4_ckt_tdag)

        return tdags

    def _controlled_controlled_phi_add_mod_N_inv_tdag(self, q, ctl1, ctl2, aux,
                                                      a):
        """Circuit that implements the inverse of doubly controlled modular addition by a."""
        qubits = [q[i] for i in reversed(range(self._n + 1))]

        tdags = []

        tmp0_ckt = QuantumCircuit(self._up_qreg, self._down_qreg,
                                  self._aux_qreg)
        self._controlled_controlled_phi_add(tmp0_ckt,
                                            q,
                                            ctl1,
                                            ctl2,
                                            a,
                                            inverse=True)
        tmp0_ckt_tdag = circuit_to_tdag(tmp0_ckt)
        tdags.append(tmp0_ckt_tdag)

        tdags.append(TaggedDAG(self._iqft_dag, qubits=qubits))

        u3_cx_u3_ckt = QuantumCircuit(self._up_qreg, self._down_qreg,
                                      self._aux_qreg)
        u3_cx_u3_ckt.u3(np.pi, 0, np.pi, q[self._n])
        u3_cx_u3_ckt.cx(q[self._n], aux)
        u3_cx_u3_ckt.u3(np.pi, 0, np.pi, q[self._n])
        u3_cx_u3_ckt_dag = circuit_to_tdag(u3_cx_u3_ckt, qubits=qubits)
        tdags.append(u3_cx_u3_ckt_dag)

        tdags.append(TaggedDAG(self._qft_dag, qubits=qubits))

        tmp2_ckt = QuantumCircuit(self._up_qreg, self._down_qreg,
                                  self._aux_qreg)
        self._controlled_controlled_phi_add(tmp2_ckt, q, ctl1, ctl2, a)
        self._controlled_phi_add(tmp2_ckt, q, aux, inverse=True)
        tmp2_ckt_tdag = circuit_to_tdag(tmp2_ckt)
        tdags.append(tmp2_ckt_tdag)

        tdags.append(TaggedDAG(self._iqft_dag, qubits=qubits))

        cx_ckt = QuantumCircuit(self._up_qreg, self._down_qreg, self._aux_qreg)
        cx_ckt.cx(q[self._n], aux)
        cx_ckt_tdag = circuit_to_tdag(cx_ckt, qubits=qubits)
        tdags.append(cx_ckt_tdag)

        tdags.append(TaggedDAG(self._qft_dag, qubits=qubits))

        tmp4_ckt = QuantumCircuit(self._up_qreg, self._down_qreg,
                                  self._aux_qreg)
        self._phi_add(tmp4_ckt, q)
        self._controlled_controlled_phi_add(tmp4_ckt,
                                            q,
                                            ctl1,
                                            ctl2,
                                            a,
                                            inverse=True)
        tmp4_ckt_tdag = circuit_to_tdag(tmp4_ckt)
        tdags.append(tmp4_ckt_tdag)

        return tdags

    def _controlled_multiple_mod_N_tdags(self, ctl, q, aux, a):
        """Circuit that implements single controlled modular multiplication by a."""
        qubits = [aux[i] for i in reversed(range(self._n + 1))]

        tdags = []

        tdags.append(TaggedDAG(self._qft_dag, qubits=qubits))

        for i in range(0, self._n):
            tdags += self._controlled_controlled_phi_add_mod_N_tdag(
                aux, q[i], ctl, aux[self._n + 1], (2**i) * a % self._N)

        tdags.append(TaggedDAG(self._iqft_dag, qubits=qubits))

        cswap_ckt = QuantumCircuit(self._up_qreg, self._down_qreg,
                                   self._aux_qreg)
        for i in range(0, self._n):
            cswap_ckt.cswap(ctl, q[i], aux[i])
        cswap_ckt_tdag = circuit_to_tdag(cswap_ckt)
        tdags.append(cswap_ckt_tdag)

        def modinv(a, m):
            def egcd(a, b):
                if a == 0:
                    return (b, 0, 1)
                else:
                    g, y, x = egcd(b % a, a)
                    return (g, x - (b // a) * y, y)

            g, x, _ = egcd(a, m)
            if g != 1:
                raise Exception('modular inverse does not exist')

            return x % m

        tdags.append(TaggedDAG(self._qft_dag, qubits=qubits))

        a_inv = modinv(a, self._N)
        for i in reversed(range(self._n)):
            tdags += self._controlled_controlled_phi_add_mod_N_inv_tdag(
                aux, q[i], ctl, aux[self._n + 1],
                math.pow(2, i) * a_inv % self._N)

        tdags.append(TaggedDAG(self._iqft_dag, qubits=qubits))

        return tdags

    def construct_circuit(self, measurement: bool = False) -> QuantumCircuit:
        """Construct circuit.
        Args:
            measurement: Boolean flag to indicate if measurement should be included in the circuit.
        Returns:
            Quantum circuit.
        """

        # Get n value used in Shor's algorithm, to know how many qubits are used
        self._n = math.ceil(math.log(self._N, 2))
        self._qft.num_qubits = self._n + 1
        self._iqft.num_qubits = self._n + 1
        self._qft_dag = circuit_to_dag(self._qft)
        self._iqft_dag = circuit_to_dag(self._iqft)

        # quantum register where the sequential QFT is performed
        self._up_qreg = QuantumRegister(2 * self._n, name='up')
        # quantum register where the multiplications are made
        self._down_qreg = QuantumRegister(self._n, name='down')
        # auxiliary quantum register used in addition and multiplication
        self._aux_qreg = QuantumRegister(self._n + 2, name='aux')

        # Create Quantum Circuit
        circuit = QuantumCircuit(self._up_qreg, self._down_qreg,
                                 self._aux_qreg)

        # Initialize down register to 1 and create maximal superposition in top register
        circuit.u2(0, np.pi, self._up_qreg)
        circuit.u3(np.pi, 0, np.pi, self._down_qreg[0])

        tdags = []
        dag_self = circuit_to_dag(circuit)

        # Apply the multiplication gates as showed in
        # the report in order to create the exponentiation
        for i in range(0, 2 * self._n):
            tdags += self._controlled_multiple_mod_N_tdags(
                self._up_qreg[i], self._down_qreg, self._aux_qreg,
                int(pow(self._a, pow(2, i))))

        for tdag in tdags:
            dag_compose_with_tagged(dag_self, tdag)
        composed_circuit = dag_to_circuit(dag_self)
        circuit.__dict__.update(composed_circuit.__dict__)

        # Apply inverse QFT
        iqft = QFT(len(self._up_qreg), inverse=True)
        circuit.compose(iqft, qubits=self._up_qreg)

        if measurement:
            up_cqreg = ClassicalRegister(2 * self._n, name='m')
            circuit.add_register(up_cqreg)
            circuit.measure(self._up_qreg, up_cqreg)

        logger.info(summarize_circuits(circuit))

        return circuit

    def _get_factors(self, output_desired, t_upper):
        """Apply the continued fractions to find r and the gcd to find the desired factors."""
        x_value = int(output_desired, 2)
        logger.info('In decimal, x_final value for this result is: %s.',
                    x_value)

        if x_value <= 0:
            self._ret['results'][output_desired] = \
                'x_value is <= 0, there are no continued fractions.'
            return False

        logger.debug('Running continued fractions for this case.')

        # Calculate T and x/T
        T = pow(2, t_upper)
        x_over_T = x_value / T

        # Cycle in which each iteration corresponds to putting one more term in the
        # calculation of the Continued Fraction (CF) of x/T

        # Initialize the first values according to CF rule
        i = 0
        b = array.array('i')
        t = array.array('f')

        b.append(math.floor(x_over_T))
        t.append(x_over_T - b[i])

        while i >= 0:

            # From the 2nd iteration onwards, calculate the new terms of the CF based
            # on the previous terms as the rule suggests
            if i > 0:
                b.append(math.floor(1 / t[i - 1]))
                t.append((1 / t[i - 1]) - b[i])

            # Calculate the CF using the known terms
            aux = 0
            j = i
            while j > 0:
                aux = 1 / (b[j] + aux)
                j = j - 1

            aux = aux + b[0]

            # Get the denominator from the value obtained
            frac = fractions.Fraction(aux).limit_denominator()
            denominator = frac.denominator

            logger.debug('Approximation number %s of continued fractions:',
                         i + 1)
            logger.debug("Numerator:%s \t\t Denominator: %s.", frac.numerator,
                         frac.denominator)

            # Increment i for next iteration
            i = i + 1

            if denominator % 2 == 1:
                if i >= self._N:
                    self._ret['results'][output_desired] = \
                        'unable to find factors after too many attempts.'
                    return False
                logger.debug(
                    'Odd denominator, will try next iteration of continued fractions.'
                )
                continue

            # If denominator even, try to get factors of N
            # Get the exponential a^(r/2)
            exponential = 0

            if denominator < 1000:
                exponential = pow(self._a, denominator / 2)

            # Check if the value is too big or not
            if math.isinf(exponential) or exponential > 1000000000:
                self._ret['results'][output_desired] = \
                    'denominator of continued fraction is too big.'
                return False

            # If the value is not to big (infinity),
            # then get the right values and do the proper gcd()
            putting_plus = int(exponential + 1)
            putting_minus = int(exponential - 1)
            one_factor = math.gcd(putting_plus, self._N)
            other_factor = math.gcd(putting_minus, self._N)

            # Check if the factors found are trivial factors or are the desired factors
            if one_factor == 1 or one_factor == self._N or \
                    other_factor == 1 or other_factor == self._N:
                logger.debug('Found just trivial factors, not good enough.')
                # Check if the number has already been found,
                # use i-1 because i was already incremented
                if t[i - 1] == 0:
                    self._ret['results'][output_desired] = \
                        'the continued fractions found exactly x_final/(2^(2n)).'
                    return False
                if i >= self._N:
                    self._ret['results'][output_desired] = \
                        'unable to find factors after too many attempts.'
                    return False
            else:
                logger.debug('The factors of %s are %s and %s.', self._N,
                             one_factor, other_factor)
                logger.debug('Found the desired factors.')
                self._ret['results'][output_desired] = (one_factor,
                                                        other_factor)
                factors = sorted((one_factor, other_factor))
                if factors not in self._ret['factors']:
                    self._ret['factors'].append(factors)
                return True

    def _run(self):
        if not self._ret['factors']:
            logger.debug('Running with N=%s and a=%s.', self._N, self._a)

            assert not self._quantum_instance.is_statevector
            circuit = self.construct_circuit(measurement=True)
            if (self.job_id is None) or ("retrieve_job" not in dir(
                    self._quantum_instance._backend)):
                counts = self._quantum_instance.execute(circuit).get_counts(
                    circuit)
            else:
                counts = self._quantum_instance._backend.retrieve_job(
                    self.job_id).result().get_counts(circuit)

            self._ret['results'] = dict()

            # For each simulation result, print proper info to user
            # and try to calculate the factors of N
            for output_desired in list(counts.keys()):
                # Get the x_value from the final state qubits
                logger.info("------> Analyzing result %s.", output_desired)
                self._ret['results'][output_desired] = None
                success = self._get_factors(output_desired, int(2 * self._n))
                if success:
                    logger.info('Found factors %s from measurement %s.',
                                self._ret['results'][output_desired],
                                output_desired)
                else:
                    logger.info(
                        'Cannot find factors from measurement %s because %s',
                        output_desired, self._ret['results'][output_desired])

        return self._ret