Upgrading PyQIR#

PyQIR 0.8#

PyQIR 0.7 was the last version of PyQIR to support QIR evaluation. Simulation of QIR is now available via the qir-runner sparse simulator.

PyQIR 0.7#

Packages#

PyQIR 0.6 was the last version of PyQIR to use three packages (pyqir-evaluator, pyqir-generator, and pyqir-parser) and a metapackage (pyqir). PyQIR 0.7 instead uses only a single package (pyqir) that has the functionality of all previous packages.

If you imported the pyqir.generator or pyqir.parser modules, then the same or an equivalent API is available in the pyqir module. If you imported the pyqir.evaluator module, it is still available under the same name with no API changes.

Generator#

IR and bitcode conversion#

The functions bitcode_to_ir and ir_to_bitcode were removed because the new Module class has the same functionality. Module supports both parsing and generating QIR. For example, instead of:

from pyqir.generator import bitcode_to_ir, ir_to_bitcode

ir = bitcode_to_ir(bitcode, "module_name")
bitcode = ir_to_bitcode(ir, "module_name")

# Or with a source filename:
ir = bitcode_to_ir(bitcode, "module_name", "source_filename")

Use this:

from pyqir import Context, Module

ir = str(Module.from_bitcode(Context(), bitcode, "name"))
bitcode = Module.from_ir(Context(), ir, "name").bitcode

# Or with a source filename:
m = Module.from_bitcode(Context(), bitcode, "module_name")
m.source_filename = "source_filename"
ir = str(m)

Types#

If you generated programs with externally-linked functions, then you used the pyqir.generator.types module to describe the type of the functions. This module has been removed. Types need to be created differently because they now directly contain LLVM type objects, which require an LLVM context.

PyQIR 0.6

PyQIR 0.7

pyqir.generator.types.VOID

pyqir.Type.void(context)

pyqir.generator.types.BOOL

pyqir.IntType(context, 1)

pyqir.generator.types.Int(width)

pyqir.IntType(context, width)

pyqir.generator.types.DOUBLE

pyqir.Type.double(context)

pyqir.generator.types.QUBIT

pyqir.qubit_type(context)

pyqir.generator.types.RESULT

pyqir.result_type(context)

pyqir.generator.types.Function(params, ret)

pyqir.FunctionType(ret, params)

There are two ways to get a context object:

  1. Use the context property on SimpleModule. For example:

    from pyqir import SimpleModule, Type
    module = SimpleModule("name", num_qubits=1, num_results=1)
    void = Type.void(module.context)
    
  2. Create one yourself. But you also need to give the context you created to SimpleModule. For example:

    from pyqir import Context, SimpleModule, Type
    context = Context()
    module = SimpleModule("name", num_qubits=1, num_results=1, context=context)
    void = Type.void(context)
    

Parser#

PyQIR 0.7 unified parsing and code generation into a single API that is designed to support both. This makes PyQIR much more powerful and will enable workflows that involve inspecting, running passes on, or otherwise transforming QIR that is parsed or generated using PyQIR. This means that the API for pyqir-parser had to be completely redesigned, which unfortunately makes upgrading challenging. Here are some tips.

Modules#

Use Module.from_bitcode or Module.from_ir instead of the QirModule constructor. See IR and bitcode conversion.

Entry points and interop-friendly functions#

Instead of QirModule.entrypoint_funcs, QirModule.interop_funcs, or QirModule.get_funcs_by_attr, filter the Module.functions list instead. For example:

entry_point = next(filter(pyqir.is_entry_point, module.functions))
interops = filter(pyqir.is_interop_friendly, module.functions)

Instructions#

The instruction class hierarchy was trimmed down significantly. Most subclasses of QirInstr were removed. The surviving subclasses are Call, FCmp, ICmp, Phi and Switch.

For any other instruction, it should be possible to use the base Instruction class to do anything that was previously possible. Use the opcode property to check what kind of instruction it is, and the operands property to read all of the values that the instruction references.

The successors property is a subset of operands and contains just the values that are basic blocks, which can be useful to follow control flow with br instructions. For example, if you have a terminator instruction term, then you can get the instructions of its first successor with term.successors[0].instructions.

Qubit and result IDs#

In PyQIR 0.6, QirQubitConstant and QirResultConstant were subclasses of QirOperand. Instead, you can try to extract a static qubit or result ID from any value using pyqir.qubit_id(value) and pyqir.result_id(value). If the value isn’t the right kind, it will return None.

Strings#

In PyQIR 0.6, you could read a global string constant from a string value using QirModule.get_global_bytes_value(value). This is possible now using pyqir.extract_byte_string(value).

Examples#

To see examples of how the new parser API can be used, take a look at test_parser.py. You can also compare it with test_parser_api.py from PyQIR 0.6 for a before-and-after view of the same test cases using both the old and new APIs.