Skip to content

Commit

Permalink
Merge branch 'main' into move-basis-search-and-visit
Browse files Browse the repository at this point in the history
  • Loading branch information
raynelfss committed Sep 27, 2024
2 parents 40931a4 + 43feab3 commit b2dc941
Show file tree
Hide file tree
Showing 17 changed files with 409 additions and 183 deletions.
42 changes: 24 additions & 18 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ from system-wide packages. This way, we avoid inadvertently becoming dependent o
particular system configuration. For developers, this also makes it easy to maintain multiple
environments (e.g. one per supported Python version, for older versions of Qiskit, etc.).



### Set up a Python venv

All Python versions supported by Qiskit include built-in virtual environment module
Expand Down Expand Up @@ -107,17 +105,17 @@ pip install -e .
Qiskit is primarily written in Python but there are some core routines
that are written in the [Rust](https://www.rust-lang.org/) programming
language to improve the runtime performance. For the released versions of
qiskit we publish precompiled binaries on the
Qiskit we publish precompiled binaries on the
[Python Package Index](https://pypi.org/) for all the supported platforms
which only requires a functional Python environment to install. However, when
building and installing from source you will need a rust compiler installed. You can do this very easily
building and installing from source you will need a Rust compiler installed. You can do this very easily
using rustup: https://rustup.rs/ which provides a single tool to install and
configure the latest version of the rust compiler.
[Other installation methods](https://forge.rust-lang.org/infra/other-installation-methods.html)
exist too. For Windows users, besides rustup, you will also need install
the Visual C++ build tools so that Rust can link against the system c/c++
libraries. You can see more details on this in the
[rustup documentation](https://rust-lang.github.io/rustup/installation/windows.html).
[rustup documentation](https://rust-lang.github.io/rustup/installation/windows-msvc.html).

If you use Rustup, it will automatically install the correct Rust version
currently used by the project.
Expand Down Expand Up @@ -145,7 +143,7 @@ Python gate objects when accessing them from a `QuantumCircuit` or `DAGCircuit`.
This makes a tradeoff between runtime performance for Python access and memory
overhead. Caching gates will result in better runtime for users of Python at
the cost of increased memory consumption. If you're working with any custom
transpiler passes written in python or are otherwise using a workflow that
transpiler passes written in Python or are otherwise using a workflow that
repeatedly accesses the `operation` attribute of a `CircuitInstruction` or `op`
attribute of `DAGOpNode` enabling caching is recommended.

Expand Down Expand Up @@ -187,8 +185,8 @@ please ensure that:
which will run these checks and report any issues.

If your code fails the local style checks (specifically the black
code formatting check) you can use `tox -eblack` to automatically
fix update the code formatting.
or Rust code formatting check) you can use `tox -eblack` and
`cargo fmt` to automatically fix the code formatting.
2. The documentation has been updated accordingly. In particular, if a
function or class has been modified during the PR, please update the
*docstring* accordingly.
Expand Down Expand Up @@ -396,11 +394,6 @@ it has been tagged:

reno report --version 0.9.0

At release time ``reno report`` is used to generate the release notes for the
release and the output will be submitted as a pull request to the documentation
repository's [release notes file](
https://github.com/Qiskit/qiskit/blob/master/docs/release_notes.rst)

#### Building release notes locally

Building The release notes are part of the standard qiskit documentation
Expand Down Expand Up @@ -572,10 +565,11 @@ Note: If you have run `test/ipynb/mpl_tester.ipynb` locally it is possible some

### Testing Rust components

Rust-accelerated functions are generally tested from Python space, but in cases
where there is Rust-specific internal details to be tested, `#[test]` functions
can be included inline. Typically it's most convenient to place these in a
separate inline module that is only conditionally compiled in, such as
Many Rust-accelerated functions are generally tested from Python space, but in cases
where new Rust-native APIs are being added, or there are Rust-specific internal details
to be tested, `#[test]` functions can be included inline. It's typically most
convenient to place these in a separate inline module that is only conditionally
compiled in, such as

```rust
#[cfg(test)]
Expand All @@ -587,6 +581,9 @@ mod tests {
}
```

For more detailed guidance on how to add Rust testing you can refer to the Rust
documentation's [guide on writing tests](https://doc.rust-lang.org/book/ch11-01-writing-tests.html).

To run the Rust-space tests, do

```bash
Expand Down Expand Up @@ -652,6 +649,15 @@ rather than via `tox`. If you have installed the development packages in your py
`pip install -r requirements-dev.txt`, then `ruff` and `black` will be available and can be run from
the command line. See [`tox.ini`](tox.ini) for how `tox` invokes them.

### Rust style and lint

For formatting and lint checking Rust code, you'll need to use different tools than you would for Python. Qiskit uses [rustfmt](https://github.com/rust-lang/rustfmt) for
code formatting. You can simply run `cargo fmt` (if you installed Rust with the
default settings using `rustup`), and it will update the code formatting automatically to
conform to the style guidelines. This is very similar to running `tox -eblack` for Python code. For lint checking, Qiskit uses [clippy](https://github.com/rust-lang/rust-clippy) which can be invoked via `cargo clippy`.

Rust lint and formatting checks are included in the the `tox -elint` command. For CI to pass you will need both checks to pass without any warnings or errors. Note that this command checks the code but won't apply any modifications, if you need to update formatting, you'll need to run `cargo fmt`.


## Building API docs locally

Expand Down Expand Up @@ -733,7 +739,7 @@ developers to test the release ahead of time. When the pre-release is tagged the
automation will publish the pre-release to PyPI (but only get installed on user request),
create the `stable/*` branch, and generate a pre-release changelog/release page. At
this point the `main` opens up for development of the next release. The `stable/*`
branches should only receive changes in the form of bug fixes at this point. If there
branches should only receive changes in the form of bug fixes at this point. If there
is a need additional release candidates can be published from `stable/*` and when the
release is ready a full release will be tagged and published from `stable/*`.

Expand Down
23 changes: 11 additions & 12 deletions crates/accelerate/src/basis/basis_translator/basis_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ use std::cell::RefCell;
use hashbrown::{HashMap, HashSet};
use pyo3::prelude::*;

use crate::equivalence::{CircuitRep, EdgeData, Equivalence, EquivalenceLibrary, Key, NodeData};
use qiskit_circuit::operations::{Operation, Param};
use crate::equivalence::{EdgeData, Equivalence, EquivalenceLibrary, Key, NodeData};
use qiskit_circuit::operations::Operation;
use rustworkx_core::petgraph::stable_graph::{EdgeReference, NodeIndex, StableDiGraph};
use rustworkx_core::petgraph::visit::Control;
use rustworkx_core::traversal::{dijkstra_search, DijkstraEvent};
use smallvec::SmallVec;

use super::compose_transforms::{BasisTransformIn, GateIdentifier};

#[pyfunction]
#[pyo3(name = "basis_search")]
Expand All @@ -38,7 +39,7 @@ use smallvec::SmallVec;
pub(crate) fn py_basis_search(
py: Python,
equiv_lib: &mut EquivalenceLibrary,
source_basis: HashSet<(String, u32)>,
source_basis: HashSet<GateIdentifier>,
target_basis: HashSet<String>,
) -> PyObject {
basis_search(
Expand All @@ -52,7 +53,7 @@ pub(crate) fn py_basis_search(
.into_py(py)
}

type BasisTransforms = Vec<(String, u32, SmallVec<[Param; 3]>, CircuitRep)>;
type BasisTransforms = Vec<(GateIdentifier, BasisTransformIn)>;
/// Search for a set of transformations from source_basis to target_basis.
///
/// Performs a Dijkstra search algorithm on the `EquivalenceLibrary`'s core graph
Expand All @@ -68,10 +69,10 @@ pub(crate) fn basis_search(
) -> Option<BasisTransforms> {
// Build the visitor attributes:
let mut num_gates_remaining_for_rule: HashMap<usize, usize> = HashMap::default();
let predecessors: RefCell<HashMap<(String, u32), Equivalence>> =
let predecessors: RefCell<HashMap<GateIdentifier, Equivalence>> =
RefCell::new(HashMap::default());
let opt_cost_map: RefCell<HashMap<(String, u32), u32>> = RefCell::new(HashMap::default());
let mut basis_transforms: Vec<(String, u32, SmallVec<[Param; 3]>, CircuitRep)> = vec![];
let opt_cost_map: RefCell<HashMap<GateIdentifier, u32>> = RefCell::new(HashMap::default());
let mut basis_transforms: Vec<(GateIdentifier, BasisTransformIn)> = vec![];

// Initialize visitor attributes:
initialize_num_gates_remain_for_rule(equiv_lib.graph(), &mut num_gates_remaining_for_rule);
Expand Down Expand Up @@ -165,10 +166,8 @@ pub(crate) fn basis_search(
if let Some(rule) = predecessors.borrow().get(&gate) {
// TODO: Logger
basis_transforms.push((
gate_key.name.to_string(),
gate_key.num_qubits,
rule.params.clone(),
rule.circuit.clone(),
(gate_key.name.to_string(), gate_key.num_qubits),
(rule.params.clone(), rule.circuit.clone()),
));
}

Expand Down
192 changes: 192 additions & 0 deletions crates/accelerate/src/basis/basis_translator/compose_transforms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// This code is part of Qiskit.
//
// (C) Copyright IBM 2024
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE.txt file in the root directory
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
//
// Any modifications or derivative works of this code must retain this
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

use hashbrown::{HashMap, HashSet};
use pyo3::prelude::*;
use qiskit_circuit::circuit_instruction::OperationFromPython;
use qiskit_circuit::imports::{GATE, PARAMETER_VECTOR, QUANTUM_REGISTER};
use qiskit_circuit::parameter_table::ParameterUuid;
use qiskit_circuit::Qubit;
use qiskit_circuit::{
circuit_data::CircuitData,
dag_circuit::{DAGCircuit, NodeType},
operations::{Operation, Param},
};
use smallvec::SmallVec;

use crate::equivalence::CircuitFromPython;

// Custom types
pub type GateIdentifier = (String, u32);
pub type BasisTransformIn = (SmallVec<[Param; 3]>, CircuitFromPython);
pub type BasisTransformOut = (SmallVec<[Param; 3]>, DAGCircuit);

#[pyfunction(name = "compose_transforms")]
pub(super) fn py_compose_transforms(
py: Python,
basis_transforms: Vec<(GateIdentifier, BasisTransformIn)>,
source_basis: HashSet<GateIdentifier>,
source_dag: &DAGCircuit,
) -> PyResult<HashMap<GateIdentifier, BasisTransformOut>> {
compose_transforms(py, &basis_transforms, &source_basis, source_dag)
}

pub(super) fn compose_transforms<'a>(
py: Python,
basis_transforms: &'a [(GateIdentifier, BasisTransformIn)],
source_basis: &'a HashSet<GateIdentifier>,
source_dag: &'a DAGCircuit,
) -> PyResult<HashMap<GateIdentifier, BasisTransformOut>> {
let mut gate_param_counts: HashMap<GateIdentifier, usize> = HashMap::default();
get_gates_num_params(source_dag, &mut gate_param_counts)?;
let mut mapped_instructions: HashMap<GateIdentifier, BasisTransformOut> = HashMap::new();

for (gate_name, gate_num_qubits) in source_basis.iter().cloned() {
let num_params = gate_param_counts[&(gate_name.clone(), gate_num_qubits)];

let placeholder_params: SmallVec<[Param; 3]> = PARAMETER_VECTOR
.get_bound(py)
.call1((&gate_name, num_params))?
.extract()?;

let mut dag = DAGCircuit::new(py)?;
// Create the mock gate and add to the circuit, use Python for this.
let qubits = QUANTUM_REGISTER.get_bound(py).call1((gate_num_qubits,))?;
dag.add_qreg(py, &qubits)?;

let gate = GATE.get_bound(py).call1((
&gate_name,
gate_num_qubits,
placeholder_params
.iter()
.map(|x| x.clone_ref(py))
.collect::<SmallVec<[Param; 3]>>(),
))?;
let gate_obj: OperationFromPython = gate.extract()?;
let qubits: Vec<Qubit> = (0..dag.num_qubits() as u32).map(Qubit).collect();
dag.apply_operation_back(
py,
gate_obj.operation,
&qubits,
&[],
if gate_obj.params.is_empty() {
None
} else {
Some(gate_obj.params)
},
gate_obj.extra_attrs,
#[cfg(feature = "cache_pygates")]
Some(gate.into()),
)?;
mapped_instructions.insert((gate_name, gate_num_qubits), (placeholder_params, dag));

for ((gate_name, gate_num_qubits), (equiv_params, equiv)) in basis_transforms {
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
}
})
.collect::<Vec<_>>();
for (node, params) in nodes_to_replace {
let param_mapping: HashMap<ParameterUuid, Param> = equiv_params
.iter()
.map(|x| ParameterUuid::from_parameter(x.to_object(py).bind(py)))
.zip(params)
.map(|(uuid, param)| -> PyResult<(ParameterUuid, Param)> {
Ok((uuid?, param.clone_ref(py)))
})
.collect::<PyResult<_>>()?;
let mut replacement = equiv.clone();
replacement
.0
.assign_parameters_from_mapping(py, param_mapping)?;
let replace_dag: DAGCircuit =
DAGCircuit::from_circuit_data(py, replacement.0, true)?;
let op_node = dag.get_node(py, node)?;
dag.py_substitute_node_with_dag(
py,
op_node.bind(py),
&replace_dag,
None,
true,
)?;
}
}
}
}
Ok(mapped_instructions)
}

/// `DAGCircuit` variant.
///
/// Gets the identifier of a gate instance (name, number of qubits) mapped to the
/// number of parameters it contains currently.
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)?;
}
}
}
}
Ok(())
}

/// `CircuitData` variant.
///
/// Gets the identifier of a gate instance (name, number of qubits) mapped to the
/// number of parameters it contains currently.
fn get_gates_num_params_circuit(
circuit: &CircuitData,
example_gates: &mut HashMap<GateIdentifier, usize>,
) -> PyResult<()> {
for inst in circuit.iter() {
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)?;
}
}
}
Ok(())
}
2 changes: 2 additions & 0 deletions crates/accelerate/src/basis/basis_translator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
use pyo3::prelude::*;

pub mod basis_search;
mod compose_transforms;

#[pymodule]
pub fn basis_translator(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(basis_search::py_basis_search))?;
m.add_wrapped(wrap_pyfunction!(compose_transforms::py_compose_transforms))?;
Ok(())
}
Loading

0 comments on commit b2dc941

Please sign in to comment.