Esempio n. 1
0
 def expand_arg_union(self, node):
     '''
     Expand a list of arguments to the union of the constituent qubits
     in QRegisters. So, given
         a = QRegister(2)
         b = QRegister(1)
     then we expand
         Barrier(a,b) -> Barrier("QBIT_1", "QBIT_2", "QBIT_3")
     Returns a list of ast.Call nodes with appropriate argument
     substitutions.
     '''
     expanded_args = []
     for arg in node.value.args:
         expanded_args.extend(self.expand_arg(arg))
     stmnt = quickcopy(node)
     stmnt.value.args = expanded_args
     return stmnt
Esempio n. 2
0
    def expand_qreg_call(self, node):
        '''
        Expands calls on pulse stubs to element-wise broadcast over
        QRegister arguments. So that given
            a = QRegister(2)
            b = QRegister(2)
        single-qubit calls expand like:
            X(a)
        becomes
            X(QBIT_1)
            X(QBIT_2)
        and two-qubit calls expand like:
            CNOT(a, b)
        becomes
            CNOT(QBIT_1, QBIT_3)
            CNOT(QBIT_2, QBIT_4)
        Control stubs are expanded over the union of consistuent qubits,
        so that
            Barrier(a, b)
        becomes
            Barrier(QBIT_1, QBIT_2, QBIT_3)

        Returns a list of ast.Call nodes with appropriate argument
        substitutions.
        '''
        new_stmnts = []
        # FIXME this assumes that nodes without a qgl_return attribute are
        # pulses. I did this because certain nodes are not getting decorated
        # with a qgl_return attribute (e.g. MEAS in assignments). Figure out
        # why...
        if hasattr(node.value,
                   'qgl_return') and node.value.qgl_return == 'control':
            new_stmnts.append(self.expand_arg_union(node))
        else:
            expanded_args = [self.expand_arg(a) for a in node.value.args]
            # TODO verify that QRegister lengths match
            for args in zip(*expanded_args):
                stmnt = quickcopy(node)
                stmnt.value.args = args
                new_stmnts.append(stmnt)

        return new_stmnts
