-
Notifications
You must be signed in to change notification settings - Fork 90
Adding Type2 targeting package and tests #363
Conversation
ScottCarda-MS
left a comment
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've just skimmed through it, but it looks good to me. Seems that the vast majority of the changes here are just due to moving things around in the file structure and under different namespaces.
|
One thing these changes do not yet support is the |
|
To make this large and unwieldy PR a little bit easier to review, I'll call out the files that a new or significantly modified here...
|
ScottCarda-MS
left a comment
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.
reset review
Does this mean EntryPointDriver will need to create the simulator via reflection in the same way that it creates the target quantum machine for Azure Quantum? |
That’s certainly one possibility, yeah. The other idea I had but haven’t tried out yet is reworking the code so that the entry point driver only depends on |
That seems strange, since it's possible to use the simulators without needing the command-line interface that the EntryPointDriver provides? However, another option is that the Q# executable project would depend on both a simulator package and the driver package, and give simulator factories to the driver at runtime. How many different simulators will there be? My understanding was that we would still have just one simulator for all execution targets, is that incorrect? |
That's a cool idea. I was trying to think about how something like that might work, so I would really appreciate any ideas or advice you have on the design there.
Unfortunately, I'm disappointed by how this part of the work turned out. I was hoping to have a single simulator that would support all targets, and I still think we should keep an eye out for the opportunities to refactor everything into one simulator to handle all targeting packages. I'll give a summary here of the issues I ran into with supporting multiple targeting packages on the same simulator... Each targeting package collects the subset of intrinsics and decompositions that comprise it. The simulator needs to implement the intrinsics in C# so that they can wrap the appropriate calls into the native C++ simulator. However, some operations that are intrinsic for one package are decompositions for another, and in that case if the compiled simulator included C# implementations of those operations they would end up overriding the corresponding decomposition. To pick a specific example, |
|
@SamarSha Rather than figure the entry point handling all as part of this PR, I've added it as an additional ToDo on the #249 so that I can ensure it gets addressed before this feature branch is merged into master. I can try a couple of the ideas we've had so far and get that together as a separate PR soon. |
Sure, I haven't thought that much about it but I could elaborate if that'd be helpful, maybe it would be easier to chat more about it offline.
Thanks for the explanation. If I understood correctly, does that mean that if the simulator could tell if a particular operation had a Q# implementation or was an intrinsic, and could decide whether to implement something based on that information, then there could be only one simulator for all targets? (Or equivalently, if a Q# implementation of an operation had higher priority over the simulator implementation?) |
Yes, that is exactly what we would need. Inheritance with abstract methods and overrides doesn't give us a way to do that. The only mechanism C# offers that prevents a given implementation of a method from being overridden is |
I think you can override the current mechanism by modifying the |
Oh, that's an interesting idea! I think I could try enlightening |
|
@alan-geller Ok, I tried the approach you suggested and still didn't quite work, but it shows promise. The issue I ran into is that the simulator still defines it's classes as inheriting from the intrinsic operations, as in |
|
Turns out dotnet does not support multiple .csproj files in the same folder, so my attempts at a clever combination of the two simulator folders was only accidentally working. Thankfully it failed in the |
|
@alan-geller @SamarSha I think I've gotten these changes to the local maximum for the time being. Please let me know if you've got any other feedback or ideas for me to follow up on. I'm hoping I can get this merged today and move to the next target package. By way of summary, here's the issues we've identified as out of scope for this PR that I hope to handle later:
|
alan-geller
left a comment
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.
A few comments, nothing huge. I really like the overall PR, I think it really cleans up a lot of our infrastructure a lot.
| # This is problematic because we currently don't want to create a package for every dll. | ||
| # | ||
| # On the other hand, when creating a package using nuget pack, nuget does not | ||
| # identifies PackageReferences defined in the csproj, so all the dependencies |
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.
| # identifies PackageReferences defined in the csproj, so all the dependencies | |
| # identify PackageReferences defined in the csproj, so all the dependencies |
| // Licensed under the MIT License. | ||
|
|
||
| namespace Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators.Circuits { | ||
| operation IsingXX (theta : Double, qubit0 : Qubit, qubit1 : Qubit) : Unit is Ctl { |
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.
Why not Adj + Ctl? Exp is Adj+Ctl, so this could be, unless there's a reason for it not to be.
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've fixed these to all be Adj as well. Thanks!
| // Licensed under the MIT License. | ||
|
|
||
| namespace Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators.Circuits { | ||
| operation IsingYY (theta : Double, qubit0 : Qubit, qubit1 : Qubit) : Unit is Ctl { |
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.
Why not Adj + Ctl? Exp is Adj+Ctl, so this could be, unless there's a reason for it not to be.
| // Licensed under the MIT License. | ||
|
|
||
| namespace Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators.Circuits { | ||
| operation IsingZZ (theta : Double, qubit0 : Qubit, qubit1 : Qubit) : Unit is Ctl { |
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.
Why not Adj + Ctl? Exp is Adj+Ctl, so this could be, unless there's a reason for it not to be.
| select op; | ||
|
|
||
| foreach (var op in ops) | ||
| foreach (var op in ops.Where(o => o.BaseType.IsAbstract)) |
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 won't work for C# overrides of Q# operations; that is, for emulation. Emulations are almost always done with an explicit call to Register today, so I think it's OK for now, but I'm not sure this is the right answer for the long term; we need to think more about it.
I think this is covered by one of the associated issues you've opened.
| /// when used with the `Controlled` functor. | ||
| @EnableTestingViaName("Test.TargetDefinitions.R") | ||
| operation R (pauli : Pauli, theta : Double, qubit : Qubit) : Unit is Adj + Ctl { | ||
| if( pauli != PauliI ) { |
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.
Just a style question: I would do this as a top-level if with cases for all 4 Paulis, rather than the way you've structured it. Either are correct, of course; I suspect that there's a (probably negligible) perf benefit to my approach, with I as the final else case, because I suspect I is the least common case, but I might be wrong. I'm curious why you structured it this way.
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.
Good point! This code is adapted from some earlier research code, and I didn't notice that refactoring opportunity. I agree that a single if-elif-else statement would work better!
| /// The qubit whose state is to be reset to $\ket{0}$. | ||
| @EnableTestingViaName("Test.TargetDefinitions.Reset") | ||
| operation Reset (target : Qubit) : Unit { | ||
| let r = M(target); |
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.
Since this doesn't necessarily leave the qubit in the |0> state -- which I assume is OK based on the file name -- it would be good to add a comment explaining this.
| SpreadZ(qubits[1], qubits[2 .. Length(qubits) - 1]); | ||
| } | ||
| apply { | ||
| ExpNoIdUtil([PauliZ,PauliZ], theta, [qubits[0], qubits[1]], rotation); |
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.
Why not just the within/apply here directly, since you know that Length(paulis) will be two and paulis[0] will be PauliZ? The recursion seems more confusing than clarifying in this case.
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.
Agreed! I noticed this on a later pass in a different branch, but I'll go ahead and incorporate that change here.
| mutable denPow = denominatorPowerOfTwo; | ||
| while(num % 2 == 0) { | ||
| set num /= 2; | ||
| set denPow += 1; |
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.
Shouldn't that be -=? If you halve the numerator, you also want to halve the denominator, so you want to subtract 1 from the power in the denominator, I think.
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.
Oh my, I think you are correct. It worries me that didn't cause any failures... Let me make that change and rerun the tests to confirm.
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.
Actually, that change makes the tests fail, so we must not understand the convention of this helper. I'll put it back.
| } | ||
|
|
||
| @EnableTestingViaName("Test.TargetDefinitions.ArrayFromIndicesQ") | ||
| internal function ArrayFromIndiciesQ(values : Qubit[], indicies : Int[]) : Qubit[] { |
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 reason you can't combine this function and the previous one into an ArrayFromIndices>'T>?
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, it should be Indices, not Indicies.
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.
Agreed, I can combine this into a single generic helper, which per suggestion from @cgranade I can model on Subarray for now, and replace with a call to that when/if the function gets moved into qsharp-runtime somewhere.
That's a good catch, thanks! The Ising gates are sort of special cases of a two qubit Exp rotation, so should be adjointable with the negative angle. I'll make sure to add that in! |
| } | ||
|
|
||
| @EnableTestingViaName("Test.TargetDefinitions.ArrayFromIndicesP") | ||
| internal function ArrayFromIndiciesP(values : Pauli[], indicies : Int[]) : Pauli[] { |
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 Microsoft.Quantum.Arrays.Subarray from the standard library; should that be pulled back into runtime as well?
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.
Good question! I'll defer to @cgranade .
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.
Oh, that's a good idea! I don't think this change is the place to do it, but I do agree that as we find ourselves reimplementing useful helper functions from the library in the core or foundation packages we should seriously consider pulling the corresponding library function in.
|
Thanks for all the feedback and reviews! |
This changes continues building on the work in feature/decomp to enable targeting alternate gate sets via packages specifying the supported set of intrinsics and decompositions for that gate set. This introduces Type2, which targets a set of intrinsics that includes
SWAPand the 3 Pauli Ising gates. It also adds support for executing against this target package in the QDK simulator, and shares some of the existing intrinsic unitary tests to validate behavior of the package.See #249 for the rundown on what work is part of this overall feature.