-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Oxidize NLocal
& family
#13310
Merged
Merged
Oxidize NLocal
& family
#13310
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
2bc79f1
NLocal in rust -- first version
Cryoris 8649036
fix check for _standard_gate
Cryoris 3b3d69c
clean up the entanglement
Cryoris b1a7f49
add n-local family members
Cryoris de4f3b6
add pending deprecations
Cryoris 5501bc5
Merge branch 'main' into rust/nlocal
Cryoris 2eea43e
update docstrings
Cryoris dbfe5f0
add tests
Cryoris 2b84ece
fix docs
Cryoris a69a780
knowing how to read helps
Cryoris d14d584
attempt no. 1 to fix the docs
Cryoris 5fc47ee
review comments
Cryoris 9da7087
Merge branch 'main' into rust/nlocal
Cryoris 49a1af9
Merge branch 'main' into rust/nlocal
Cryoris 069f45c
review comments
Cryoris 54b487c
Merge branch 'rust/nlocal' of github.com:Cryoris/qiskit-terra into ru…
Cryoris 6950476
remove box and fix typecheck
Cryoris 0f690f3
remove Iterable instancechecks
Cryoris File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
// 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 pyo3::{ | ||
prelude::*, | ||
types::{PyList, PyTuple}, | ||
}; | ||
use qiskit_circuit::{ | ||
circuit_instruction::OperationFromPython, | ||
operations::{Operation, Param, StandardGate}, | ||
packed_instruction::PackedOperation, | ||
}; | ||
use smallvec::SmallVec; | ||
|
||
use crate::{circuit_library::entanglement::get_entanglement, QiskitError}; | ||
|
||
#[derive(Debug, Clone)] | ||
pub enum BlockOperation { | ||
Standard { gate: StandardGate }, | ||
PyCustom { builder: Py<PyAny> }, | ||
} | ||
|
||
impl BlockOperation { | ||
pub fn assign_parameters( | ||
&self, | ||
py: Python, | ||
params: &[&Param], | ||
) -> PyResult<(PackedOperation, SmallVec<[Param; 3]>)> { | ||
match self { | ||
Self::Standard { gate } => Ok(( | ||
(*gate).into(), | ||
SmallVec::from_iter(params.iter().map(|&p| p.clone())), | ||
)), | ||
Self::PyCustom { builder } => { | ||
// the builder returns a Python operation plus the bound parameters | ||
let py_params = | ||
PyList::new_bound(py, params.iter().map(|&p| p.clone().into_py(py))).into_any(); | ||
|
||
let job = builder.call1(py, (py_params,))?; | ||
let result = job.downcast_bound::<PyTuple>(py)?; | ||
|
||
let operation: OperationFromPython = result.get_item(0)?.extract()?; | ||
let bound_params = result | ||
.get_item(1)? | ||
.iter()? | ||
.map(|ob| Param::extract_no_coerce(&ob?)) | ||
.collect::<PyResult<SmallVec<[Param; 3]>>>()?; | ||
|
||
Ok((operation.operation, bound_params)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
#[pyclass] | ||
pub struct Block { | ||
pub operation: BlockOperation, | ||
pub num_qubits: u32, | ||
pub num_parameters: usize, | ||
} | ||
|
||
#[pymethods] | ||
impl Block { | ||
#[staticmethod] | ||
#[pyo3(signature = (gate,))] | ||
pub fn from_standard_gate(gate: StandardGate) -> Self { | ||
Block { | ||
operation: BlockOperation::Standard { gate }, | ||
num_qubits: gate.num_qubits(), | ||
num_parameters: gate.num_params() as usize, | ||
} | ||
} | ||
|
||
#[staticmethod] | ||
#[pyo3(signature = (num_qubits, num_parameters, builder,))] | ||
pub fn from_callable( | ||
py: Python, | ||
num_qubits: i64, | ||
num_parameters: i64, | ||
builder: &Bound<PyAny>, | ||
) -> PyResult<Self> { | ||
if !builder.is_callable() { | ||
return Err(QiskitError::new_err( | ||
"builder must be a callable: parameters->(bound gate, bound gate params)", | ||
)); | ||
} | ||
let block = Block { | ||
operation: BlockOperation::PyCustom { | ||
builder: builder.to_object(py), | ||
}, | ||
num_qubits: num_qubits as u32, | ||
num_parameters: num_parameters as usize, | ||
}; | ||
|
||
Ok(block) | ||
} | ||
} | ||
|
||
// We introduce typedefs to make the types more legible. We can understand the hierarchy | ||
// as follows: | ||
// Connection: Vec<u32> -- indices that the multi-qubit gate acts on | ||
// BlockEntanglement: Vec<Connection> -- entanglement for single block | ||
// LayerEntanglement: Vec<BlockEntanglement> -- entanglements for all blocks in the layer | ||
// Entanglement: Vec<LayerEntanglement> -- entanglement for every layer | ||
type BlockEntanglement = Vec<Vec<u32>>; | ||
pub(super) type LayerEntanglement = Vec<BlockEntanglement>; | ||
|
||
/// Represent the entanglement in an n-local circuit. | ||
pub struct Entanglement { | ||
// Possible optimization in future: This eagerly expands the full entanglement for every layer. | ||
// This could be done more efficiently, e.g., by creating entanglement objects that store | ||
// their underlying representation (e.g. a string or a list of connections) and returning | ||
// these when given a layer-index. | ||
entanglement_vec: Vec<LayerEntanglement>, | ||
} | ||
|
||
impl Entanglement { | ||
/// Create an entanglement from the input of an n_local circuit. | ||
pub fn from_py( | ||
num_qubits: u32, | ||
reps: usize, | ||
entanglement: &Bound<PyAny>, | ||
entanglement_blocks: &[&Block], | ||
) -> PyResult<Self> { | ||
let entanglement_vec = (0..reps) | ||
.map(|layer| -> PyResult<LayerEntanglement> { | ||
if entanglement.is_callable() { | ||
let as_any = entanglement.call1((layer,))?; | ||
let as_list = as_any.downcast::<PyList>()?; | ||
unpack_entanglement(num_qubits, layer, as_list, entanglement_blocks) | ||
} else { | ||
let as_list = entanglement.downcast::<PyList>()?; | ||
unpack_entanglement(num_qubits, layer, as_list, entanglement_blocks) | ||
} | ||
}) | ||
.collect::<PyResult<_>>()?; | ||
|
||
Ok(Self { entanglement_vec }) | ||
} | ||
|
||
pub fn get_layer(&self, layer: usize) -> &LayerEntanglement { | ||
&self.entanglement_vec[layer] | ||
} | ||
|
||
pub fn iter(&self) -> impl Iterator<Item = &LayerEntanglement> { | ||
self.entanglement_vec.iter() | ||
} | ||
} | ||
|
||
fn unpack_entanglement( | ||
num_qubits: u32, | ||
layer: usize, | ||
entanglement: &Bound<PyList>, | ||
entanglement_blocks: &[&Block], | ||
) -> PyResult<LayerEntanglement> { | ||
entanglement_blocks | ||
.iter() | ||
.zip(entanglement.iter()) | ||
.map(|(block, ent)| -> PyResult<Vec<Vec<u32>>> { | ||
get_entanglement(num_qubits, block.num_qubits, &ent, layer)?.collect() | ||
}) | ||
.collect() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe?:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah nice I didn't know about this! But it doesn't like that because it looks like
from_iter
expects&Param
and.cloned
returnsParam
types 🤔There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But doesn't
p.clone()
also return aParam
instance too? It's not a big deal, I kind of wrote this assuming clippy would be grumpy about it.