Esempio n. 3
0
def compile_function(filename,
                     main_name=None,
                     toplevel_bindings=None,
                     saveOutput=False,
                     intermediate_output=None):

    NodeError.reset()

    print('\n\nCOMPILING [%s] main %s' %
          (filename, main_name if main_name else '(default)'))

    # Use whether intermediate_output is None to decide
    # whether to call printout blocks at all
    # Old code set intermediate_output to /dev/null

    if intermediate_output:
        try:
            intermediate_fout = open(intermediate_output, 'w')
        except BaseException as exc:
            NodeError.fatal_msg(None,
                                ('cannot save intermediate output in [%s]' %
                                 intermediate_output))
    else:
        intermediate_fout = None

    # Process imports in the input file, and find the main.
    # If there's no main, then bail out right away.

    try:
        rel_path = os.path.relpath(filename)
        filename = rel_path
    except Exception as e:
        # If that wasn't a good path, give up immediately
        NodeError.error_msg(
            None, "Failed to make relpath from %s: %s" % (filename, e))

    NodeError.halt_on_error()

    print('%s: CALLING IMPORTER' % datetime.now())
    importer = NameSpaces(filename, main_name)
    if not importer.qglmain:
        NodeError.fatal_msg(None, 'no qglmain function found')

    NodeError.halt_on_error()

    ptree = importer.qglmain

    if intermediate_output:
        ast_text_orig = pyqgl2.ast_util.ast2str(ptree)
        print(('%s: ORIGINAL CODE:\n%s' % (datetime.now(), ast_text_orig)),
              file=intermediate_fout,
              flush=True)

    # When QGL2 flattens various kinds of control flow and runtime
    # computations it emits QGL1 instruction that the user may not
    # have imported.
    #
    # TODO: this is a hack, but the approach of adding these
    # blindly to the namespace is also a hack.  This is a
    # placeholder until we figure out a cleaner approach.

    required_imports = [
        'Wait', 'Barrier', 'Goto', 'LoadCmp', 'CmpEq', 'CmpNeq', 'CmpGt',
        'CmpLt', 'BlockLabel', 'Store'
    ]

    modname = ptree.qgl_fname
    for symbol in required_imports:
        if not add_import_from_as(importer, modname, 'qgl2.qgl1', symbol):
            NodeError.error_msg(ptree, 'Could not import %s' % symbol)
    NodeError.halt_on_error()

    ptree1 = ptree

    # We may need to iterate over the inlining processes a few times,
    # because inlining may expose new things to inline.
    #
    # TODO: as a stopgap, we're going to limit iterations to 20, which
    # is enough to handle fairly deeply-nested, complex non-recursive
    # programs.  What we do is iterate until we converge (the outcome
    # stops changing) or we hit this limit.  We should attempt at this
    # point to prove that the expansion is divergent, but we don't
    # do this, but instead assume the worst if the program is complex
    # enough to look like it's "probably" divergent.
    #

    print('%s: CALLING INLINER' % datetime.now())
    MAX_ITERS = 20
    for iteration in range(MAX_ITERS):

        print('%s: ITERATION %d' % (datetime.now(), iteration))

        inliner = Inliner(importer)
        ptree1 = inliner.inline_function(ptree1)
        NodeError.halt_on_error()

        if intermediate_output:
            print(('INLINED CODE (iteration %d):\n%s' %
                   (iteration, pyqgl2.ast_util.ast2str(ptree1))),
                  file=intermediate_fout,
                  flush=True)

        if inliner.change_cnt == 0:
            NodeError.diag_msg(
                None, ('expansion converged after iteration %d' % iteration))
            break

    if iteration == (MAX_ITERS - 1):
        NodeError.error_msg(
            None,
            ('expansion did not converge after %d iterations' % MAX_ITERS))

    # transform passed toplevel_bindings into a local_context dictionary

    # FIXME: If the qgl2main provides a default for an arg
    # that is 'missing', then don't count it as missing

    arg_names = [x.arg for x in ptree1.args.args]
    if isinstance(toplevel_bindings, tuple):
        if len(arg_names) != len(toplevel_bindings):
            NodeError.error_msg(
                None,
                'Invalid number of arguments supplied to qgl2main (got %d, expected %d)'
                % (len(toplevel_bindings), len(arg_names)))
        local_context = {
            name: quickcopy(value)
            for name, value in zip(arg_names, toplevel_bindings)
        }
    elif isinstance(toplevel_bindings, dict):
        invalid_args = toplevel_bindings.keys() - arg_names
        if len(invalid_args) > 0:
            NodeError.error_msg(
                None, 'Invalid arguments supplied to qgl2main: {}'.format(
                    invalid_args))
        missing_args = arg_names - toplevel_bindings.keys()
        if len(missing_args) > 0:
            NodeError.error_msg(
                None,
                'Missing arguments for qgl2main: {}'.format(missing_args))
        local_context = quickcopy(toplevel_bindings)
    elif toplevel_bindings:
        NodeError.error_msg(
            None, 'Unrecognized type for toplevel_bindings: {}'.format(
                type(toplevel_bindings)))
    else:
        local_context = None
    NodeError.halt_on_error()

    evaluator = EvalTransformer(SimpleEvaluator(importer, local_context))

    print('%s: CALLING EVALUATOR' % datetime.now())
    ptree1 = evaluator.visit(ptree1)
    NodeError.halt_on_error()

    if DebugMsg.ACTIVE_LEVEL < 3:
        print('%s: EVALUATOR RESULT:\n%s' %
              (datetime.now(), pyqgl2.ast_util.ast2str(ptree1)))
    # It's very hard to read the intermediate form, before the
    # QBIT names are added, so we don't save this right now.
    # print(('EVALUATOR RESULT:\n%s' % pyqgl2.ast_util.ast2str(ptree1)),
    #         file=intermediate_fout, flush=True)

    # Dump out all the variable bindings, for debugging purposes
    #
    # print('EV total state:')
    # evaluator.print_state()

    evaluator.replace_bindings(ptree1.body)

    if DebugMsg.ACTIVE_LEVEL < 3:
        print('%s: EVALUATOR REBINDINGS:\n%s' %
              (datetime.now(), pyqgl2.ast_util.ast2str(ptree1)))
    if intermediate_output:
        print(
            ('EVALUATOR + REBINDINGS:\n%s' % pyqgl2.ast_util.ast2str(ptree1)),
            file=intermediate_fout,
            flush=True)

    # base_namespace = importer.path2namespace[filename]

    # if intermediate_output:
    #     text = base_namespace.pretty_print()
    #     print(('EXPANDED NAMESPACE:\n%s' % text),
    #           file=intermediate_fout, flush=True)

    new_ptree1 = ptree1

    # Try to flatten out repeat, range, ifs
    flattener = Flattener()
    print('%s: CALLING FLATTENER' % datetime.now())
    new_ptree2 = flattener.visit(new_ptree1)
    NodeError.halt_on_error()
    if intermediate_output:
        print(('%s: FLATTENED CODE:\n%s' %
               (datetime.now(), pyqgl2.ast_util.ast2str(new_ptree2))),
              file=intermediate_fout,
              flush=True)

    # TODO Is it ever necessary to replace bindings again at this point?
    # evaluator.replace_bindings(new_ptree2.body)
    # evaluator.get_state()

    if intermediate_output:
        print(('Final qglmain: %s\n' % new_ptree2.name),
              file=intermediate_fout,
              flush=True)

    new_ptree3 = new_ptree2

    # Done. Time to generate the QGL1

    # Try to guess the proper function name
    fname = main_name
    if not fname:
        if isinstance(ptree, ast.FunctionDef):
            fname = ptree.name
        else:
            fname = "qgl1Main"

    # Get the QGL1 function that produces the proper sequences
    print('%s: GENERATING QGL1 SEQUENCE FUNCTION' % datetime.now())
    qgl1_main = get_sequence_function(new_ptree3,
                                      fname,
                                      importer,
                                      evaluator.allocated_qbits,
                                      intermediate_fout,
                                      saveOutput,
                                      filename,
                                      setup=evaluator.setup())
    NodeError.halt_on_error()
    return qgl1_main
