Open avasch01 opened 4 years ago
Hi @avasch01, Thanks for filing! I would like to start with the concrete need to satisfy, rather than focusing on a particular implementation - let's task about the functionality (what do you want the capability to be?) so we can better assess what options we have.
I believe the feature request is the following: You have a custom simulator that implements a certain set of quantum operations. You would like to use those operations as part of your Q# program. One way to do that today is to have a Q# library that defines these operations as intrinsic and define suitable C# classes that provide the necessary implementation as part of you simulator implementation. Today, this would look something like this:
// inner class within your simulator:
public class QSimR : Intrinsic.R
{
// assuming your simulator is written in C++
[DllImport(QSIM_DLL_NAME, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl, EntryPoint = "R")]
private static extern void R(uint id, Pauli basis, double angle, uint qubit);
// ... constructor etc
public override Func<(Pauli, double, Qubit), QVoid> Body => (_args) =>
{
// ... implementation invoking R
};
// ... more code
This is not particularly straight-forward to implement, so the ask would be to make this easier. Would something that instead looks like this for your simulator implementation meet your need:
// no class needed, but instead directly within your simulator:
[DllImport(QSIM_DLL_NAME, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl, EntryPoint = "R")]
private static extern void _R(uint id, Pauli basis, double angle, uint qubit);
public override void R (Pauli axis, double angle, Qubit target)
{
// ... implementation invoking _R
};
A custom simulator then would only inherit from our base class, and implement whatever overrides it wants to specify explicitly. That should be fairly minimal in terms of the code that is needed to define a custom simulator.
Here are some thoughts I had on providing the boilerplate for quantum processor extensions. I'm hoping it can be fodder for further ideas and brainstorming.
We could define an interface called IQuantumProcessorExtensions
, and have SimulatorBase
inherit from that in addition to IQuantumProcessor
. This interface would actually be something that is then produced during our C# generation step, empty by default. Similarly, we'd have an IQuantumDispatcherExtensions
that the default dispatcher inherits from, also produced by C# generation and empty by default. Then as part of C# generation, we would look for methods with a special annotation, something like:
@ProcessorExtension("Csharp.Namespace.For.Extension")
operation W(q0 : Qubit, q1 : Qubit, q2 : Qubit) : Unit is Adj + Ctl {
body intrinsic;
}
Then the C# generation would automatically generate a processor extension and dispatcher extension that handle dispatching to a wrapper around an invocation of W
from the given namespace. We could also have an alternate version, @NativeProcessorExtension
that takes a dll name and any relevant signature info such that the generated processor extension instead wraps a call to a native method. In both managed and native code, we would establish a calling convention where the signature would include a reference/pointer to a simulator object and then the remainder of the arguments based on the operation definition.
This would hopefully meet the goals of having simple interface definition (just writing the Q# operation with the appropriate annotation and having the rest generated for you) and be compatible with C# or C++ depending on which annotation is used. Maybe even future annotations for extensions in other languages could be introduced, like Python or Rust, etc.
Possibly challenging would be the fact that these extensions would need to be static and thus everything they would need to access from the simulator would have to have appropriate accessors on the provided simulator object.
Thanks for sharing your thoughts, @swernli!
@ProcessorExtension("Csharp.Namespace.For.Extension") operation W(q0 : Qubit, q1 : Qubit, q2 : Qubit) : Unit is Adj + Ctl { body intrinsic; }
My main concern with this approach would be that the Q# source code is aware of the C# runtime implementation, making it difficult for the Q# intrinsic definitions to be used across different simulators or hardware. We should likely add something along these lines to the Q# API Design Principles, but ideally, Q# APIs shouldn't encourage writing quantum programs that can only be simulated, or that behave in fundamentally different ways when run on a simulator and on actual hardware.
From that perspective, it's important that operations which enable fast-paths for simulation have pure Q# fallbacks, and don't expose implementation details of the runtime.
@cgranade That’s an excellent point. Putting any C# or C++ references directly in the Q# limits its flexibility and portability. I wonder if there is a more generic, portable way to add additional info to operations defined as “body intrinsic” that limits the infrastructure/boilerplate developers need to implement themselves and also makes it easier to incorporate new native code into an existing simulator. I’ll think about that some more.
I wonder if there is a more generic, portable way to add additional info to operations defined as “body intrinsic” that limits the infrastructure/boilerplate developers need to implement themselves and also makes it easier to incorporate new native code into an existing simulator.
Thanks for the discussion, @swernli! To expand a bit on my thoughts above, at least part of the issue is that in many cases, for portability you'll want simulator-specific intrinsics to have pure-Q# fallbacks. In principle, that allows for a Q# program to be operationally correct across simulators (and eventually hardware) while still allowing a fast-path for simulators that admit a more efficient implementation. That's distinct from the use case of exposing machine-specific functionality (e.g.: Mølmer–Sørensen), but covers cases such as estimating measurement frequencies (common in variational algorithms) and implementing state preparation and unitary evolution without needing to explicitly run each step in the pure-Q# decomposition.
As a consequence, the simulator-specific Q# operations will quite often need to be correct Q# operations in and of themselves, such that we'll need to keep that in mind with a proposed solution.
@cgranade @swernli I actually don't have an issue with annotating the Q# code in this case, though instead of being intrinsic it should have an implementation. Here is my thinking: If you want to build a custom simulator that exposes a particular gate that is not usually defined as part of our intrinsics, then you also by necessity need to provide a library with it defining that gate. That library then should also have the Q# implementation, such that the code would run anywhere. I don't see in that sense how the code based on that library wouldn't remain generally applicable. Attributes in a sense are supposed to contain whatever metadata you would like to annotate.
@cgranade @swernli @bettinaheim Stefan's proposal is very similar to a feature that was in the original Q# spec, but never implemented, called externals. The notion was that you could define a function or operation that was implemented by an arbitrary .NET routine, with the routine name provided as part of the declaration. Moving the routine name into an attribute makes a lot of sense.
I like @cgranade 's suggestion of allowing such callables to also provide a Q# implementation as a fallback. I could imagine, in such a case, providing some sort of runtime (or perhaps compile-time) switch to say whether such an operation should be executed using the external routine or the callback.
I think this is pretty easy to address as part of the code gen. There are several options for the generated code; I'll write up a proposal.
@cgranade @swernli @bettinaheim Here's a proposal:
Microsoft.Quantum.Intrinsics
namespace in terms of the physical operations for the target. This would have to be in the Microsoft.Quantum.Intrinsics
namespace, and effectively replaces the default implementation of that namespace.Microsoft.Quantum.Intrinsics
namespace.Microsoft.Quantum.Simulation.Common.SimulatorBase
. For each physical operation that the target supports, an appropriate subclass of AbstractCallable
is generated. For each specialization of that operation that is supported, a stub is generated in the subclass that unwraps the argument tuple, calls a method on the generated target-wide class, and wraps the result as needed. The SimulatorBase
subclass would have all of the stub methods that were required as part of the callable generation.SimulatorBase
subclass should be implemented as a subclass of the imported target's SimulatorBase
subclass. This makes it easy to add an emulated operation to an existing simulator.There are (obviously) some details to be worked out, but I think this approach is both general and simple. I like the idea of explicitly defining an "interface" for each target -- I think @vadym-kl and I talked about that several years ago. This would also let us drop the QuantumProcessor
special case, since it would just become another generated target simulator class.
Thoughts?
I think we are getting to a good plan. Let's pursue this as part of our larger refactoring to pull in target specific decompositions.
Please describe what you would like the feature to accomplish. We need to provide extensibility to IQuantumProcessor interface. Users, especially researchers, should be able to add custom gates/operations to their quantum processors, simulators, emulators, hardware translators, etc. Use case: if some simulator supports a custom gate BCZ, there should be a way ot add it to the interface, implement it on the side of the simulator, and expose it to Q# users so that they can use it when they run against the simulator that supports it. Example: A simulator may support applying a unitary to the state many times more efficiently that decomposing it into elementary operations. It needs a way to define an API "ApplyUnitary(Martix, ArrayOfQubits). Example: An emulator might want to implement a super-efficient way to do state preparation in one shot. Need a custom function that takes in the state and array of qubits. Since we cannot anticipate all usages of this - research is open-ended - we need a flexible way for researchers to add any operation they want to experiment with it.
Describe the solution you'd like IQuantumProcessor should add APIs:
Describe alternatives you've considered IQuantumProcessor is easy to use to implement simulators. We considered alternatives such as extensibility through SimulatorBase, but it requires too much code to write, and our customers refuse to participate. With the proposed approach it is very easy to implement a simulator, and to add a custom operation. This approach allows for easy mirroring of this solution to the native C code through interop. Some other considered solutions relied on features such as reflection, so they did not allow for this interoperability. So, for alternative solutions, I have two requirements: