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)
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())
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)
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()
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)
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()
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)
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()
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
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