iqsharp icon indicating copy to clipboard operation
iqsharp copied to clipboard

from ... import ... behavior doesn't work for Q# libraries

Open guenp opened this issue 5 years ago • 7 comments

Describe the bug The following code:

import qsharp
qsharp.packages.add("Microsoft.Quantum.Chemistry")
qsharp.reload()
from Microsoft.Quantum.Chemistry.JordanWigner.VQE import EstimateEnergy

throws this error:

ModuleNotFoundError: No module named 'Microsoft'

Expected behavior I would expect to be able to use from ... import ... with libraries.

System information Running in iqsharp-based Docker container.

guenp avatar Sep 14 '20 17:09 guenp

@rmshaffer it seems that this is not the intended usage of the library functions, instead, the recommended route is to create a Q# application and import the operation there. I think it would make sense to make it easier to import these kinds of generic library functions and call them directly from Python, and there are several ways to achieve that, one of which is to create shims for each library operation/function we want to explicitly support. What do you think would be the best way to resolve this issue?

guenp avatar Oct 01 '20 19:10 guenp

@guenp Thanks for the ping on this. So, this won't directly solve the problem, but there is already code in IQ# for auto-generating Q# wrappers around an arbitrary Q# operation, which is similar to what you are proposing, I believe: https://github.com/microsoft/iqsharp/blob/249490833c5d4c0728c911eab796b7b22394ffd6/src/Core/Compiler/CompilerService.cs#L148-L168

We could consider exposing some functionality similar to that via Python. Do you have any thoughts on what a convenient way to consume this might look like?

rmshaffer avatar Oct 01 '20 20:10 rmshaffer

@rmshaffer Oh great! How is this functionality currently used? And how does this work if the operation contains "unsafe" variables (such as qubit registers), does it pass the error from the entrypoint wrapper directly to the user or will it fail upon generation? I think the most transparent way to me would be either to have a generate_qsharp_program function where you can specify the namespace and name of the operation you want to access, and the target file path where it can then write the files, or a import_qsharp_operation function that saves the entrypoint to some temp location. But I am open to other suggestions.

guenp avatar Oct 01 '20 20:10 guenp

That functionality is currently used for Azure Quantum job submission. Azure Quantum only supports submitting operations/functions marked with the @EntryPoint() attribute, but from IQ# we wanted to enable submitting any operation in the notebook. So we use that code to make a wrapper operation and compile it. The "unsafe" variable validation happens only at compile time in this case - the Q# compiler will throw an error if the @EntryPoint() signature contains things like qubit registers.

Yes, I think your idea makes sense. It's nice in that it doesn't change the semantics of how to interact with Q# libraries, but it removes the pain in manually creating boilerplate code.

Here's a somewhat-concrete proposal.

We already have feature request https://github.com/microsoft/iqsharp/issues/25, which is for a %printcode magic command in IQ#. That command would print out the Q# code that implements the given callable. (Presumably we will also add something like a .sourcecode() method on QSharpCallable in Python which returns the code as a string.)

My proposal would be to have some flag passed to this command, e.g., %printcode --wrapper, where instead of printing the callable's code itself, we print out a wrapper for it. And then maybe some nice API like qsharp.generate_wrapper() (or maybe there's a better name) around this in Python, which returns the generated code as a string.

As an example, consider the operation you mentioned, Microsoft.Quantum.Chemistry.JordanWigner.VQE.EstimateEnergy. Then, to run resource estimation on this operation from Python, you could do something like:

wrapper_code = qsharp.generate_wrapper("Microsoft.Quantum.Chemistry.JordanWigner.VQE.EstimateEnergy")
estimate_energy_op = qsharp.compile(wrapper_code)
result = estimate_energy_op.estimate_resources(jwHamiltonian=..., nSamples=...)

Or, of course, you could take the wrapper_code and save it to a file, modify it, etc.

Thoughts/feedback on that approach, @guenp?

rmshaffer avatar Oct 02 '20 13:10 rmshaffer

@rmshaffer Yes I think that approach would work well! Does the wrapper code currently consist of one or multiple files? I would think that aside from the entrypoint program we would also need to include the .csproj file.

guenp avatar Oct 05 '20 20:10 guenp

The wrapper code itself is just a single operation or function that wraps the one of interest. I believe for this purpose we wouldn't even need the @EntryPoint() attribute, since it's not required for the scenario of simply compiling and simulating Q# operations directly from Python.

As for the .csproj -- I see, there's a little bit more to this scenario than I was initially thinking. For the simple case I outlined above, there's no need for actually saving a .csproj file or a .qs file because you could just import the package and then compile the code dynamically. So the full script could look like:

import qsharp
qsharp.packages.add("Microsoft.Quantum.Chemistry")
wrapper_code = qsharp.generate_wrapper("Microsoft.Quantum.Chemistry.JordanWigner.VQE.EstimateEnergy")
estimate_energy_op = qsharp.compile(wrapper_code)
result = estimate_energy_op.estimate_resources(jwHamiltonian=..., nSamples=...)

However, as you mentioned the idea of actually saving the generated files for future use -- then yes, you'd want something a little bit more sophisticated that could generate a .csproj and a .qs file using a specified namespace. Then, with those files present, the script would look much cleaner:

import qsharp
from MyNamespace import EstimateEnergy
result = EstimateEnergy.estimate_resources(jwHamiltonian=..., nSamples=...)

In that case then maybe, exactly as you suggested (sorry, I was slow to understand), there could be a utility function that takes:

  • the source package name
  • the fully-qualified target operation name
  • the desired namespace name
  • the target folder path (perhaps defaulting to the current folder if you don't specify one).

And then the boilerplate .csproj, .qs, and even a stub .py could be generated there automatically. Maybe this utility function is called something like qsharp.utilities.generate_python_wrapper; inside a utilities module to make it clear that it's more of a helper tool than something to be used in an actual program.

It'd be a nice starting point even for users who want to do something more complicated than just a wrapper.

rmshaffer avatar Oct 05 '20 21:10 rmshaffer

Yes indeed that's what I was thinking, apologies for not being more clear. I like the approach you suggest as well especially because it's much simpler. In line with your suggestion, instead of saving to a temp location we can indeed just compile directly. On top of that I was thinking to have a convenience function import_qsharp_operation or simply import_operation that abstracts away the part where you compile the Q# code:

import qsharp
estimate_energy_op = qsharp.import_operation(namespace="Microsoft.Quantum.Chemistry.JordanWigner.VQE", operation="EstimateEnergy")

where

def import_operation(namespace: str, operation: str):
   wrapper_code = qsharp.generate_wrapper(operation, namespace)
   return qsharp.compile(wrapper_code)

For the second use case of generating the boilerplate code, as per your suggestion I agree this could be a nice starting point if people want to tinker with the generated Q# code. If I understood correctly, are you suggesting something like this?

def generate_qsharp_program(operation: str, source_namespace: str, target_namespace: str, path: str = None):
   wrapper_code = qsharp.generate_wrapper(operation, source_namespace)
   entrypoint_code, project_code = qsharp.generate_project(source_namespace, target_namespace)
   save_files(entrypoint_code, project_code, path) # save the files at the given location or at current directory if path is None

guenp avatar Oct 05 '20 21:10 guenp

Thank you for the feedback! After consideration we've decided to stick with the current design on this, with the workarounds as noted in the above thread.

anjbur avatar Sep 27 '22 16:09 anjbur