-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Port synth_cnot_phase_aam to Rust #12937
base: main
Are you sure you want to change the base?
Port synth_cnot_phase_aam to Rust #12937
Conversation
Pull Request Test Coverage Report for Build 11683949353Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
Thanks for working on this! I have not yet looked closely at the PR (and it's marked as work-in-progress), but I would like to suggest to set up a proper directory structure from the beginning. You have added a new |
Thanks Noted !!!
…On Sun, 11 Aug, 2024, 6:58 pm Alexander Ivrii, ***@***.***> wrote:
Thanks for working on this! I have not yet looked closely at the PR (and
it's marked as work-in-progress), but I would like to suggest to set up a
proper directory structure from the beginning. You have added a new
linear_phase directory (which is great), and now you want to add mod.rs
to that (and not modify files in linear directory). Please feel free to
ask any additional questions.
—
Reply to this email directly, view it on GitHub
<#12937 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/ANBTUUIEN2PKYVENG4DGVHDZQ5RGDAVCNFSM6AAAAABMJKPVUSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEOBSG43DCNJUGU>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
I see that you have some lint issues, and would like to suggest to use |
Thanks for both the information, I think both probably are not mentioned in
the contributing.md for qiskit
…On Wed, 21 Aug, 2024, 12:36 pm Shelly Garion, ***@***.***> wrote:
I see that you have some lint issues, and would like to suggest to use cargo
clippy.
In addition, could you compare the performance of your rust code with the
previous python code?
you should then compile it in the release mode: python setup.py
build_rust --inplace --release
—
Reply to this email directly, view it on GitHub
<#12937 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/ANBTUUM66T4Z5NQCE4AFHFDZSQ353AVCNFSM6AAAAABMJKPVUSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMBRGI4TCOBRGY>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
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.
This looks like a nice progress. I have briefly experimenting with your code: please see a more detailed comment above.
def synth_cnot_pahse_aam_xlated(cnots: list[list[int]], angles: list[str], section_size:int 2) -> QuantumCircuit | ||
_circuit_data = synth_cnot_phase_aam_xlated(cnots, angles, section_size) | ||
qc_from_rust = QuantumCircuit._from_circuit_data(_circuit_data) | ||
return qc_from_rust | ||
|
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.
This function has a small typo, you want section_size: int = 2
.
More importantly, there are still some problems with passing cnots
and angles
from the Python code to the Rust code. I am also learning Rust/Pyo3 and am not sure if the following is optimal, but one possible way to fix these is to modify the Python function to
def synth_cnot_phase_aam_xlated(cnots: list[list[int]], angles: list[str], section_size: int = 2) -> QuantumCircuit:
cnots = np.asarray(cnots, dtype=np.uint8)
_circuit_data = _synth_cnot_phase_aam_xlated(cnots, angles, section_size)
qc_from_rust = QuantumCircuit._from_circuit_data(_circuit_data)
return qc_from_rust
and the Rust function to:
#[pyfunction]
#[pyo3(signature = (cnots, angles, section_size=2))]
pub fn synth_cnot_phase_aam(
py: Python,
cnots: PyReadonlyArray2<u8>,
angles: &Bound<PyList>,
section_size: Option<i64>,
) -> PyResult<CircuitData> {
...
let mut rust_angles: Vec::<String> = Vec::new();
for angle in angles.iter() {
let rust_string: String = angle.extract()?;
rust_angles.push(rust_string);
}
...
(you could write the above loop more compactly, but let's not worry about this right now).
Now running the first example in test_cnot_phase_synthesis.py
makes the Rust code throw a panic exception, so you will need to debug what's going on.
One pitfall that I noticed when experimenting with the Python version of synth_cnot_phase_aam
is that it clears the list of angles
passed to it as input, so pay attention to this. :)
@ShellyGarion , @HuangJunye , @alexanderivrii , |
Thank you for opening a new pull request. Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient. While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone. One or more of the following people are relevant to this code:
|
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.
I didn't finish all of it yet, but here's already some comments 🙂
} | ||
|
||
type Instructions = (StandardGate, SmallVec<[Param; 3]>, SmallVec<[Qubit; 2]>); | ||
pub fn _synth_cnot_count_full_pmh(mat: Array2<bool>, sec_size: Option<i64>) -> Vec<Instructions> { |
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.
Some comments on this:
- It's likely better to keep this returning an iterator instead of collecting into a vector, this way the
CircuitData
constructor can simply iterate over the inputs. If you do need a vector you could collect where you call this function 🙂 - Since this is a Rust internal function, it might be better to have
section_size
be ausize
directly. We were only usingi64
as input from Python, but logically, this should be an unsigned integer (also is there a reason for renaming? 🙂) - We might want to promote this as public function, so I would think we don't want a leading underscore in the name here. To avoid the naming conflict you could maybe rename it to
synth_pmh
?
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.
1> Apart from the name changes and typo fix, I am reverting changes made in "156da41", there is a very tiny bit of performance cost, and the return type looked complex, so I decided to revert, please let me know if you want those changes anyway :)
2> I decided to keep it as Option<i64>
because we have to convert from Options<i64>
to Option<usize>
in both occurrences in pmh.rs
and cnot_phase_synth.rs
as soon as we get data from python, and then send it to synth_pmh
, furthermore its being converted to usize
anyway in line: 185. Also no reason behind renaming it
3> Done!
#[pyo3(signature = (cnots, angles, section_size=2))] | ||
pub fn synth_cnot_phase_aam( | ||
py: Python, | ||
cnots: PyReadonlyArray2<u8>, |
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.
Is there a specific reason for u8
? 🙂
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.
in cnot_phase_synth.rs
we have _s.row(_j).sum() as usize == _s.row(_j).len()
. and the trait num_traits::Zero is not implemented for bool
. So, we need cnots
to be some number, since it is only 0 or 1, I chose u8
to save on memory because 1 is the max value it can get.
let icnot = s_cpy.column(index).to_vec(); | ||
if icnot == state.row(qubit_idx).to_vec() { | ||
match rust_angles.remove(index) { | ||
gate if gate == "t" => instructions.push(( |
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.
Could this (and below) be written more concisely by matching the gate name to the StandardGate
and then pushing the gate afterwards (to avoid repeating the same instruction creation)?
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.
I chose not to change it, below code is slowing things down, from x309 to x290.
I tried doing it this way
let _data = match rust_angles.remove(index) {
gate if gate == "t" => (StandardGate::TGate, smallvec![]),
gate if gate == "tdg" => (StandardGate::TdgGate, smallvec![]),
gate if gate == "s" => (StandardGate::SGate, smallvec![]),
gate if gate == "sdg" => (StandardGate::SdgGate, smallvec![]),
gate if gate == "z" => (StandardGate::ZGate, smallvec![]),
angles_in_pi => (
StandardGate::PhaseGate,
smallvec![Param::Float(
(angles_in_pi.parse::<f64>()?) % PI
)],
),
};
instructions.push((_data.0, _data.1, smallvec![Qubit(_ep as u32)]));
let mut rust_angles: Vec<String> = angles | ||
.iter() | ||
.filter_map(|data| data.extract::<String>().ok()) | ||
.collect(); |
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.
I know the Python code does the same, but this seems highly volatile and we should change this: the input type should be properly differentiated in either floats or strings, but not a mix. You can leave this for now @MozammilQ but maybe we can find a better solution @alexanderivrii and @ShellyGarion 🙂
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.
Indeed, it's quite strange that the API of angles
can accept strings e.g. s, t, sdg, tdg or z
or float
.
in the test we see that the angles are only strings, e.g. s, t, sdg, tdg or z
. It would be good to add some tests of angles which are float
to check that the code works well for any angle.
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.
@ShellyGarion ,
I had already taken care of this by angles = [angle if isinstance(angle, str) else f"{angle}" for angle in angles]
in cnot_phase_synth.py
. This converts all occurrences of float in the list of angles to String which rust subsequently accepts.
Anyways, I have added a test. Take a note of the change in circuit. I have just changed the circuit in the docstring to the actual circuit I am getting here locally, but not have changed the circuit in the code, just to make sure the code works for the circuit generated from the older python implementation of synth_cnot_phase_aam
, there by proving the circuit generated by rust is merely "equivalent" to the circuit generated by python version and not different.
Nevertheless, I am surprised because there is no random steps in the rust or the python implementation of the algorithm, so both the rust and python implementation should produce exact same circuit, but this is not the case.
Maybe, the difference is because when I append the output of synth_pmh
I just do .rev()
but not the actual .inverse()
in python space as it has been done originally in python implementation.
I decided to do only .rev()
thinking inverse of a cx
is itself. So, just reversing the strings of cx
s in the circuit should be equivalent to doing .inverse()
on QuantumCircuit in python space. Please, guide me if you feel I am lost :)
crates/accelerate/src/synthesis/linear_phase/cnot_phase_synth.rs
Outdated
Show resolved
Hide resolved
} | ||
|
||
let state_bool = state.mapv(|x| x != 0); | ||
let mut instrs = _synth_cnot_count_full_pmh(state_bool, section_size) |
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.
Does this need to be mutable?
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.
290 | instructions.append(&mut instrs); ^^^cannot borrow as mutable
because, append
moves all data from instrs
to instructions
and empties instrs
.
.iter() | ||
.filter_map(|data| data.extract::<String>().ok()) | ||
.collect(); | ||
let mut state = Array2::<u8>::eye(num_qubits); |
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.
Could this be made into a matrix of bools right away? It seems like it is treated as one.
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.
if icnot == state.row(_ep).to_vec()
, so state
has to be u8
.
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.
@Cryoris ,
just want to let you know that I value your comments and not agreeing to some is not questioning you rather its having an opportunity to learn, please guide me if you feel I am lost :)
I am still learning rust.
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.
Also, I am measuring performance by taking average of 6 runs of code in the picture in the summary of this PR.
I am taking average to compensate for time cpu could spend doing something else.
.into_iter() | ||
.rev() | ||
.collect(); | ||
let mut instrs = synth_pmh(state_bool, section_size).rev().collect(); |
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.
Is it possible to write the instructions
as iterator, instead of creating it as vector and pushing each element? That would allow us to avoid the collections and have CircuitData
directly consume the iterator (and likely be a bit faster 🙂)
let insts = instructions.chain(synth_pmh(...).rev());
CircuitData::from_standard_gates(..., insts,...);
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.
I have implemented Iterator
for all the loops in the logic, the performance seems to be same, but the code has become very complex. and, the program crashes for inputs when num_qubits * depth > 14900. (This is stack-overflow, it worked when I increased the stack to 40MB by doing ulimit -s 40960
)
at the end I have no option other than to collect these iterator into a vector, because I have to get the residual state out of the iterator to feed into synth_pmh which has to be chained into final instructor iterator.
Still, using vec.push(...) doesn't seem bad, please have a look at it and guide me what to change next :)
|
||
let rust_angles = angles | ||
.iter() | ||
.filter_map(|data| data.extract::<String>().ok()) |
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.
doing
.filter_map(|data| data.extract::<String>().or_else(|_| data.extract::<f64>().map(|f| f.to_string())).ok())
or, doing
angles = [angle if isinstance(angle, str) else f"{angle}" for angle in angles]
in qiskit/synthesis/linear_phase/cnot_phase_synth.py
takes the same amount of time to execute, so, I decided to keep this check in python space only! :)
Summary
Details and comments
fixes #12230