Skip to content

Commit a748bd5

Browse files
committed
- Initial commit for improving circuit performance.
- Changed `gate_mapping` to be a class attribute constant. - Removed `process_gate_params_flag` from `quick.circuit.Circuit` attributes. - Slightly changed `.process_gate_params()` to support fast append of gate without preprocessing values.
1 parent 606525c commit a748bd5

7 files changed

Lines changed: 121 additions & 202 deletions

File tree

quick/circuit/circuit.py

Lines changed: 26 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,6 @@ class Circuit(ABC):
159159
The stack of the circuit log. Used for logging gate definitions.
160160
`global_phase` : float
161161
The global phase of the circuit.
162-
`process_gate_params_flag` : bool
163-
The flag to process the gate parameters.
164162
165163
Raises
166164
------
@@ -169,6 +167,8 @@ class Circuit(ABC):
169167
ValueError
170168
- Number of qubits must be greater than 0.
171169
"""
170+
gate_mapping: dict[str, Callable] = {}
171+
172172
def __init__(
173173
self,
174174
num_qubits: int
@@ -183,12 +183,10 @@ def __init__(
183183

184184
self.num_qubits = num_qubits
185185
self.circuit: Any
186-
self.gate_mapping: dict[str, Callable] = self._define_gate_mapping()
187186
self.measured_qubits: set[int] = set()
188187
self.circuit_log: list[dict] = []
189188
self.stack: list[list[dict]] = [self.circuit_log]
190189
self.global_phase: float = 0
191-
self.process_gate_params_flag: bool = True
192190

193191
def _convert_param_type(
194192
self,
@@ -353,22 +351,21 @@ def _validate_angle(
353351
value = [self._validate_single_angle(angle) for angle in value]
354352

355353
if all(angle == 0 for angle in value):
356-
# Indicate no operation needed
357354
return None
358355

359356
else:
360357
value = self._validate_single_angle(value)
361358

362359
if value == 0:
363-
# Indicate no operation needed
364360
return None
365361

366362
return value
367363

368364
def process_gate_params(
369365
self,
370366
gate: str,
371-
params: dict
367+
params: dict,
368+
fast_append: bool = False
372369
) -> dict | None:
373370
""" Process the gate parameters for the circuit.
374371
@@ -378,6 +375,10 @@ def process_gate_params(
378375
The gate to apply to the circuit.
379376
`params` : dict
380377
The parameters of the gate.
378+
`fast_append` : bool, optional, default=False
379+
If True, append the gate to the circuit log without validation.
380+
This is useful for internal methods that already validate
381+
the parameters.
381382
382383
Returns
383384
-------
@@ -399,45 +400,41 @@ def process_gate_params(
399400
-----
400401
>>> gate = self.process_gate_params(gate="X", params={"qubit_indices": 0})
401402
"""
402-
if not self.process_gate_params_flag:
403-
return None
404-
405403
# Remove the "self" key from the dictionary to avoid the inclusion of str(circuit)
406404
# in the circuit log
407405
params.pop("self", None)
408406

409-
qubit_indices = []
407+
if not fast_append:
408+
qubit_indices = []
410409

411-
for name, value in params.items():
412-
value = self._convert_param_type(value)
413-
value = self._validate_qubit_index(name, value)
410+
for name, value in params.items():
411+
value = self._convert_param_type(value)
412+
value = self._validate_qubit_index(name, value)
414413

415-
if value is None:
416-
continue
414+
if value is None:
415+
continue
417416

418-
if name in ALL_QUBIT_KEYS:
419-
qubit_indices.append(value) if isinstance(value, int) else qubit_indices.extend(value)
417+
if name in ALL_QUBIT_KEYS:
418+
qubit_indices.append(value) if isinstance(value, int) else qubit_indices.extend(value)
420419

421-
value = self._validate_angle(name, value)
420+
value = self._validate_angle(name, value)
422421

423-
# Indicate no operation needed
424-
if value is None:
425-
return None
422+
if value is None:
423+
return None
426424

427-
params[name] = value
425+
params[name] = value
428426

429-
if len(set(qubit_indices)) != len(qubit_indices):
430-
raise ValueError(
431-
"Qubit indices must be unique. "
432-
f"Received {qubit_indices} instead."
433-
)
427+
if len(set(qubit_indices)) != len(qubit_indices):
428+
raise ValueError(
429+
"Qubit indices must be unique. "
430+
f"Received {qubit_indices} instead."
431+
)
434432

435433
# Given the control state affects the circuit by adding X gates to
436434
# 0 valued control qubits, we don't need to include the control state
437435
# in the gate definition
438436
params.pop("control_state", None)
439437

440-
# Add the gate to the circuit log
441438
gate_dict = {"gate": gate, **params, "definition": []}
442439
self.stack[-1].append(gate_dict)
443440

@@ -468,8 +465,6 @@ def decompose_last(
468465
>>> with self.decompose_last():
469466
>>> self.X(qubit_indices)
470467
"""
471-
# If the gate is parameterized, and its rotation is effectively zero, return
472-
# as no operation is needed
473468
if gate is None:
474469
yield
475470
return
@@ -536,21 +531,6 @@ def controlled_state(
536531
if control_state_bits:
537532
self.X(control_state_bits)
538533

539-
@staticmethod
540-
@abstractmethod
541-
def _define_gate_mapping() -> dict[str, Callable]:
542-
""" Define the gate mapping for the circuit.
543-
544-
Notes
545-
-----
546-
The gate mapping is defined for each QC framework, and is meant to be used internally.
547-
548-
Returns
549-
-------
550-
`gate_mapping` : dict[str, Callable]
551-
The mapping of the gates to the circuit.
552-
"""
553-
554534
@abstractmethod
555535
def _gate_mapping(
556536
self,

quick/circuit/cirqcircuit.py

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
if TYPE_CHECKING:
3131
from quick.backend import Backend
3232
from quick.circuit import Circuit
33+
from quick.circuit.utils import const
3334
from quick.circuit.circuit import GATES
3435

3536

@@ -94,8 +95,6 @@ class CirqCircuit(Circuit):
9495
The circuit log.
9596
`global_phase` : float
9697
The global phase of the circuit.
97-
`process_gate_params_flag` : bool
98-
The flag to process the gate parameters.
9998
10099
Raises
101100
------
@@ -108,6 +107,23 @@ class CirqCircuit(Circuit):
108107
-----
109108
>>> circuit = CirqCircuit(num_qubits=2)
110109
"""
110+
gate_mapping: dict[str, Callable] = {
111+
"I": const(I),
112+
"X": const(X),
113+
"Y": const(Y),
114+
"Z": const(Z),
115+
"H": const(H),
116+
"S": const(S),
117+
"Sdg": const(S**-1),
118+
"T": const(T),
119+
"Tdg": const(T**-1),
120+
"RX": lambda angles: Rx(rads=angles[0]),
121+
"RY": lambda angles: Ry(rads=angles[0]),
122+
"RZ": lambda angles: Rz(rads=angles[0]),
123+
"Phase": lambda angles: cirq.ZPowGate(exponent=angles[0]/np.pi),
124+
"U3": lambda angles: U3(angles)
125+
}
126+
111127
def __init__(
112128
self,
113129
num_qubits: int
@@ -126,35 +142,6 @@ def __init__(
126142
for i in range(self.num_qubits):
127143
self.circuit.append(I(self.qr[i]))
128144

129-
@staticmethod
130-
def _define_gate_mapping() -> dict[str, Callable]:
131-
# Define lambda factory for non-parameterized gates
132-
def const(x):
133-
return lambda _angles: x
134-
135-
# Note that quick only uses U3, CX, and Global Phase gates and constructs the other gates
136-
# by performing decomposition
137-
# However, if the user wants to override the decomposition and use the native gates, they
138-
# can do so by using the below gate mapping
139-
gate_mapping = {
140-
"I": const(I),
141-
"X": const(X),
142-
"Y": const(Y),
143-
"Z": const(Z),
144-
"H": const(H),
145-
"S": const(S),
146-
"Sdg": const(S**-1),
147-
"T": const(T),
148-
"Tdg": const(T**-1),
149-
"RX": lambda angles: Rx(rads=angles[0]),
150-
"RY": lambda angles: Ry(rads=angles[0]),
151-
"RZ": lambda angles: Rz(rads=angles[0]),
152-
"Phase": lambda angles: cirq.ZPowGate(exponent=angles[0]/np.pi),
153-
"U3": lambda angles: U3(angles)
154-
}
155-
156-
return gate_mapping
157-
158145
def _gate_mapping(
159146
self,
160147
gate: GATES,

quick/circuit/pennylanecircuit.py

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
if TYPE_CHECKING:
3131
from quick.backend import Backend
3232
from quick.circuit import Circuit
33+
from quick.circuit.utils import const
3334
from quick.circuit.circuit import GATES
3435

3536

@@ -71,8 +72,6 @@ class PennylaneCircuit(Circuit):
7172
The circuit log.
7273
`global_phase` : float
7374
The global phase of the circuit.
74-
`process_gate_params_flag` : bool
75-
The flag to process the gate parameters.
7675
7776
Raises
7877
------
@@ -85,6 +84,23 @@ class PennylaneCircuit(Circuit):
8584
-----
8685
>>> circuit = PennylaneCircuit(num_qubits=2)
8786
"""
87+
gate_mapping: dict[str, Callable] = {
88+
"I": const(qml.Identity(0).matrix()),
89+
"X": const(qml.PauliX(0).matrix()),
90+
"Y": const(qml.PauliY(0).matrix()),
91+
"Z": const(qml.PauliZ(0).matrix()),
92+
"H": const(qml.Hadamard(wires=0).matrix()),
93+
"S": const(qml.S(wires=0).matrix()),
94+
"Sdg": const(qml.adjoint(qml.S(0)).matrix()), # type: ignore
95+
"T": const(qml.T(wires=0).matrix()),
96+
"Tdg": const(qml.adjoint(qml.T(0)).matrix()), # type: ignore
97+
"RX": lambda angles: qml.RX(phi=angles[0], wires=0).matrix(), # type: ignore
98+
"RY": lambda angles: qml.RY(phi=angles[0], wires=0).matrix(), # type: ignore
99+
"RZ": lambda angles: qml.RZ(phi=angles[0], wires=0).matrix(), # type: ignore
100+
"Phase": lambda angles: qml.PhaseShift(phi=angles[0], wires=0).matrix(), # type: ignore
101+
"U3": lambda angles: qml.U3(theta=angles[0], phi=angles[1], delta=angles[2], wires=0).matrix() # type: ignore
102+
}
103+
88104
def __init__(
89105
self,
90106
num_qubits: int
@@ -95,35 +111,6 @@ def __init__(
95111
self.device = qml.device("default.qubit", wires=self.num_qubits)
96112
self.circuit: list[qml.Operation] = []
97113

98-
@staticmethod
99-
def _define_gate_mapping() -> dict[str, Callable]:
100-
# Define lambda factory for non-parameterized gates
101-
def const(x):
102-
return lambda _angles: x
103-
104-
# Note that quick only uses U3, CX, and Global Phase gates and constructs the other gates
105-
# by performing decomposition
106-
# However, if the user wants to override the decomposition and use the native gates, they
107-
# can do so by using the below gate mapping
108-
gate_mapping = {
109-
"I": const(qml.Identity(0).matrix()),
110-
"X": const(qml.PauliX(0).matrix()),
111-
"Y": const(qml.PauliY(0).matrix()),
112-
"Z": const(qml.PauliZ(0).matrix()),
113-
"H": const(qml.Hadamard(wires=0).matrix()),
114-
"S": const(qml.S(wires=0).matrix()),
115-
"Sdg": const(qml.adjoint(qml.S(0)).matrix()), # type: ignore
116-
"T": const(qml.T(wires=0).matrix()),
117-
"Tdg": const(qml.adjoint(qml.T(0)).matrix()), # type: ignore
118-
"RX": lambda angles: qml.RX(phi=angles[0], wires=0).matrix(), # type: ignore
119-
"RY": lambda angles: qml.RY(phi=angles[0], wires=0).matrix(), # type: ignore
120-
"RZ": lambda angles: qml.RZ(phi=angles[0], wires=0).matrix(), # type: ignore
121-
"Phase": lambda angles: qml.PhaseShift(phi=angles[0], wires=0).matrix(), # type: ignore
122-
"U3": lambda angles: qml.U3(theta=angles[0], phi=angles[1], delta=angles[2], wires=0).matrix() # type: ignore
123-
}
124-
125-
return gate_mapping
126-
127114
def _gate_mapping(
128115
self,
129116
gate: GATES,

quick/circuit/qiskitcircuit.py

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
if TYPE_CHECKING:
4040
from quick.backend import Backend
4141
from quick.circuit import Circuit
42+
from quick.circuit.utils import const
4243
from quick.circuit.circuit import GATES
4344

4445

@@ -78,8 +79,6 @@ class QiskitCircuit(Circuit):
7879
The circuit log.
7980
`global_phase` : float
8081
The global phase of the circuit.
81-
`process_gate_params_flag` : bool
82-
The flag to process the gate parameters.
8382
8483
Raises
8584
------
@@ -92,6 +91,23 @@ class QiskitCircuit(Circuit):
9291
-----
9392
>>> circuit = QiskitCircuit(num_qubits=2)
9493
"""
94+
gate_mapping: dict[str, Callable] = {
95+
"I": const(IGate()),
96+
"X": const(XGate()),
97+
"Y": const(YGate()),
98+
"Z": const(ZGate()),
99+
"H": const(HGate()),
100+
"S": const(SGate()),
101+
"Sdg": const(SdgGate()),
102+
"T": const(TGate()),
103+
"Tdg": const(TdgGate()),
104+
"RX": lambda angles: RXGate(angles[0]),
105+
"RY": lambda angles: RYGate(angles[0]),
106+
"RZ": lambda angles: RZGate(angles[0]),
107+
"Phase": lambda angles: PhaseGate(angles[0]),
108+
"U3": lambda angles: U3Gate(theta=angles[0], phi=angles[1], lam=angles[2])
109+
}
110+
95111
def __init__(
96112
self,
97113
num_qubits: int
@@ -102,36 +118,6 @@ def __init__(
102118
qr = QuantumRegister(self.num_qubits)
103119
cr = ClassicalRegister(self.num_qubits)
104120
self.circuit: QuantumCircuit = QuantumCircuit(qr, cr)
105-
self.gate_mapping = self._define_gate_mapping()
106-
107-
@staticmethod
108-
def _define_gate_mapping() -> dict[str, Callable]:
109-
# Define lambda factory for non-parameterized gates
110-
def const(x):
111-
return lambda _angles: x
112-
113-
# Note that quick only uses U3, CX, and Global Phase gates and constructs the other gates
114-
# by performing decomposition
115-
# However, if the user wants to override the decomposition and use the native gates, they
116-
# can do so by using the below gate mapping
117-
gate_mapping = {
118-
"I": const(IGate()),
119-
"X": const(XGate()),
120-
"Y": const(YGate()),
121-
"Z": const(ZGate()),
122-
"H": const(HGate()),
123-
"S": const(SGate()),
124-
"Sdg": const(SdgGate()),
125-
"T": const(TGate()),
126-
"Tdg": const(TdgGate()),
127-
"RX": lambda angles: RXGate(angles[0]),
128-
"RY": lambda angles: RYGate(angles[0]),
129-
"RZ": lambda angles: RZGate(angles[0]),
130-
"Phase": lambda angles: PhaseGate(angles[0]),
131-
"U3": lambda angles: U3Gate(theta=angles[0], phi=angles[1], lam=angles[2])
132-
}
133-
134-
return gate_mapping
135121

136122
def _gate_mapping(
137123
self,

0 commit comments

Comments
 (0)