Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into ParameterExpression…
Browse files Browse the repository at this point in the history
…_rust
  • Loading branch information
doichanj committed Jan 15, 2025
2 parents 06abd7e + 087e393 commit 21c8a80
Show file tree
Hide file tree
Showing 30 changed files with 351 additions and 392 deletions.
36 changes: 0 additions & 36 deletions .github/workflows/wheels-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,6 @@ on:
default: "default"
required: false

wheels-32bit:
description: >-
The action to take for Tier 1 wheels.
Choose from 'default', 'build' or 'skip'.
This builds multiple artifacts, which all match 'wheels-32bit-*'.
type: string
default: "default"
required: false

wheels-linux-s390x:
description: >-
The action to take for Linux s390x wheels.
Expand Down Expand Up @@ -133,33 +124,6 @@ jobs:
path: ./wheelhouse/*.whl
name: ${{ inputs.artifact-prefix }}wheels-tier-1-${{ matrix.os }}

wheels-32bit:
name: "Wheels / 32bit"
if: (inputs.wheels-32bit == 'default' && inputs.default-action || inputs.wheels-32bit) == 'build'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- name: Build wheels
uses: pypa/[email protected]
env:
CIBW_SKIP: 'pp* cp36-* cp37-* cp38-* *musllinux* *amd64 *x86_64'
- uses: actions/upload-artifact@v4
with:
path: ./wheelhouse/*.whl
name: ${{ inputs.artifact-prefix }}wheels-32bit-${{ matrix.os }}

wheels-linux-s390x:
name: "Wheels / Linux s390x"
if: (inputs.wheels-linux-s390x == 'default' && inputs.default-action || inputs.wheels-linux-s390x) == 'build'
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ jobs:
artifact-prefix: "deploy-core-"
default-action: "skip"
wheels-tier-1: "build"
wheels-32bit: "build"
sdist: "build"
upload-core:
name: Deploy core
Expand All @@ -39,7 +38,6 @@ jobs:
artifact-prefix: "deploy-others-"
default-action: "build"
wheels-tier-1: "skip"
wheels-32bit: "skip"
sdist: "skip"
upload-others:
name: Deploy other
Expand Down
36 changes: 18 additions & 18 deletions crates/accelerate/src/circuit_library/pauli_evolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,22 +192,22 @@ fn multi_qubit_evolution(
/// followed by a CX-chain and then a single Pauli-Z rotation on the last qubit. Then the CX-chain
/// is uncomputed and the inverse basis transformation applied. E.g. for the evolution under the
/// Pauli string XIYZ we have the circuit
/// ┌───┐┌───────┐┌───┐
/// 0: ─────────────┤ X ├┤ Rz(2) ├┤ X ├──────────
/// ──────┐┌───┐└─┬─┘└───────┘└─┬─┘┌───┐┌────┐
/// 1: ┤ √Xdg ├┤ X ├──■─────────────■──┤ X ├┤ √X ├
/// ──────┘└─┬─┘ └─┬─┘└────┘
/// 2: ─────────────────────────────────┼────────
/// ┌───┐ │ │ ┌───┐
/// 3: ─┤ H ├────■───────────────────────■──┤ H ├─
/// ───┘ └───
///
/// ───┐ ┌───┐┌───────┐┌───┐┌───┐
/// 0: ┤ H ├──────┤ X ├┤ Rz(2) ├┤ X ├┤ H ├────────
/// └───┘ └─┬─┘└───────┘└─┬─┘└───┘
/// 1: ─────────────┼─────────────┼───────────────
/// ────┐┌───┐ │ │ ┌───┐┌──────
/// 2: ┤ √X ├┤ X ├──■─────────────■──┤ X ├┤ √Xdg ├
/// ────┘└─┬─┘ └─┬─┘└──────
/// 3: ────────■───────────────────────■──────────
///
/// Args:
/// num_qubits: The number of qubits in the Hamiltonian.
/// sparse_paulis: The Paulis to implement. Given in a sparse-list format with elements
/// ``(pauli_string, qubit_indices, coefficient)``. An element of the form
/// ``("IXYZ", [0,1,2,3], 0.2)``, for example, is interpreted in terms of qubit indices as
/// I_q0 X_q1 Y_q2 Z_q3 and will use a RZ rotation angle of 0.4.
/// ``(pauli_string, qubit_indices, rz_rotation_angle)``. An element of the form
/// ``("XIYZ", [0,1,2,3], 2)``, for example, is interpreted in terms of qubit indices as
/// X_q0 I_q1 Y_q2 Z_q3 and will use a RZ rotation angle of 2.
/// insert_barriers: If ``true``, insert a barrier in between the evolution of individual
/// Pauli terms.
/// do_fountain: If ``true``, implement the CX propagation as "fountain" shape, where each
Expand Down Expand Up @@ -244,7 +244,7 @@ pub fn py_pauli_evolution(
}

paulis.push(pauli);
times.push(time); // note we do not multiply by 2 here, this is done Python side!
times.push(time); // note we do not multiply by 2 here, this is already done Python side!
indices.push(tuple.get_item(1)?.extract::<Vec<u32>>()?)
}

Expand All @@ -266,12 +266,12 @@ pub fn py_pauli_evolution(
},
);

// When handling all-identity Paulis above, we added the time as global phase.
// However, the all-identity Paulis should add a negative phase, as they implement
// exp(-i t I). We apply the negative sign here, to only do a single (-1) multiplication,
// instead of doing it every time we find an all-identity Pauli.
// When handling all-identity Paulis above, we added the RZ rotation angle as global phase,
// meaning that we have implemented of exp(i 2t I). However, what we want it to implement
// exp(-i t I). To only use a single multiplication, we apply a factor of -0.5 here.
// This is faster, in particular as long as the parameter expressions are in Python.
if modified_phase {
global_phase = multiply_param(&global_phase, -1.0, py);
global_phase = multiply_param(&global_phase, -0.5, py);
}

CircuitData::from_packed_operations(py, num_qubits as u32, 0, evos, global_phase)
Expand Down
2 changes: 1 addition & 1 deletion crates/accelerate/src/synthesis/evolution/pauli_network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ fn inject_rotations(
if pauli_support_size == 0 {
// in case of an all-identity rotation, update global phase by subtracting
// the angle
global_phase = radd_param(global_phase, multiply_param(&angles[i], -1.0, py), py);
global_phase = radd_param(global_phase, multiply_param(&angles[i], -0.5, py), py);
hit_paulis[i] = true;
dag.remove_node(i);
} else if pauli_support_size == 1 && dag.is_front_node(i) {
Expand Down
6 changes: 6 additions & 0 deletions crates/accelerate/src/target_transpiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,12 @@ impl Target {

/// Checks whether an instruction is supported by the Target based on instruction name and qargs.
pub fn instruction_supported(&self, operation_name: &str, qargs: Option<&Qargs>) -> bool {
// Handle case where num_qubits is None by checking globally supported operations
let qargs: Option<&Qargs> = if self.num_qubits.is_none() {
None
} else {
qargs
};
if self.gate_map.contains_key(operation_name) {
if let Some(_qargs) = qargs {
let qarg_set: HashSet<&PhysicalQubit> = _qargs.iter().collect();
Expand Down
6 changes: 5 additions & 1 deletion crates/accelerate/src/unitary_synthesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ fn get_2q_decomposers_from_target(
let mut available_2q_props: IndexMap<&str, (Option<f64>, Option<f64>)> = IndexMap::new();

let mut qubit_gate_map = IndexMap::new();

match target.operation_names_for_qargs(Some(&qubits)) {
Ok(direct_keys) => {
qubit_gate_map.insert(&qubits, direct_keys);
Expand Down Expand Up @@ -597,7 +598,10 @@ fn get_2q_decomposers_from_target(
OperationRef::Standard(_) => (),
_ => continue,
}

// Filter out non-2q-gate candidates
if op.operation.num_qubits() != 2 {
continue;
}
available_2q_basis.insert(key, replace_parametrized_gate(op.clone()));

if target.contains_key(key) {
Expand Down
8 changes: 7 additions & 1 deletion crates/circuit/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2345,8 +2345,14 @@ pub fn add_param(param: &Param, summand: f64, py: Python) -> Param {
}

pub fn radd_param(param1: Param, param2: Param, py: Python) -> Param {
match [param1, param2] {
match [&param1, &param2] {
[Param::Float(theta), Param::Float(lambda)] => Param::Float(theta + lambda),
[Param::Float(theta), Param::ParameterExpression(_lambda)] => {
add_param(&param2, *theta, py)
}
[Param::ParameterExpression(_theta), Param::Float(lambda)] => {
add_param(&param1, *lambda, py)
}
[Param::ParameterExpression(theta), Param::ParameterExpression(lambda)] => {
Param::ParameterExpression(
theta
Expand Down
Binary file removed docs/source_images/depth.gif
Binary file not shown.
2 changes: 1 addition & 1 deletion qiskit/circuit/library/generalized_gates/permutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def inverse(self, annotated: bool = False) -> PermutationGate:

return PermutationGate(pattern=_inverse_pattern(self.pattern))

def _qasm2_decomposition(self):
def _qasm_decomposition(self):
# pylint: disable=cyclic-import
from qiskit.synthesis.permutation import synth_permutation_basic

Expand Down
2 changes: 1 addition & 1 deletion qiskit/circuit/library/generalized_gates/unitary.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def control(
)
return gate

def _qasm2_decomposition(self):
def _qasm_decomposition(self):
"""Return an unparameterized version of ourselves, so the OQ2 exporter doesn't choke on the
non-standard things in our `params` field."""
out = self.definition.to_gate()
Expand Down
29 changes: 8 additions & 21 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -889,27 +889,6 @@ class QuantumCircuit:
assert qc.size() == 19
A particularly important circuit property is known as the circuit :meth:`depth`. The depth
of a quantum circuit is a measure of how many "layers" of quantum gates, executed in
parallel, it takes to complete the computation defined by the circuit. Because quantum
gates take time to implement, the depth of a circuit roughly corresponds to the amount of
time it takes the quantum computer to execute the circuit. Thus, the depth of a circuit
is one important quantity used to measure if a quantum circuit can be run on a device.
The depth of a quantum circuit has a mathematical definition as the longest path in a
directed acyclic graph (DAG). However, such a definition is a bit hard to grasp, even for
experts. Fortunately, the depth of a circuit can be easily understood by anyone familiar
with playing `Tetris <https://en.wikipedia.org/wiki/Tetris>`_. Lets see how to compute this
graphically:
.. image:: /source_images/depth.gif
:alt: Rotate the circuit and let each gate fall as far as possible. The gates fall \
into "layers". The depth of the circuit is the number of layers.
We can verify our graphical result using :meth:`QuantumCircuit.depth`::
assert qc.depth() == 9
.. automethod:: count_ops
.. automethod:: depth
.. automethod:: get_instructions
Expand Down Expand Up @@ -3490,6 +3469,14 @@ def depth(
) -> int:
"""Return circuit depth (i.e., length of critical path).
The depth of a quantum circuit is a measure of how many
"layers" of quantum gates, executed in parallel, it takes to
complete the computation defined by the circuit. Because
quantum gates take time to implement, the depth of a circuit
roughly corresponds to the amount of time it takes the quantum
computer to execute the circuit.
.. warning::
This operation is not well defined if the circuit contains control-flow operations.
Expand Down
4 changes: 2 additions & 2 deletions qiskit/qasm2/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ def _define_custom_operation(operation, gates_to_define):
# definition, but still continue to return the given object as the call-site object.
if operation.base_class in known_good_parameterized:
parameterized_operation = type(operation)(*_FIXED_PARAMETERS[: len(operation.params)])
elif hasattr(operation, "_qasm2_decomposition"):
new_op = operation._qasm2_decomposition()
elif hasattr(operation, "_qasm_decomposition"):
new_op = operation._qasm_decomposition()
parameterized_operation = operation = new_op.copy(name=_escape_name(new_op.name, "gate_"))
else:
parameterized_operation = operation
Expand Down
9 changes: 6 additions & 3 deletions qiskit/qasm3/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1180,13 +1180,16 @@ def build_gate_call(self, instruction: CircuitInstruction):
This will also push the gate into the symbol table (if required), including recursively
defining the gate blocks."""
ident = self.symbols.get_gate(instruction.operation)
operation = instruction.operation
if hasattr(operation, "_qasm_decomposition"):
operation = operation._qasm_decomposition()
ident = self.symbols.get_gate(operation)
if ident is None:
ident = self.define_gate(instruction.operation)
ident = self.define_gate(operation)
qubits = [self._lookup_bit(qubit) for qubit in instruction.qubits]
parameters = [
ast.StringifyAndPray(self._rebind_scoped_parameters(param))
for param in instruction.operation.params
for param in operation.params
]
if not self.disable_constants:
for parameter in parameters:
Expand Down
6 changes: 0 additions & 6 deletions qiskit/synthesis/evolution/product_formula.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,6 @@ def settings(self) -> dict[str, typing.Any]:
"preserve_order": self.preserve_order,
}

def _normalize_coefficients(
self, paulis: list[str | list[int], float | complex | ParameterExpression]
) -> list[str | list[int] | ParameterValueType]:
"""Ensure the coefficients are real (or parameter expressions)."""
return [[(op, qubits, real_or_fail(coeff)) for op, qubits, coeff in ops] for ops in paulis]

def _custom_evolution(self, num_qubits, pauli_rotations):
"""Implement the evolution for the non-standard path.
Expand Down
27 changes: 22 additions & 5 deletions qiskit/synthesis/evolution/suzuki_trotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
import typing
from collections.abc import Callable
from itertools import chain
import numpy as np

from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.quantum_info.operators import SparsePauliOp, Pauli
from qiskit.utils.deprecation import deprecate_arg
Expand Down Expand Up @@ -138,7 +140,7 @@ def expand(
.. code-block:: text
("X", [0], t), ("ZZ", [0, 1], 2t), ("X", [0], 2)
("X", [0], t), ("ZZ", [0, 1], 2t), ("X", [0], t)
Note that the rotation angle contains a factor of 2, such that that evolution
of a Pauli :math:`P` over time :math:`t`, which is :math:`e^{itP}`, is represented
Expand All @@ -157,7 +159,10 @@ def expand(
time = evolution.time

def to_sparse_list(operator):
paulis = (time * (2 / self.reps) * operator).to_sparse_list()
paulis = [
(pauli, indices, real_or_fail(coeff) * time * 2 / self.reps)
for pauli, indices, coeff in operator.to_sparse_list()
]
if not self.preserve_order:
return reorder_paulis(paulis)

Expand All @@ -171,9 +176,6 @@ def to_sparse_list(operator):
# here would be the location to do so.
non_commuting = [[op] for op in to_sparse_list(operators)]

# normalize coefficients, i.e. ensure they are float or ParameterExpression
non_commuting = self._normalize_coefficients(non_commuting)

# we're already done here since Lie Trotter does not do any operator repetition
product_formula = self._recurse(self.order, non_commuting)
flattened = self.reps * list(chain.from_iterable(product_formula))
Expand Down Expand Up @@ -213,3 +215,18 @@ def _recurse(order, grouped_paulis):
],
)
return outer + inner + outer


def real_or_fail(value, tol=100):
"""Return real if close, otherwise fail. Unbound parameters are left unchanged.
Based on NumPy's ``real_if_close``, i.e. ``tol`` is in terms of machine precision for float.
"""
if isinstance(value, ParameterExpression):
return value

abstol = tol * np.finfo(float).eps
if abs(np.imag(value)) < abstol:
return np.real(value)

raise ValueError(f"Encountered complex value {value}, but expected real.")
Loading

0 comments on commit 21c8a80

Please sign in to comment.