Skip to content

Commit

Permalink
Merge branch 'main' into move-equivalence
Browse files Browse the repository at this point in the history
  • Loading branch information
raynelfss authored Jun 27, 2024
2 parents 48bb8eb + 76af5b4 commit ffa0a81
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 26 deletions.
13 changes: 13 additions & 0 deletions crates/circuit/src/gate_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ const fn c64(re: f64, im: f64) -> Complex64 {
pub static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] =
[[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(1., 0.)]];

#[inline]
pub fn r_gate(theta: f64, phi: f64) -> [[Complex64; 2]; 2] {
let half_theta = theta / 2.;
let cost = c64(half_theta.cos(), 0.);
let sint = half_theta.sin();
let cosphi = phi.cos();
let sinphi = phi.sin();
[
[cost, c64(-sint * sinphi, -sint * cosphi)],
[c64(sint * sinphi, -sint * cosphi), cost],
]
}

#[inline]
pub fn rx_gate(theta: f64) -> [[Complex64; 2]; 2] {
let half_theta = theta / 2.;
Expand Down
2 changes: 1 addition & 1 deletion crates/circuit/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [
// CRZGate = 31
["placeholder", "placeholder"],
// RGate 32
["placeholder", "placeholder"],
["qiskit.circuit.library.standard_gates.r", "RGate"],
// CHGate = 33
["qiskit.circuit.library.standard_gates.h", "CHGate"],
// CPhaseGate = 34
Expand Down
52 changes: 47 additions & 5 deletions crates/circuit/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [
1, 1, 1, 2, 2, 2, 3, 1, 1, 1, // 0-9
2, 2, 1, 0, 1, 1, 1, 1, 1, 1, // 10-19
1, 1, 1, 2, 2, 2, 1, 1, 1, 34, // 20-29
34, 34, 34, 2, 2, 2, 2, 2, 3, 2, // 30-39
34, 34, 1, 2, 2, 2, 2, 2, 3, 2, // 30-39
2, 2, 34, 34, 34, 2, 34, 34, 34, 34, // 40-49
34, 34, 34, // 50-52
];
Expand All @@ -286,7 +286,7 @@ static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, // 0-9
0, 0, 0, 1, 0, 0, 1, 3, 0, 0, // 10-19
0, 0, 0, 0, 2, 2, 1, 2, 3, 34, // 20-29
34, 34, 34, 0, 1, 0, 0, 0, 0, 3, // 30-39
34, 34, 2, 0, 1, 0, 0, 0, 0, 3, // 30-39
1, 3, 34, 34, 34, 0, 34, 34, 34, 34, // 40-49
34, 34, 34, // 50-52
];
Expand Down Expand Up @@ -551,7 +551,12 @@ impl Operation for StandardGate {
_ => None,
},
Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(),
Self::RGate => todo!(),
Self::RGate => match params {
[Param::Float(theta), Param::Float(phi)] => {
Some(aview2(&gate_matrix::r_gate(*theta, *phi)).to_owned())
}
_ => None,
},
Self::CHGate => todo!(),
Self::CPhaseGate => todo!(),
Self::CSGate => todo!(),
Expand Down Expand Up @@ -994,7 +999,21 @@ impl Operation for StandardGate {
)
}),
Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(),
Self::RGate => todo!(),
Self::RGate => Python::with_gil(|py| -> Option<CircuitData> {
let theta_expr = clone_param(&params[0], py);
let phi_expr1 = add_param(&params[1], -PI2, py);
let phi_expr2 = multiply_param(&phi_expr1, -1.0, py);
let defparams = smallvec![theta_expr, phi_expr1, phi_expr2];
Some(
CircuitData::from_standard_gates(
py,
1,
[(Self::UGate, defparams, smallvec![Qubit(0)])],
FLOAT_ZERO,
)
.expect("Unexpected Qiskit python bug"),
)
}),
Self::CHGate => todo!(),
Self::CPhaseGate => todo!(),
Self::CSGate => todo!(),
Expand Down Expand Up @@ -1034,14 +1053,37 @@ impl Operation for StandardGate {

const FLOAT_ZERO: Param = Param::Float(0.0);

// Return explictly requested copy of `param`, handling
// each variant separately.
fn clone_param(param: &Param, py: Python) -> Param {
match param {
Param::Float(theta) => Param::Float(*theta),
Param::ParameterExpression(theta) => Param::ParameterExpression(theta.clone_ref(py)),
Param::Obj(_) => unreachable!(),
}
}

fn multiply_param(param: &Param, mult: f64, py: Python) -> Param {
match param {
Param::Float(theta) => Param::Float(*theta * mult),
Param::ParameterExpression(theta) => Param::ParameterExpression(
theta
.clone_ref(py)
.call_method1(py, intern!(py, "__rmul__"), (mult,))
.expect("Parameter expression for global phase failed"),
.expect("Multiplication of Parameter expression by float failed."),
),
Param::Obj(_) => unreachable!(),
}
}

fn add_param(param: &Param, summand: f64, py: Python) -> Param {
match param {
Param::Float(theta) => Param::Float(*theta + summand),
Param::ParameterExpression(theta) => Param::ParameterExpression(
theta
.clone_ref(py)
.call_method1(py, intern!(py, "__add__"), (summand,))
.expect("Sum of Parameter expression and float failed."),
),
Param::Obj(_) => unreachable!(),
}
Expand Down
3 changes: 3 additions & 0 deletions qiskit/circuit/library/standard_gates/r.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from qiskit.circuit.gate import Gate
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.parameterexpression import ParameterValueType
from qiskit._accelerate.circuit import StandardGate


class RGate(Gate):
Expand Down Expand Up @@ -49,6 +50,8 @@ class RGate(Gate):
\end{pmatrix}
"""

_standard_gate = StandardGate.RGate

def __init__(
self,
theta: ParameterValueType,
Expand Down
8 changes: 3 additions & 5 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4649,9 +4649,7 @@ def r(
Returns:
A handle to the instructions created.
"""
from .library.standard_gates.r import RGate

return self.append(RGate(theta, phi), [qubit], [], copy=False)
return self._append_standard_gate(StandardGate.RGate, [theta, phi], qargs=[qubit])

def rv(
self,
Expand Down Expand Up @@ -5404,12 +5402,12 @@ def mcx(
ValueError: if the given mode is not known, or if too few ancilla qubits are passed.
AttributeError: if no ancilla qubits are passed, but some are needed.
"""
from .library.standard_gates.x import MCXGrayCode, MCXRecursive, MCXVChain
from .library.standard_gates.x import MCXGate, MCXRecursive, MCXVChain

num_ctrl_qubits = len(control_qubits)

available_implementations = {
"noancilla": MCXGrayCode(num_ctrl_qubits, ctrl_state=ctrl_state),
"noancilla": MCXGate(num_ctrl_qubits, ctrl_state=ctrl_state),
"recursion": MCXRecursive(num_ctrl_qubits, ctrl_state=ctrl_state),
"v-chain": MCXVChain(num_ctrl_qubits, False, ctrl_state=ctrl_state),
"v-chain-dirty": MCXVChain(num_ctrl_qubits, dirty_ancillas=True, ctrl_state=ctrl_state),
Expand Down
6 changes: 6 additions & 0 deletions releasenotes/notes/fix-mcx-performance-de86bcc9f969b81e.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
Improve the decomposition of the gate generated by :meth:`.QuantumCircuit.mcx`
without using ancilla qubits, so that the number of :class:`.CXGate` will grow
quadratically in the number of qubits and not exponentially.
12 changes: 7 additions & 5 deletions test/python/circuit/test_circuit_qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,14 @@ def test_circuit_qasm_with_mcx_gate(self):

# qasm output doesn't support parameterized gate yet.
# param0 for "gate mcuq(param0) is not used inside the definition
expected_qasm = """OPENQASM 2.0;
pattern = r"""OPENQASM 2.0;
include "qelib1.inc";
gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; }
qreg q[4];
mcx q[0],q[1],q[2],q[3];"""
self.assertEqual(dumps(qc), expected_qasm)
gate mcx q0,q1,q2,q3 { h q3; p\(pi/8\) q0; p\(pi/8\) q1; p\(pi/8\) q2; p\(pi/8\) q3; cx q0,q1; p\(-pi/8\) q1; cx q0,q1; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; p\(pi/8\) q2; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; h q3; }
gate (?P<mcx_id>mcx_[0-9]*) q0,q1,q2,q3 { mcx q0,q1,q2,q3; }
qreg q\[4\];
(?P=mcx_id) q\[0\],q\[1\],q\[2\],q\[3\];"""
expected_qasm = re.compile(pattern, re.MULTILINE)
self.assertRegex(dumps(qc), expected_qasm)

def test_circuit_qasm_with_mcx_gate_variants(self):
"""Test circuit qasm() method with MCXGrayCode, MCXRecursive, MCXVChain"""
Expand Down
4 changes: 2 additions & 2 deletions test/python/circuit/test_controlled_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -764,9 +764,9 @@ def test_small_mcx_gates_yield_cx_count(self, num_ctrl_qubits):

@data(1, 2, 3, 4)
def test_mcxgraycode_gates_yield_explicit_gates(self, num_ctrl_qubits):
"""Test creating an mcx gate calls MCXGrayCode and yeilds explicit definition."""
"""Test an MCXGrayCode yields explicit definition."""
qc = QuantumCircuit(num_ctrl_qubits + 1)
qc.mcx(list(range(num_ctrl_qubits)), [num_ctrl_qubits])
qc.append(MCXGrayCode(num_ctrl_qubits), list(range(qc.num_qubits)), [])
explicit = {1: CXGate, 2: CCXGate, 3: C3XGate, 4: C4XGate}
self.assertEqual(type(qc[0].operation), explicit[num_ctrl_qubits])

Expand Down
13 changes: 7 additions & 6 deletions test/python/qasm2/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,13 +387,14 @@ def test_mcx_gate(self):

# qasm output doesn't support parameterized gate yet.
# param0 for "gate mcuq(param0) is not used inside the definition
expected_qasm = """\
OPENQASM 2.0;
pattern = r"""OPENQASM 2.0;
include "qelib1.inc";
gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; }
qreg q[4];
mcx q[0],q[1],q[2],q[3];"""
self.assertEqual(qasm2.dumps(qc), expected_qasm)
gate mcx q0,q1,q2,q3 { h q3; p\(pi/8\) q0; p\(pi/8\) q1; p\(pi/8\) q2; p\(pi/8\) q3; cx q0,q1; p\(-pi/8\) q1; cx q0,q1; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; p\(pi/8\) q2; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; h q3; }
gate (?P<mcx_id>mcx_[0-9]*) q0,q1,q2,q3 { mcx q0,q1,q2,q3; }
qreg q\[4\];
(?P=mcx_id) q\[0\],q\[1\],q\[2\],q\[3\];"""
expected_qasm = re.compile(pattern, re.MULTILINE)
self.assertRegex(qasm2.dumps(qc), expected_qasm)

def test_mcx_gate_variants(self):
n = 5
Expand Down
4 changes: 2 additions & 2 deletions test/python/transpiler/test_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def test_decompose_only_given_label(self):

def test_decompose_only_given_name(self):
"""Test decomposition parameters so that only given name is decomposed."""
decom_circ = self.complex_circuit.decompose(["mcx"])
decom_circ = self.complex_circuit.decompose(["mcx"], reps=2)
dag = circuit_to_dag(decom_circ)

self.assertEqual(len(dag.op_nodes()), 13)
Expand All @@ -236,7 +236,7 @@ def test_decompose_only_given_name(self):

def test_decompose_mixture_of_names_and_labels(self):
"""Test decomposition parameters so that mixture of names and labels is decomposed"""
decom_circ = self.complex_circuit.decompose(["mcx", "gate2"])
decom_circ = self.complex_circuit.decompose(["mcx", "gate2"], reps=2)
dag = circuit_to_dag(decom_circ)

self.assertEqual(len(dag.op_nodes()), 15)
Expand Down

0 comments on commit ffa0a81

Please sign in to comment.