Skip to content

Commit

Permalink
Merge branch 'main' into revamp-unitary-synth
Browse files Browse the repository at this point in the history
  • Loading branch information
ElePT authored Jan 21, 2025
2 parents cca430d + d884a3c commit 87ebea3
Show file tree
Hide file tree
Showing 48 changed files with 580 additions and 349 deletions.
8 changes: 1 addition & 7 deletions .github/workflows/wheels-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,20 +173,14 @@ jobs:
wheels-linux-aarch64:
name: "Wheels / Linux AArch64"
if: (inputs.wheels-linux-aarch64 == 'default' && inputs.default-action || inputs.wheels-linux-aarch64) == 'build'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- uses: dtolnay/rust-toolchain@stable
- uses: docker/setup-qemu-action@v3
with:
platforms: all
- uses: pypa/[email protected]
env:
CIBW_ARCHS_LINUX: aarch64
CIBW_TEST_COMMAND: cp -r {project}/test . && QISKIT_PARALLEL=FALSE stestr --test-path test/python run --abbreviate -n test.python.compiler.test_transpiler
- uses: actions/upload-artifact@v4
with:
path: ./wheelhouse/*.whl
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ license = "Apache-2.0"
# Each crate can add on specific features freely as it inherits.
[workspace.dependencies]
bytemuck = "1.21"
indexmap.version = "2.7.0"
indexmap.version = "2.7.1"
hashbrown.version = "0.14.5"
num-bigint = "0.4"
num-complex = "0.4"
Expand Down
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,30 +40,30 @@ To install from source, follow the instructions in the [documentation](https://d
Now that Qiskit is installed, it's time to begin working with Qiskit. The essential parts of a quantum program are:
1. Define and build a quantum circuit that represents the quantum state
2. Define the classical output by measurements or a set of observable operators
3. Depending on the output, use the primitive function `sampler` to sample outcomes or the `estimator` to estimate values.
3. Depending on the output, use the Sampler primitive to sample outcomes or the Estimator primitive to estimate expectation values.

Create an example quantum circuit using the `QuantumCircuit` class:

```python
import numpy as np
from qiskit import QuantumCircuit

# 1. A quantum circuit for preparing the quantum state |000> + i |111>
qc_example = QuantumCircuit(3)
qc_example.h(0) # generate superpostion
qc_example.p(np.pi/2,0) # add quantum phase
qc_example.cx(0,1) # 0th-qubit-Controlled-NOT gate on 1st qubit
qc_example.cx(0,2) # 0th-qubit-Controlled-NOT gate on 2nd qubit
# 1. A quantum circuit for preparing the quantum state |000> + i |111> / √2
qc = QuantumCircuit(3)
qc.h(0) # generate superposition
qc.p(np.pi / 2, 0) # add quantum phase
qc.cx(0, 1) # 0th-qubit-Controlled-NOT gate on 1st qubit
qc.cx(0, 2) # 0th-qubit-Controlled-NOT gate on 2nd qubit
```

This simple example makes an entangled state known as a [GHZ state](https://en.wikipedia.org/wiki/Greenberger%E2%80%93Horne%E2%80%93Zeilinger_state) $(|000\rangle + i|111\rangle)/\sqrt{2}$. It uses the standard quantum gates: Hadamard gate (`h`), Phase gate (`p`), and CNOT gate (`cx`).
This simple example creates an entangled state known as a [GHZ state](https://en.wikipedia.org/wiki/Greenberger%E2%80%93Horne%E2%80%93Zeilinger_state) $(|000\rangle + i|111\rangle)/\sqrt{2}$. It uses the standard quantum gates: Hadamard gate (`h`), Phase gate (`p`), and CNOT gate (`cx`).

Once you've made your first quantum circuit, choose which primitive function you will use. Starting with `sampler`,
Once you've made your first quantum circuit, choose which primitive you will use. Starting with the Sampler,
we use `measure_all(inplace=False)` to get a copy of the circuit in which all the qubits are measured:

```python
# 2. Add the classical output in the form of measurement of all qubits
qc_measured = qc_example.measure_all(inplace=False)
qc_measured = qc.measure_all(inplace=False)

# 3. Execute using the Sampler primitive
from qiskit.primitives import StatevectorSampler
Expand All @@ -73,7 +73,7 @@ result = job.result()
print(f" > Counts: {result[0].data["meas"].get_counts()}")
```
Running this will give an outcome similar to `{'000': 497, '111': 503}` which is `000` 50% of the time and `111` 50% of the time up to statistical fluctuations.
To illustrate the power of Estimator, we now use the quantum information toolbox to create the operator $XXY+XYX+YXX-YYY$ and pass it to the `run()` function, along with our quantum circuit. Note the Estimator requires a circuit _**without**_ measurement, so we use the `qc_example` circuit we created earlier.
To illustrate the power of the Estimator, we now use the quantum information toolbox to create the operator $XXY+XYX+YXX-YYY$ and pass it to the `run()` function, along with our quantum circuit. Note that the Estimator requires a circuit _**without**_ measurements, so we use the `qc` circuit we created earlier.

```python
# 2. Define the observable to be measured
Expand All @@ -83,7 +83,7 @@ operator = SparsePauliOp.from_list([("XXY", 1), ("XYX", 1), ("YXX", 1), ("YYY",
# 3. Execute using the Estimator primitive
from qiskit.primitives import StatevectorEstimator
estimator = StatevectorEstimator()
job = estimator.run([(qc_example, operator)], precision=1e-3)
job = estimator.run([(qc, operator)], precision=1e-3)
result = job.result()
print(f" > Expectation values: {result[0].data.evs}")
```
Expand All @@ -96,17 +96,17 @@ The power of quantum computing cannot be simulated on classical computers and yo
However, running a quantum circuit on hardware requires rewriting to the basis gates and connectivity of the quantum hardware.
The tool that does this is the [transpiler](https://docs.quantum.ibm.com/api/qiskit/transpiler), and Qiskit includes transpiler passes for synthesis, optimization, mapping, and scheduling.
However, it also includes a default compiler, which works very well in most examples.
The following code will map the example circuit to the `basis_gates = ['cz', 'sx', 'rz']` and a linear chain of qubits $0 \rightarrow 1 \rightarrow 2$ with the `coupling_map =[[0, 1], [1, 2]]`.
The following code will map the example circuit to the `basis_gates = ["cz", "sx", "rz"]` and a linear chain of qubits $0 \rightarrow 1 \rightarrow 2$ with the `coupling_map = [[0, 1], [1, 2]]`.

```python
from qiskit import transpile
qc_transpiled = transpile(qc_example, basis_gates = ['cz', 'sx', 'rz'], coupling_map =[[0, 1], [1, 2]] , optimization_level=3)
qc_transpiled = transpile(qc, basis_gates=["cz", "sx", "rz"], coupling_map=[[0, 1], [1, 2]], optimization_level=3)
```

### Executing your code on real quantum hardware

Qiskit provides an abstraction layer that lets users run quantum circuits on hardware from any vendor that provides a compatible interface.
The best way to use Qiskit is with a runtime environment that provides optimized implementations of `sampler` and `estimator` for a given hardware platform. This runtime may involve using pre- and post-processing, such as optimized transpiler passes with error suppression, error mitigation, and, eventually, error correction built in. A runtime implements `qiskit.primitives.BaseSamplerV2` and `qiskit.primitives.BaseEstimatorV2` interfaces. For example,
The best way to use Qiskit is with a runtime environment that provides optimized implementations of Sampler and Estimator for a given hardware platform. This runtime may involve using pre- and post-processing, such as optimized transpiler passes with error suppression, error mitigation, and, eventually, error correction built in. A runtime implements `qiskit.primitives.BaseSamplerV2` and `qiskit.primitives.BaseEstimatorV2` interfaces. For example,
some packages that provide implementations of a runtime primitive implementation are:

* https://github.com/Qiskit/qiskit-ibm-runtime
Expand Down
27 changes: 12 additions & 15 deletions crates/accelerate/src/barrier_before_final_measurement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,21 @@ pub fn barrier_before_final_measurements(
dag: &mut DAGCircuit,
label: Option<String>,
) -> PyResult<()> {
let is_exactly_final = |inst: &PackedInstruction| FINAL_OP_NAMES.contains(&inst.op.name());
let final_ops: HashSet<NodeIndex> = dag
.op_nodes(true)
.filter(|node| {
let NodeType::Operation(ref inst) = dag.dag()[*node] else {
unreachable!();
};
if !FINAL_OP_NAMES.contains(&inst.op.name()) {
return false;
.filter_map(|(node, inst)| {
if !is_exactly_final(inst) {
return None;
}
let is_final_op = dag.bfs_successors(*node).all(|(_, child_successors)| {
!child_successors.iter().any(|suc| match dag.dag()[*suc] {
NodeType::Operation(ref suc_inst) => {
!FINAL_OP_NAMES.contains(&suc_inst.op.name())
}
_ => false,
dag.bfs_successors(node)
.all(|(_, child_successors)| {
child_successors.iter().all(|suc| match dag[*suc] {
NodeType::Operation(ref suc_inst) => is_exactly_final(suc_inst),
_ => true,
})
})
});
is_final_op
.then_some(node)
})
.collect();
if final_ops.is_empty() {
Expand All @@ -60,7 +57,7 @@ pub fn barrier_before_final_measurements(
let final_packed_ops: Vec<PackedInstruction> = ordered_node_indices
.into_iter()
.map(|node| {
let NodeType::Operation(ref inst) = dag.dag()[node] else {
let NodeType::Operation(ref inst) = dag[node] else {
unreachable!()
};
let res = inst.clone();
Expand Down
52 changes: 22 additions & 30 deletions crates/accelerate/src/basis/basis_translator/compose_transforms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use qiskit_circuit::parameter_table::ParameterUuid;
use qiskit_circuit::Qubit;
use qiskit_circuit::{
circuit_data::CircuitData,
dag_circuit::{DAGCircuit, NodeType},
dag_circuit::DAGCircuit,
operations::{Operation, Param},
};
use smallvec::SmallVec;
Expand Down Expand Up @@ -83,24 +83,18 @@ pub(super) fn compose_transforms<'a>(
for (_, dag) in &mut mapped_instructions.values_mut() {
let nodes_to_replace = dag
.op_nodes(true)
.filter_map(|node| {
if let Some(NodeType::Operation(op)) = dag.dag().node_weight(node) {
if (gate_name.as_str(), *gate_num_qubits)
== (op.op.name(), op.op.num_qubits())
{
Some((
node,
op.params_view()
.iter()
.map(|x| x.clone_ref(py))
.collect::<SmallVec<[Param; 3]>>(),
))
} else {
None
}
} else {
None
}
.filter(|(_, op)| {
(op.op.num_qubits() == *gate_num_qubits)
&& (op.op.name() == gate_name.as_str())
})
.map(|(node, op)| {
(
node,
op.params_view()
.iter()
.map(|x| x.clone_ref(py))
.collect::<SmallVec<[Param; 3]>>(),
)
})
.collect::<Vec<_>>();
for (node, params) in nodes_to_replace {
Expand Down Expand Up @@ -141,17 +135,15 @@ fn get_gates_num_params(
dag: &DAGCircuit,
example_gates: &mut HashMap<GateIdentifier, usize>,
) -> PyResult<()> {
for node in dag.op_nodes(true) {
if let Some(NodeType::Operation(op)) = dag.dag().node_weight(node) {
example_gates.insert(
(op.op.name().to_string(), op.op.num_qubits()),
op.params_view().len(),
);
if op.op.control_flow() {
let blocks = op.op.blocks();
for block in blocks {
get_gates_num_params_circuit(&block, example_gates)?;
}
for (_, inst) in dag.op_nodes(true) {
example_gates.insert(
(inst.op.name().to_string(), inst.op.num_qubits()),
inst.params_view().len(),
);
if inst.op.control_flow() {
let blocks = inst.op.blocks();
for block in blocks {
get_gates_num_params_circuit(&block, example_gates)?;
}
}
}
Expand Down
12 changes: 5 additions & 7 deletions crates/accelerate/src/basis/basis_translator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,7 @@ fn extract_basis(
basis: &mut HashSet<GateIdentifier>,
min_qubits: usize,
) -> PyResult<()> {
for node in circuit.op_nodes(true) {
let operation: &PackedInstruction = circuit.dag()[node].unwrap_operation();
for (node, operation) in circuit.op_nodes(true) {
if !circuit.has_calibration_for_index(py, node)?
&& circuit.get_qargs(operation.qubits).len() >= min_qubits
{
Expand Down Expand Up @@ -279,8 +278,7 @@ fn extract_basis_target(
min_qubits: usize,
qargs_with_non_global_operation: &HashMap<Option<Qargs>, HashSet<String>>,
) -> PyResult<()> {
for node in dag.op_nodes(true) {
let node_obj: &PackedInstruction = dag.dag()[node].unwrap_operation();
for (node, node_obj) in dag.op_nodes(true) {
let qargs: &[Qubit] = dag.get_qargs(node_obj.qubits);
if dag.has_calibration_for_index(py, node)? || qargs.len() < min_qubits {
continue;
Expand Down Expand Up @@ -430,7 +428,7 @@ fn apply_translation(
let mut is_updated = false;
let mut out_dag = dag.copy_empty_like(py, "alike")?;
for node in dag.topological_op_nodes()? {
let node_obj = dag.dag()[node].unwrap_operation();
let node_obj = dag[node].unwrap_operation();
let node_qarg = dag.get_qargs(node_obj.qubits);
let node_carg = dag.get_cargs(node_obj.clbits);
let qubit_set: HashSet<Qubit> = HashSet::from_iter(node_qarg.iter().copied());
Expand Down Expand Up @@ -608,7 +606,7 @@ fn replace_node(
}
if node.params_view().is_empty() {
for inner_index in target_dag.topological_op_nodes()? {
let inner_node = &target_dag.dag()[inner_index].unwrap_operation();
let inner_node = &target_dag[inner_index].unwrap_operation();
let old_qargs = dag.get_qargs(node.qubits);
let old_cargs = dag.get_cargs(node.clbits);
let new_qubits: Vec<Qubit> = target_dag
Expand Down Expand Up @@ -669,7 +667,7 @@ fn replace_node(
.zip(node.params_view())
.into_py_dict_bound(py);
for inner_index in target_dag.topological_op_nodes()? {
let inner_node = &target_dag.dag()[inner_index].unwrap_operation();
let inner_node = &target_dag[inner_index].unwrap_operation();
let old_qargs = dag.get_qargs(node.qubits);
let old_cargs = dag.get_cargs(node.clbits);
let new_qubits: Vec<Qubit> = target_dag
Expand Down
72 changes: 35 additions & 37 deletions crates/accelerate/src/check_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

use qiskit_circuit::circuit_data::CircuitData;
use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType};
use qiskit_circuit::dag_circuit::DAGCircuit;
use qiskit_circuit::imports::CIRCUIT_TO_DAG;
use qiskit_circuit::operations::{Operation, OperationRef};
use qiskit_circuit::Qubit;
Expand All @@ -36,45 +36,43 @@ fn recurse<'py>(
None => edge_set.contains(&[qubits[0].into(), qubits[1].into()]),
}
};
for node in dag.op_nodes(false) {
if let NodeType::Operation(inst) = &dag.dag()[node] {
let qubits = dag.get_qargs(inst.qubits);
if inst.op.control_flow() {
if let OperationRef::Instruction(py_inst) = inst.op.view() {
let raw_blocks = py_inst.instruction.getattr(py, "blocks")?;
let circuit_to_dag = CIRCUIT_TO_DAG.get_bound(py);
for raw_block in raw_blocks.bind(py).iter().unwrap() {
let block_obj = raw_block?;
let block = block_obj
.getattr(intern!(py, "_data"))?
.downcast::<CircuitData>()?
.borrow();
let new_dag: DAGCircuit =
circuit_to_dag.call1((block_obj.clone(),))?.extract()?;
let wire_map = (0..block.num_qubits())
.map(|inner| {
let outer = qubits[inner];
match wire_map {
Some(wire_map) => wire_map[outer.index()],
None => outer,
}
})
.collect::<Vec<_>>();
let res = recurse(py, &new_dag, edge_set, Some(&wire_map))?;
if res.is_some() {
return Ok(res);
}
for (node, inst) in dag.op_nodes(false) {
let qubits = dag.get_qargs(inst.qubits);
if inst.op.control_flow() {
if let OperationRef::Instruction(py_inst) = inst.op.view() {
let raw_blocks = py_inst.instruction.getattr(py, "blocks")?;
let circuit_to_dag = CIRCUIT_TO_DAG.get_bound(py);
for raw_block in raw_blocks.bind(py).iter().unwrap() {
let block_obj = raw_block?;
let block = block_obj
.getattr(intern!(py, "_data"))?
.downcast::<CircuitData>()?
.borrow();
let new_dag: DAGCircuit =
circuit_to_dag.call1((block_obj.clone(),))?.extract()?;
let wire_map = (0..block.num_qubits())
.map(|inner| {
let outer = qubits[inner];
match wire_map {
Some(wire_map) => wire_map[outer.index()],
None => outer,
}
})
.collect::<Vec<_>>();
let res = recurse(py, &new_dag, edge_set, Some(&wire_map))?;
if res.is_some() {
return Ok(res);
}
}
} else if qubits.len() == 2
&& (dag.calibrations_empty() || !dag.has_calibration_for_index(py, node)?)
&& !check_qubits(qubits)
{
return Ok(Some((
inst.op.name().to_string(),
[qubits[0].0, qubits[1].0],
)));
}
} else if qubits.len() == 2
&& (dag.calibrations_empty() || !dag.has_calibration_for_index(py, node)?)
&& !check_qubits(qubits)
{
return Ok(Some((
inst.op.name().to_string(),
[qubits[0].0, qubits[1].0],
)));
}
}
Ok(None)
Expand Down
Loading

0 comments on commit 87ebea3

Please sign in to comment.