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.