Esempio n. 4
0
    def group(node, local_vars=None):

        # We want to make a copy of params of the root node,
        # but we DO NOT need to recurse through the body of
        # the node (and this is potentially a time-sink).
        # So we grab a reference to the node body, reassign
        # the node body to the empty list, make a copy of
        # the node recursively, and then put the original
        # body back.
        #
        orig_body = node.body
        node.body = list()
        new_node = quickcopy(node)
        node.body = orig_body

        all_qbits = MarkReferencedQbits.marker(node,
                                               local_vars=local_vars,
                                               force_recursion=True)

        # TODO: need to check that it's a FunctionDef

        alloc_stmnts = list()
        body_stmnts = list()

        # Divide between qbit allocation and ordinary
        # statements.  This is ugly: it would be better
        # to move qbit allocation outside qgl2main. TODO
        #
        for stmnt in node.body:
            # if (isinstance(stmnt, ast.Assign) and
            #         stmnt.targets[0].qgl_is_qbit):
            if isinstance(stmnt, ast.Assign):
                alloc_stmnts.append(stmnt)
            else:
                body_stmnts.append(stmnt)

        new_groups = list()

        qbits = sorted(all_qbits)

        for i in range(len(qbits)):
            qbit = qbits[i]

            # An optimization: we need to make a copy of body_stmnts
            # so we can modify it, but for the last iteration, we can
            # cannibalize it and use the input list as our scratch_body.
            # In the (common) case that there's only one qbit, this
            # has a large effect on performance.
            #
            if i == (len(qbits) - 1):
                scratch_body = body_stmnts
            else:
                scratch_body = quickcopy(body_stmnts)

            pruned_body = QbitPruner(set([qbit])).prune_body(scratch_body)
            if not pruned_body:
                continue

            with_group = expr2ast('with group(%s): pass' % qbit)
            copy_all_loc(with_group, node, recurse=True)

            # Insert a special Barrier named "group_marker..."
            # at the start of each per-qbit group.
            # The compiler later looks for this to know
            # which sequence is for which Qubit.
            bid = BarrierIdentifier.next_bid()
            beg_barrier = AddSequential.make_barrier_ast([qbit],
                                                         with_group,
                                                         name='group_marker',
                                                         bid=bid)
            if DebugMsg.ACTIVE_LEVEL < 3:
                print("For qbit %s, inserting group_marker: %s" %
                      (qbit, ast2str(beg_barrier)))

            with_group.body = [beg_barrier] + pruned_body

            with_group.qbit = qbit
            MarkReferencedQbits.marker(with_group, local_vars=local_vars)

            new_groups.append(with_group)

        with_grouped = expr2ast('with grouped: pass')
        copy_all_loc(with_grouped, node, recurse=True)
        MarkReferencedQbits.marker(with_grouped, local_vars=local_vars)

        with_grouped.body = new_groups

        new_node.body = alloc_stmnts + list([with_grouped])

        return new_node