Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #28 #49

Merged
merged 1 commit into from
Mar 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 69 additions & 25 deletions quick/circuit/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@
"""
if name in ALL_QUBIT_KEYS:
if isinstance(value, list):
# For simplicity, we consider the [i] index to be just 1 (int instead of list)
# For simplicity, we consider the [i] index to be just i (int instead of list)
if len(value) == 1:
value = self._process_single_qubit_index(value[0])
else:
Expand Down Expand Up @@ -285,7 +285,10 @@
- Angle must be a number.
"""
if not isinstance(angle, (int, float)):
raise TypeError(f"Angle must be a number. Unexpected type {type(angle)} received.")
raise TypeError(
"Angle must be a number. "
f"Received {type(angle)} instead."
)

if abs(angle) <= EPSILON or abs(angle % PI_DOUBLE) <= EPSILON:
angle = 0.0
Expand Down Expand Up @@ -395,7 +398,7 @@

params[name] = value

if sorted(list(set(qubit_indices))) != sorted(qubit_indices):
if len(set(qubit_indices)) != len(qubit_indices):
raise ValueError(
"Qubit indices must be unique. "
f"Received {qubit_indices} instead."
Expand Down Expand Up @@ -5729,23 +5732,20 @@
# Define angle closeness threshold
threshold = PI * compression_percentage

# Initialize a list for the indices that will be removed
indices_to_remove = []
new_circuit_log = []

# Iterate over all angles, and set the angles within the
# compression percentage to 0 (this means the gate does nothing, and can be removed)
for index, operation in enumerate(self.circuit_log):
for operation in self.circuit_log:
if "angle" in operation:
if abs(operation["angle"]) < threshold:
indices_to_remove.append(index)
continue
elif "angles" in operation:
if all([abs(angle) < threshold for angle in operation["angles"]]):
indices_to_remove.append(index)

# Remove the operations with angles within the compression percentage
for index in sorted(indices_to_remove, reverse=True):
del self.circuit_log[index]
continue
new_circuit_log.append(operation)

Check warning on line 5746 in quick/circuit/circuit.py

View check run for this annotation

Codecov / codecov/patch

quick/circuit/circuit.py#L5746

Added line #L5746 was not covered by tests

self.circuit_log = new_circuit_log
self.update()

def change_mapping(
Expand All @@ -5771,24 +5771,18 @@
-----
>>> circuit.change_mapping(qubit_indices=[1, 0])
"""
if not all(isinstance(index, int) for index in qubit_indices):
raise TypeError("Qubit indices must be a collection of integers.")

if sorted(list(set(qubit_indices))) != list(range(self.num_qubits)):
if len(set(qubit_indices)) != self.num_qubits:
raise ValueError("Qubit indices must be unique.")

if isinstance(qubit_indices, Sequence):
qubit_indices = list(qubit_indices)
elif isinstance(qubit_indices, np.ndarray):
qubit_indices = qubit_indices.tolist()
if any(not isinstance(qubit_index, int) for qubit_index in qubit_indices):
raise TypeError("Qubit indices must all be integers.")

if self.num_qubits != len(qubit_indices):
raise ValueError("The number of qubits must match the number of qubits in the circuit.")

# Update the qubit indices
for operation in self.circuit_log:
for key in set(operation.keys()).intersection(ALL_QUBIT_KEYS):
if isinstance(operation[key], list):
if isinstance(operation[key], Sequence):
operation[key] = [qubit_indices[index] for index in operation[key]]
else:
operation[key] = qubit_indices[operation[key]]
Expand Down Expand Up @@ -5829,7 +5823,7 @@
# Iterate over the gate log and apply corresponding gates in the new framework
for gate_info in self.circuit_log:
# Extract gate name and remove it from gate_info for kwargs
gate_name = gate_info.pop("gate", None)
gate_name = gate_info.pop("gate")

# Extract gate definition and remove it from gate_info for kwargs
gate_definition = gate_info.pop("definition", None)
Expand Down Expand Up @@ -6328,8 +6322,58 @@
>>> circuit1 == circuit2
"""
if not isinstance(other_circuit, Circuit):
raise TypeError("Circuits must be compared with other circuits.")
return self.circuit_log == other_circuit.circuit_log
raise TypeError(
"Circuits can only be compared with other Circuits. "
f"Received {type(other_circuit)} instead."
)
return self.get_dag() == other_circuit.get_dag()

def is_equivalent(
self,
other_circuit: Circuit,
check_unitary: bool=True,
check_dag: bool=False
) -> bool:
""" Check if the circuit is equivalent to another circuit.

Parameters
----------
`other_circuit` : quick.circuit.Circuit
The other circuit to compare to.
`check_unitary` : bool, optional, default=True
Whether or not to check the unitary of the circuit.
`check_dag` : bool, optional, default=False
Whether or not to check the DAG of the circuit.

Returns
-------
bool
Whether the two circuits are equivalent.

Raises
------
TypeError
- Circuits must be compared with other circuits.

Usage
-----
>>> circuit1.is_equivalent(circuit2)
"""
if not isinstance(other_circuit, Circuit):
raise TypeError(

Check warning on line 6363 in quick/circuit/circuit.py

View check run for this annotation

Codecov / codecov/patch

quick/circuit/circuit.py#L6363

Added line #L6363 was not covered by tests
"Circuits can only be compared with other Circuits. "
f"Received {type(other_circuit)} instead."
)

if check_unitary:
if not np.allclose(self.get_unitary(), other_circuit.get_unitary()):
return False

Check warning on line 6370 in quick/circuit/circuit.py

View check run for this annotation

Codecov / codecov/patch

quick/circuit/circuit.py#L6370

Added line #L6370 was not covered by tests

if check_dag:
if self.get_dag() != other_circuit.get_dag():
return False

return True

def __len__(self) -> int:
""" Get the number of the circuit operations.
Expand Down
70 changes: 69 additions & 1 deletion quick/circuit/dag/dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,33 @@
"""
from quick.circuit.circuit import ALL_QUBIT_KEYS

gate_node = DAGNode(operation["gate"])
# Extract the gate parameters
params = dict()
meta_params = dict()

# Log all parameters except 'definition' and 'gate'
# in the meta params to ensure uniqueness of the node
# when the gate is repeated on multiple qubits in
# parallel, i.e., a CX followed by two H on control
# and target
# This avoids `__eq__` issues with DAGNode
for key in operation:
if key not in ALL_QUBIT_KEYS.union(['definition', 'gate']):
params[key] = operation[key]
if key not in ['definition', 'gate']:
meta_params[key] = operation[key]

# Define the name of the node
# For simplicity, we omit the empty params for gates
# that only have qubit indices as parameter
if params == {}:
node_name = f"{operation['gate']}"
else:
node_name = f"{operation['gate']}({str(params).strip('{}')})"

meta_name = f"{operation['gate']}({str(meta_params).strip('{}')})"

gate_node = DAGNode(node_name, meta_name=meta_name)
qubit_indices = []

# Add qubits from any valid qubit key to the
Expand Down Expand Up @@ -108,6 +134,48 @@
"""
return max(qubit.depth for qubit in self.qubits.values())

def __eq__(
self,
other_circuit: object
) -> bool:
""" Check if two circuits are equal.

Parameters
----------
`other_circuit` : object
The other circuit to compare with.

Returns
-------
bool
True if the two circuits are equal, False otherwise.

Raises
------
TypeError
If the other circuit is not an instance of `quick.circuit.dag.DAGCircuit`.

Usage
-----
>>> dag1 = DAGCircuit(2)
>>> dag2 = DAGCircuit(2)
>>> dag1 == dag2
"""
if not isinstance(other_circuit, DAGCircuit):
raise TypeError(

Check warning on line 165 in quick/circuit/dag/dagcircuit.py

View check run for this annotation

Codecov / codecov/patch

quick/circuit/dag/dagcircuit.py#L165

Added line #L165 was not covered by tests
"DAGCircuits can only be compared with other DAGCircuits. "
f"Received {type(other_circuit)} instead."
)

if self.num_qubits != other_circuit.num_qubits:
return False

for qubit in self.qubits:
if self.qubits[qubit] != other_circuit.qubits[qubit]:
return False

return True

def __repr__(self) -> str:
""" Get the string representation of the circuit.

Expand Down
Loading