Parameterized measurement patterns
The current implementation only allows the creation of measurement patterns with a fixed set of parameters (rotation angles). However, for quantum-classical algorithms such as VQE or QAOA, it is desirable to run many identical quantum circuits (patterns) with differences in the rotation parameters only. For this, it would be ideal to have the option to create parameterized measurement patterns whose rotation angles can be changed after transpilation and optimizations (standardization, signal shifting, Pauli measurement preprocessing, and more).
For this, I propose to:
- write
Parameterclass in a new filegraphix/parameter.pyto instantiate parameter objects (c.f. qiskit parameterized circuit), which has parameter name and bounds (we assume them to be real values). - Modify
graphix.transpiler.Circuitclass to accept parameterized rotation angles - modify methods of
graphix.pattern.Patternandgraphix.simulator.Simulatorclasses to ask for parameter value substitution when running the pattern as keyword argument, if there are parameterized commands. - modify
graphix.pattern.pauli_nodes()to consider parameterized measurement commands as non-Pauli measurements - add methods to
graphix.pattern.Patternto inspect parameterized patterns
I may be missing other fixes and additions that need to be done to make this feature work, which I would be keen to discuss on this issues page.
Please see our contribution guide before starting, and do not forget to comment on this issue to show your interest. We are always happy to answer questions, so do not hesitate to ask on this page.
Thank you for your interest, and good luck!
Hey!
I was thinking about what we were trying to do here. (** Btw the link you have provided in the comment appears to be void ; ) Anyway I understand that we need to have placeholder for a paramter as they have in the qiskit't parameter class (Parameter, ParameterExpression, etc. link ). As for it, were you aiming to have a new class alike it here or maybe we can simply use the availiable implementation from qiskit ? In my experience the class is pretty much adaptable to the things we might need here, or so it seems to me. (** I have looked through the details of the transpiler and the simulator module , and goinf through the pattern module at the moment.) , though I think we will need to develop a method similar to bind_parameters / assign_parameters for the the Pattern class as well.
Hi @pafloxy! sorry for the delay responding.
I was thinking about what we were trying to do here. (** Btw the link you have provided in the comment appears to be void ; ) Anyway I understand that we need to have placeholder for a paramter as they have in the qiskit't parameter class (
Parameter,ParameterExpression, etc. link ).
yes exactly - and thanks for pointing out about the link:)
As for it, were you aiming to have a new class alike it here or maybe we can simply use the availiable implementation from qiskit ? In my experience the class is pretty much adaptable to the things we might need here, or so it seems to me. (** I have looked through the details of the
transpilerand thesimulatormodule , and goinf through thepatternmodule at the moment.) , though I think we will need to develop a method similar tobind_parameters/assign_parametersfor the thePatternclass as well.
My opinion is that it would be best to have a new class (some simplified version of qiskit implementation I imagine) here, for better maintainability. I agree we would need new methods in Pattern class.
My opinion is that it would be best to have a new class (some simplified version of qiskit implementation I imagine) here, for better maintainability. I agree we would need new methods in
Patternclass.
Though I am well acquainted with the ParameterExpression class of qiskit and different ways to use it, understanding the class itself is something I find rather complicaetd and the source code itself isn't super clear either. In any case, what would be the some of the properties of thic class that you think would be important for our purpose (or alternatively which are the things that we do not require) ?
methods like assign, bind, a way to give bounds for parameters, and checks for parameter name conflicts would be essential, and we would also need checks in parameter passing, for example checking for unkown type and nan.
Hello again. I was still working to get this done, so for now I was trying to get a skeleton structure of the parameterization using the qiskits Parameter class from which then I will try to reduce the redundant dependencies to turn it finally into a something that would suitable for your package. Reason being I would like to segregate the problem into two complementary parts, one about desigining an independent paramter class and the other about including paramter based methods to pre-existing modules like pattern, 'simulator', etc.
But anyway, i had one question again. As it seems one is supposed to use Pattern.add method to add a particular command however i see that at some places you are directly changing the attributes (for eg. chech the snapshot from generator module
where you are using pattern.seq.append(["M", j, "XY", angles[j], [], []]) )
don't you think we should somehow freeze such attributes and intoroduce methods instead to change them. This might be impotrant in our case becasue upon appending a measurement command we need to be certain if it is parameterized or not.
I undersand that pauli_nodes should distinguish the paramterized nodes. But I was also wondering whether it would be nice to keep track of the parameterised measurement commands under a seperate data structure withing the Pattern class ? Probably such might be essential somehow incase we do not assign values to all the paramters at once but only to some of them. Are we considering such cases ?
Also I think it would be possible to partially simulate the measurement pattern as well incase we make sure that for every qubit over which the measurement command is to be simulated, all qubits sending correction to that qubits must have a values assigned to their parameter as well ?
Let me know what you think of this. :)
@pafloxy Hi, thanks for these nice progress and I agree we really should start using pattern.add almost everywhere once we implement the parameter class. The part of code you showed indeed is a good example of where we should update. I can think of all generator.py, transpiler.py and some part of pattern.py eg where we reconstruct pattern after pauli measurement preprocessing and perhaps after LocalPattern standardization/signal shifting.
I undersand that pauli_nodes should distinguish the paramterized nodes.
yes indeed, we need to treat parameterized nodes as non-Pauli-measured node which you cannot preprocess.
But I was also wondering whether it would be nice to keep track of the parameterised measurement commands under a seperate data structure withing the Pattern class ? Probably such might be essential somehow incase we do not assign values to all the paramters at once but only to some of them. Are we considering such cases ?
I personally think we do not need to worry about such case for now - I believe standard quantum-classical algorithms like QAOA update parameters at once before each 'quantum' step?
Also I think it would be possible to partially simulate the measurement pattern as well incase we make sure that for every qubit over which the measurement command is to be simulated, all qubits sending correction to that qubits must have a values assigned to their parameter as well ?
I do not fully understand the case considered, but for simplicity I think we can ask for all the parameter to be assigned a value before simulated. As you say, it would be nice to be able to step into the pattern simulation (say stopping in the middle of pattern execution and inspecting the state vector, etc c.f. #35).
Thank you for reading my comments,
I personally think we do not need to worry about such case for now - I believe standard quantum-classical algorithms like QAOA update parameters at once before each 'quantum' step?
Yes that is usually the case, however I was considering parameterized measurement patterns in their general sense than just the measuremnt patterns generated by translating directly from usual variational circuits.
I do not fully understand the case considered, but for simplicity I think we can ask for all the parameter to be assigned a value before simulated. As you say, it would be nice to be able to step into the pattern simulation (say stopping in the middle of pattern execution and inspecting the state vector, etc c.f. #35).
Yeah that could certainly be the case but actually I thought it would be more general to consider the case where not all the parameters are assigned numerical values.
Since the resource state preparation posses no parameterisation the only issues can arisr from simulating measurements. Problems in such a case would arise if we are to simulate the measurement on a node which recieves correction from node which has a paramterised measurement angle.
But I think this could be avoided easily. For a pattern to be simulatable, we can restrict the order of parameter assignment in the same fashion as dictated by the flow conditions, then we will never end up in a case like the one above.
For ease of computations we can simply add some slight restrictions, say we can restrict value assignment such that nodes in layer $l$ could be assigned values only if all nodes in previous layers are assigned values as well. Which will make our pattern simulatable till the $l$ th layer from the input layer.
** layer $l$ arises from the partial order.
Problems in such a case would arise if we are to simulate the measurement on a node which recieves correction from node which has a paramterised measurement angle.
thanks for your reply, I might add one comment here. The byproduct correction is not dependent on the angle of the measurement basis, if the plane (e.g. XY, YZ) is fixed.
It is indeed not impossible to make the measurement plane parameterizable but I personally think that would be an overkill for now. Parametrising the measurement plane and making it dynamic during execution means the choice of byproduct command (being one of X, Z or XZ) will also become adaptive - and this makes process like standardization a lot more complex.
thanks for your reply, I might add one comment here. The byproduct correction is not dependent on the angle of the measurement basis, if the plane (e.g. XY, YZ) is fixed.
I understand that. But what I meant was that even if we stick to just XY plane measurements we still have to decide wtheter the measuremnt result was 1/0. And uptil now I assume that only nodes with measurement angle assigned would be measured.
So if the byproduct on a later node is affected by the the measurement of a parameterised node we won't be able to simulate the measurement on the later node.
Isn't that right ? I'll be glad if you can clarify :)
@pafloxy Okay now I see the situation you were considering! So you were thinking of a case where you have parameterised measurement commands in the pattern, and want to execute with parameter kept as a placeholder, and once execution reached this command you skip over the it to move to next one, leaving the measurement signal of skipped-over measurement also a placeholder.
I do not think such a case is realistic - any measurement command that depend on this 'placeholder measurement signal' cannot be performed because you cannot determine the measurement angle (it could be possible by popping out such signal as X or Z commands and moving it around somehow, but perhaps not very likely). What you can do instead is to defer the parameterised measurement by moving the command towards the end of the pattern, following commutations rules, and while keeping eye not to move past measurement command that depend on the signal from it.
Please let me know if I understood your intention correctly, and feel free to ask further questions.
I do not think such a case is realistic - any measurement command that depend on this 'placeholder measurement signal' cannot be performed because you cannot determine the measurement angle (it could be possible by popping out such signal as X or Z commands and moving it around somehow, but perhaps not very likely).
Actually my intent was exactly to avoid this scenario by not allowing measurement of nodes that recieve corrections from paramterised nodes, unless numerical values have been assigned to all such parameters. And I wasn't really thinking of keeping placeholder for measurement results as to me they seems a bit complicated to reason about.
What you can do instead is to defer the parameterised measurement by moving the command towards the end of the pattern, following commutations rules, and while keeping eye not to move past measurement command that depend on the signal from it.
Yeah that was similar what i had in mind. But to make the task easier I suggested that we can use the layered structure of the graph due to the partial order (i'll assume we number them from the input to the output i.e input layer -> l= 0). Say to simulate the measuremnts upto layer l = l', it is sufficient to have that all measurement parameters for nodes in l=0 to l= l'-1 has been assigned. Albeit this is not a necessary condition, and it would have been ideal to do it the way you suggested but it is way easier to implement I believe and yet better than requireing us to assign all the paramters at once.
I undertand that you have methods to make the dependency beteween nodes more explicit i.e via the CommandNode and LocalPattern type data-structures, and maybe what you suggest is doable as well, but I will need to check if I understand them well as I was just focussing on the usual Pattern class up untill now. !
Let me know if I'm wrong at some point.
@pafloxy
I suggested that we can use the layered structure of the graph due to the partial order (i'll assume we number them from the input to the output i.e input layer -> l= 0). Say to simulate the measuremnts upto layer l = l', it is sufficient to have that all measurement parameters for nodes in l=0 to l= l'-1 has been assigned.
I think you're totally right, we can indeed do that! Other than LocalPattern structure, I think Pattern.get_layers() would help identifying the dependency structure. We would need to make the pattern executions, such as PatternSimulator.run(), be aware of layer information, however that should be an easy addition to the method.
Thank you for commenting.
But I also had another question in this regard, I am having facing some ambiguity here. Firstly I see we have checks against the flow conditions whenever we are generating a pattern from a given graph, say as in generator.py, but the Pattern class by itself doesn't have any contraints when we are appending a measurement command. Do you think it might lead to issues in some cases? I understand it won't be much of a problem if we don't directly construct measuremnt commands.
Usually the layered structure is constructed directly from the flow finding algorithm which I guess is supposed to find the maximally-delayed flow (ref. S.Pedrix, M.Mhalla), which might not be unique. Here we have get_layers in gflow.py module which is just a decorator for the output l_k from the flow finding algorithm as well as there is a method Patter.get_layers() which works using Pattern._get_dependency() method. I am not sure why we need to different approaches. Also it seems we get different outputs from them in certain cases.
Eg. The QAOA example from your examples/qaoa.py has structure
using
f, l_k = flow(g, {0, 1, 2, 3}, set(pattern.output_nodes))
gflow.get_layers(l_k)[1]
gives
whereas
pattern.get_layers()[1]
gives
Shouldn't they be same smehow ? let me know if I'm missing something.
@pafloxy Patter.get_layers() works even when there's no flow or gflow (which can happen after pauli measurement preprocessing or with manual pattern generation), whereas get_layers in gflow.py requires either flow or gflow.
I see. I remember reading in your paper that flow isn't conserved upon the Pauli Optimisation scheme the paper introduces but yet it is still deterministic.
But wasn't gflow a necessary condition for determinism ?
the way we preprocess the Pauli measurement is by 'running' the pattern on the graph state simulator, instead of rewriting the pattern, for faster processing. Because we also preprocess measurements on the input node (if it is Pauli-measured), I am not aware of general proof that resulting graph should have gflow (though it's quite possible that it is the case for most graphs). Determinism is guaranteed by the determinism of the pattern before the proprocessing.
Either way, we wanted to make sure Pattern.get_layers() work for any type of patterns and thus our way of implementation. Also, gflow-finding algorithm is rather slow, and may not be so practical for working out layers for large patterns.
More importantly, perhaps, the dependency layer you get from gflow.get_layers for a given pattern may not be accurate, except when pattern was generated based on the gflow of the graph (ie using generator.generate_from_graph) - because what you get from gflow.get_layers the specific maximally-delayed one and there exist other ways for nodes to depend with each other to be deterministic.
So, I would think it's best to trust Pattern.get_layers() if you already have a pattern, if not re-generating pattern from gflow. What do you think?
I see, Actually to my knowledge I undesrtood the "flow" conditions to be the only criterion to be satisfied for determinism, maybe I should have alook at the reference for graph state simulator at M. Elliot, B. Eastin & C. Caves.
Most probably you are right, but I will just need some time to think about it, in a theoretical perspective, most probably I'll get back with some questions. Sorry about that : /
But as for the code design I think we can proceed to a certain extent.
Another question, Why do we need this operation while appending Measurement Commands
self.output_nodes.remove(cmd[1]) ?
output_nodes are the nodes that are left unmeasured after the pattern completed. So adding measurement command means that the corresponding node is not an output.
That's for sure, but incase we already instantiate the Pattern class by passing a set of output_nodes or are not aware of them, and then only add measurement commands manually on just non-output qubits this might lead to errors. Since the qubit to be removed is not already part of the output_nodes.
For example something like this pat = Pattern(4, output_nodes= {0})
And I think this would have caused you problems in the generator.generate_from_graph method incase you have had used the add method rather than directly appending commands to the seq since there too you do not have a predefined output_nodes when instantiate the Pattern
I see, that’s a good point. Would it work if we change the implementation of generate_from_flow, such that we don’t initialise Pattern with output_nodes specified and let add function take care of it?
generate_from_flow ?
sorry, that was a typo, I meant generate_from_graph.
What I rather think is that we should remove the self.output_nodes.remove() thing cause I don't see much use to it, as it will only let the code work as expected if we have all the nodes marked as output_nodes initially, which doesn't seem very reasonable to me.
We can simply raise an error when user tries to add a measurement command on an node which is part of the output_nodes. As well add an class method to convert an node to output node incase we want to do so at any stage of the computation.
And yes we will need to freeze the attribrutes like seq and restrict modifying it exclusively via the add method ( do we have something to remove commands too ?), this will be important as it lets us flag out paramterised measurement commands directly within the method.
I also realised that the deadline to make PRs and to be eligible for the bounty would be within just a day, which ovbiously won't be sufficient for me to completely implement the required things :smiling_face_with_tear: . And I don't reallly want to make it in a rush and turn in it into something not very useful by the end.
But I belive you will agree that I had to do some hard digging to find out all such small issues here and there, so I would be glad if there is an workaround to the deadline thing. I remain interested in developing the code concerned either way since it is important for my own work somehow. :slightly_smiling_face:
@pafloxy re unitaryhack, let met first assign you to the issue, and if you could quickly open a draft PR (perhaps add the draft implementation with qiskit module there to show it's in progress), that may also help. I will then talk to UnitaryHack admin to ask how to proceed in this kind of case. let me answer the earlier comment later today.
I can do that, but I have it in a notebook since it was just about trying things out, than to directly intergrate it into the code. I think at this point it iis better that I write it in words rather than to modify the code directly, since I will need to some methods in the Pattern class to track the measurements that are paramterised.
regarding the hack - the admin said you have until 20th June to complete PR to be eligible for the bounty. see the message by UnitaryHack team on another PR on this repo: https://github.com/TeamGraphix/graphix/pull/56#issuecomment-1589831563
I see, I'll try to get to it.
What about the modification suggested to the Pattern.add() method ?
I think we'll need to settle such small things first, to get it done by the deadline 🙃
Sorry for the delay responding re the add method!
What I rather think is that we should remove the self.output_nodes.remove() thing cause I don't see much use to it, as it will only let the code work as expected if we have all the nodes marked as output_nodes initially, which doesn't seem very reasonable to me.
Agreed, I think it’s better not to do self.output_nodes.remove() in pattern.add. Let’s remove that part.
We can simply raise an error when user tries to add a measurement command on a node which is part of the output_nodes. As well add a class method to convert a node to output node incase we want to do so at any stage of the computation.
Also agreed. That would be a good way I think, I look forward to seeing the implementation which would give better sense of how it goes.
And yes we will need to freeze the attribrutes like seq and restrict modifying it exclusively via the add method ( do we have something to remove commands too ?), this will be important as it lets us flag out paramterised measurement commands directly within the method.
Yes, we would need to freeze the seq attribute. No we don’t have a method to remove commands.