def test_to_permutation(self): """Test that converting linear functions to permutations works correctly.""" # Original circuit with two linear blocks; the second block happens to be # a permutation circuit1 = QuantumCircuit(4) circuit1.cx(0, 1) circuit1.cx(0, 2) circuit1.cx(0, 3) circuit1.h(3) circuit1.swap(2, 3) circuit1.cx(1, 2) circuit1.cx(2, 1) circuit1.cx(1, 2) # new circuit with linear functions extracted circuit2 = PassManager(CollectLinearFunctions()).run(circuit1) # check that we have two blocks of linear functions self.assertEqual(circuit2.count_ops()["linear_function"], 2) # new circuit with linear functions converted to permutations # (the first linear function should not be converted, the second should) circuit3 = PassManager(LinearFunctionsToPermutations()).run(circuit2) # check that there is one linear function and one permutation self.assertEqual(circuit3.count_ops()["linear_function"], 1) self.assertEqual(circuit3.count_ops()["permutation_[2,0,1]"], 1) # check that the final circuit is still equivalent to the original circuit self.assertEqual(Operator(circuit1), Operator(circuit3))
def test_real_amplitudes_circuit_5q(self): """Test that for the 5-qubit real amplitudes circuit extracting linear functions produces the expected number of linear blocks, and synthesizing these blocks produces an expected number of CNOTs. """ ansatz = RealAmplitudes(5, reps=2) circuit1 = ansatz.decompose() # collect linear functions circuit2 = PassManager(CollectLinearFunctions()).run(circuit1) self.assertEqual(circuit2.count_ops()["linear_function"], 2) # synthesize linear functions circuit3 = PassManager(LinearFunctionsSynthesis()).run(circuit2) self.assertEqual(circuit3.count_ops()["cx"], 8)
def test_disconnected_gates2(self): """Test that extraction of linear functions does not create linear functions out of disconnected gates. """ circuit1 = QuantumCircuit(4) circuit1.cx(0, 1) circuit1.cx(1, 0) circuit1.cx(2, 3) # collect linear functions circuit2 = PassManager(CollectLinearFunctions()).run(circuit1) # we expect that the first two CX gates will be combined into # a linear function, but the last will not self.assertEqual(circuit2.count_ops()["linear_function"], 1) self.assertEqual(circuit2.count_ops()["cx"], 1)
def test_optimize_cliffords(self): """Test OptimizeCliffords pass.""" rng = np.random.default_rng(1234) for _ in range(20): # Create several random Cliffords cliffs = [random_clifford(3, rng) for _ in range(5)] # The first circuit contains these cliffords qc1 = QuantumCircuit(5) for cliff in cliffs: qc1.append(cliff, [4, 0, 2]) self.assertEqual(qc1.count_ops()["clifford"], 5) # The second circuit is obtained by running the OptimizeCliffords pass. qc2 = PassManager(OptimizeCliffords()).run(qc1) self.assertEqual(qc2.count_ops()["clifford"], 1) # The third circuit contains the decompositions of Cliffods. qc3 = QuantumCircuit(5) for cliff in cliffs: qc3.append(cliff.to_circuit(), [4, 0, 2]) self.assertNotIn("clifford", qc3.count_ops()) # Check that qc1, qc2 and qc3 and their decompositions are all equivalent. self.assertTrue(Operator(qc1).equiv(Operator(qc1.decompose()))) self.assertTrue(Operator(qc2).equiv(Operator(qc2.decompose()))) self.assertTrue(Operator(qc3).equiv(Operator(qc3.decompose()))) self.assertTrue(Operator(qc1).equiv(Operator(qc2))) self.assertTrue(Operator(qc1).equiv(Operator(qc3)))
def test_can_combine_cliffords(self): """Test that we can combine a pair of Cliffords over the same qubits using OptimizeCliffords transpiler pass.""" cliff1 = self.create_cliff1() cliff2 = self.create_cliff2() cliff3 = self.create_cliff3() # Create a circuit with two consective cliffords qc1 = QuantumCircuit(4) qc1.append(cliff1, [3, 1, 2]) qc1.append(cliff2, [3, 1, 2]) self.assertEqual(qc1.count_ops()["clifford"], 2) # Run OptimizeCliffords pass, and check that only one Clifford remains qc1opt = PassManager(OptimizeCliffords()).run(qc1) self.assertEqual(qc1opt.count_ops()["clifford"], 1) # Create the expected circuit qc2 = QuantumCircuit(4) qc2.append(cliff3, [3, 1, 2]) # Check that all possible operators are equal self.assertTrue(Operator(qc1).equiv(Operator(qc1.decompose()))) self.assertTrue(Operator(qc1opt).equiv(Operator(qc1opt.decompose()))) self.assertTrue(Operator(qc1).equiv(Operator(qc1opt))) self.assertTrue(Operator(qc2).equiv(Operator(qc2.decompose()))) self.assertTrue(Operator(qc1opt).equiv(Operator(qc2)))
def test_connected_gates(self): """Test that extraction of linear functions combines gates which become connected later. """ circuit1 = QuantumCircuit(4) circuit1.cx(0, 1) circuit1.cx(1, 0) circuit1.cx(2, 3) circuit1.swap(0, 3) # collect linear functions circuit2 = PassManager(CollectLinearFunctions()).run(circuit1) # we expect that the first two CX gates will be combined into # a linear function, but the last will not self.assertEqual(circuit2.count_ops()["linear_function"], 1) self.assertNotIn("cx", circuit2.count_ops().keys()) self.assertNotIn("swap", circuit2.count_ops().keys())
def test_two_linear_blocks(self): """Test that when we have two blocks of linear gates with one nonlinear gate in the middle, we end up with two LinearFunctions.""" # Create a circuit with a nonlinear gate (h) cleanly separating it into two linear blocks. circuit1 = QuantumCircuit(4) circuit1.cx(0, 1) circuit1.cx(0, 2) circuit1.cx(0, 3) circuit1.h(3) circuit1.swap(2, 3) circuit1.cx(1, 2) circuit1.cx(0, 1) # new circuit with linear functions extracted using transpiler pass circuit2 = PassManager(CollectLinearFunctions()).run(circuit1) # We expect to see 3 gates (linear, h, linear) self.assertEqual(len(circuit2.data), 3) inst1, qargs1, cargs1 = circuit2.data[0] inst2, qargs2, cargs2 = circuit2.data[2] self.assertIsInstance(inst1, LinearFunction) self.assertIsInstance(inst2, LinearFunction) # Check that the first linear function represents the subcircuit before h resulting_subcircuit1 = QuantumCircuit(4) resulting_subcircuit1.append(inst1, qargs1, cargs1) expected_subcircuit1 = QuantumCircuit(4) expected_subcircuit1.cx(0, 1) expected_subcircuit1.cx(0, 2) expected_subcircuit1.cx(0, 3) self.assertEqual(Operator(resulting_subcircuit1), Operator(expected_subcircuit1)) # Check that the second linear function represents the subcircuit after h resulting_subcircuit2 = QuantumCircuit(4) resulting_subcircuit2.append(inst2, qargs2, cargs2) expected_subcircuit2 = QuantumCircuit(4) expected_subcircuit2.swap(2, 3) expected_subcircuit2.cx(1, 2) expected_subcircuit2.cx(0, 1) self.assertEqual(Operator(resulting_subcircuit2), Operator(expected_subcircuit2)) # now a circuit with linear functions synthesized synthesized_circuit = PassManager( LinearFunctionsSynthesis()).run(circuit2) # check that there are no LinearFunctions present in synthesized_circuit self.assertNotIn("linear_function", synthesized_circuit.count_ops().keys()) # check that we have an equivalent circuit self.assertEqual(Operator(circuit2), Operator(synthesized_circuit))
def test_single_linear_block(self): """Test that when all gates in a circuit are either CX or SWAP, we end up with a single LinearFunction.""" # original circuit circuit = QuantumCircuit(4) circuit.cx(0, 1) circuit.cx(0, 2) circuit.cx(0, 3) circuit.swap(2, 3) circuit.cx(0, 1) circuit.cx(0, 3) # new circuit with linear functions extracted using transpiler pass optimized_circuit = PassManager(CollectLinearFunctions()).run(circuit) # check that this circuit consists of a single LinearFunction self.assertIn("linear_function", optimized_circuit.count_ops().keys()) self.assertEqual(len(optimized_circuit.data), 1) inst1, _, _ = optimized_circuit.data[0] self.assertIsInstance(inst1, LinearFunction) # construct a circuit with linear function directly, without the transpiler pass expected_circuit = QuantumCircuit(4) expected_circuit.append(LinearFunction(circuit), [0, 1, 2, 3]) # check that we have an equivalent circuit self.assertEqual(Operator(optimized_circuit), Operator(expected_circuit)) # now a circuit with linear functions synthesized synthesized_circuit = PassManager( LinearFunctionsSynthesis()).run(optimized_circuit) # check that there are no LinearFunctions present in synthesized_circuit self.assertNotIn("linear_function", synthesized_circuit.count_ops().keys()) # check that we have an equivalent circuit self.assertEqual(Operator(optimized_circuit), Operator(synthesized_circuit))
def test_disconnected_gates1(self): """Test that extraction of linear functions does not create linear functions out of disconnected gates. """ circuit1 = QuantumCircuit(4) circuit1.cx(0, 1) circuit1.cx(2, 3) # collect linear functions circuit2 = PassManager(CollectLinearFunctions()).run(circuit1) # check that there are no LinearFunctions present in synthesized_circuit self.assertNotIn("linear_function", circuit2.count_ops().keys())
def test_accept_dagdependency(self): """ Check that users can supply DAGDependency in the template list. """ circuit_in = QuantumCircuit(2) circuit_in.cnot(0, 1) circuit_in.cnot(0, 1) templates = [circuit_to_dagdependency(circuit_in)] pass_ = TemplateOptimization(template_list=templates) circuit_out = PassManager(pass_).run(circuit_in) self.assertEqual(circuit_out.count_ops().get('cx', 0), 0)
def test_topological_ordering(self): """Test that Clifford optimization pass optimizes Cliffords across a gate on a different qubit.""" cliff1 = self.create_cliff1() cliff2 = self.create_cliff1() qc1 = QuantumCircuit(5) qc1.append(cliff1, [0, 1, 2]) qc1.h(4) qc1.append(cliff2, [0, 1, 2]) # The second circuit is obtained by running the OptimizeCliffords pass. qc2 = PassManager(OptimizeCliffords()).run(qc1) self.assertEqual(qc2.count_ops()["clifford"], 1)
def test_not_collecting_single_gates2(self): """Test that extraction of linear functions does not create linear functions out of single gates. """ circuit1 = QuantumCircuit(3) circuit1.h(0) circuit1.h(1) circuit1.swap(0, 1) circuit1.s(1) circuit1.swap(1, 2) circuit1.h(2) # collect linear functions circuit2 = PassManager(CollectLinearFunctions()).run(circuit1) # check that there are no LinearFunctions present in synthesized_circuit self.assertNotIn("linear_function", circuit2.count_ops().keys())
def test_unbound_parameters(self): """ Test that partial matches with parameters will not raise errors. This tests that if parameters are still in the temporary template after _attempt_bind then they will not be used. """ class PhaseSwap(Gate): """CZ gates used for the test.""" def __init__(self, num_qubits, params): super().__init__("p", num_qubits, params) def inverse(self): inverse = UnitaryGate( np.diag( [1.0, 1.0, np.exp(-1.0j * self.params[0]), np.exp(-1.0j * self.params[0])] ) ) inverse.name = "p" return inverse def template(): beta = Parameter("β") qc = QuantumCircuit(2) qc.cx(1, 0) qc.cx(1, 0) qc.p(beta, 1) qc.append(PhaseSwap(2, [beta]), [0, 1]) return qc circuit_in = QuantumCircuit(2) circuit_in.cx(1, 0) circuit_in.cx(1, 0) pass_ = TemplateOptimization(template_list=[template()]) circuit_out = PassManager(pass_).run(circuit_in) # This template will not fully match as long as gates with parameters do not # commute with any other gates in the DAG dependency. self.assertEqual(circuit_out.count_ops().get("cx", 0), 2)