Exemplo n.º 1
0
    def visit_Assign(self, node):
        """
        Flatten an assignment. The only assignments we should see
        are Qubits, measurements, and runtime computations. If we see a
        measurement, we need to schedule the pulse (the RHS). A runtime
        computation is passed through as an opaque STORE command.
        """
        if not isinstance(node.value, ast.Call):
            NodeError.error_msg(node,
                                "Unexpected assignment [%s]" % ast2str(node))
        if hasattr(node, 'qgl2_type'):
            if node.qgl2_type == 'qbit':
                return node
            elif node.qgl2_type == 'measurement':
                new_node = ast.Expr(value=node.value)
                new_node.qgl2_type = 'measurement'
                pyqgl2.ast_util.copy_all_loc(new_node, node)
                return new_node
            elif node.qgl2_type == 'runtime_call':
                # put the runtime_call in a STORE command
                new_node = expr2ast('Store()')
                # first argument is the STORE destination
                # TODO we want to re-write the target as an address
                target_str = ast2str(node.targets[0]).strip()
                new_node.value.args.append(ast.Str(s=target_str))
                # second argument is the str() representation
                # of the runtime call
                call_str = ast2str(node.value).strip()
                new_node.value.args.append(ast.Str(s=call_str))
                new_node.qgl2_type = 'runtime_call'
                pyqgl2.ast_util.copy_all_loc(new_node, node, recurse=True)
                return new_node

        return node
Exemplo n.º 2
0
    def emit_function(self, func_name='qgl1_main', setup=None):
        """
        Create a function that, when run, creates the context
        in which the sequence is evaluated, and evaluate it.

        Assumes find_imports and find_sequences have already
        been called.

        func_name is the name for the function, if provided.
        I'm not certain that the name has any significance
        or whether this function will be, for all practical
        purposes, a lambda.
        """

        # assumes a standard 4-space indent; if the surrounding
        # context uses some other indentation scheme, the interpreter
        # may gripe, and pep8 certainly will
        #
        indent = '    '

        # FIXME: Ugliness
        # In the proper namespace we need to import all the QGL1 functions
        # that this method is using / might use.
        # Here we include the imports matching stuff in qgl2.qgl1.py
        # as required by create_imports_list(), plus
        # extras as required.

        # FIXME: Since QubitFactory is in qgl2.qgl1, why is this needed?
        # - find_imports didn't find QubitFactory?
        base_imports = indent + 'from QGL import QubitFactory\n'

        found_imports = ('\n' + indent).join(self.create_imports_list())

        # define the method preamble
        preamble = 'def %s():\n' % func_name
        preamble += base_imports
        preamble += indent + found_imports
        preamble += '\n\n'

        # FIXME: In below block, calls to ast2str are the slowest part
        # (78%) of calling get_sequence_function. Fixable?

        for node in self.qbit_creates:
            preamble += indent + ast2str(node).strip() + '\n'

        if setup:
            for setup_stmnt in setup:
                preamble += indent + ('%s\n' % ast2str(setup_stmnt).strip())

        sequence = [ast2str(item).strip() for item in self.sequence]

        # TODO there must be a more elegant way to indent this properly
        seq_str = indent + 'seq = [\n' + 2 * indent
        seq_str += (',\n' + 2 * indent).join(sequence)
        seq_str += '\n' + indent + ']\n'

        postamble = indent + 'return seq\n'

        res = preamble + seq_str + postamble
        return res
Exemplo n.º 3
0
def with_qiter_eq(stmnt1, stmnt2):
    """
    Given two "with-qiter" statements, determine whether they are
    identical and therefore the effect of the two statements
    can be implemented by repeating one of the statements twice.

    Returns True if the statements are identical, False otherwise.

    Note that the statements are assumed to be consecutive,
    NOT separated by a third statement.  If the statements are
    separated, then the comparison is still legitimate, but
    does not imply that the two statements can be replaced with
    iteration.

    Also note that the comparison is assumed to be done
    AFTER the evaluator has finished transforming the statements,
    including rebinding any variables and/or variable names.
    At this point, only a small subset of valid Python statements
    and expressions may appear in the given statements.  We use
    this assumption liberally, and therefore this method may
    need to change if/when the evaluator changes.
    """

    try:
        stmnt_txt1 = ast2str(stmnt1).strip()
    except BaseException as exc:
        print('with_qiter_eq ast2str failed stmnt1 %s' % str(exc))
        return False

    try:
        stmnt_txt2 = ast2str(stmnt2).strip()
    except BaseException as exc:
        print('with_qiter_eq ast2str failed stmnt2 %s' % str(exc))
        return False

    # Just to be doubly certain, try converting the text back
    # to AST again, and then check those results.

    try:
        stmnt1_ast_again = expr2ast(stmnt_txt1)
        stmnt1_txt_again = ast2str(stmnt1_ast_again).strip()
    except BaseException as exc:
        print('with_qiter_eq expr2ast failed stmnt1 %s' % str(exc))
        return False

    try:
        stmnt2_ast_again = expr2ast(stmnt_txt1)
        stmnt2_txt_again = ast2str(stmnt2_ast_again).strip()
    except BaseException as exc:
        print('with_qiter_eq expr2ast failed stmnt2 %s' % str(exc))
        return False

    if stmnt_txt1 != stmnt_txt2:
        return False
    elif stmnt1_txt_again != stmnt2_txt_again:
        return False
    else:
        return True
Exemplo n.º 4
0
    def visit_Expr(self, node):

        # we only really understand stubs right now
        if isinstance(node.value, ast.Call):
            print('RTC CALL: %s' % ast2str(node.value).strip())

        return node
Exemplo n.º 5
0
    def do_seq(self, qbits, body):
        chan_name = '/'.join(qbits)

        if chan_name not in self.qbit2sequence:
            self.qbit2sequence[chan_name] = list()

        for stmnt in body:
            txt = ast2str(stmnt).strip()

            self.qbit2sequence[chan_name].append(txt)
Exemplo n.º 6
0
    def factory(node, local_vars):
        '''
        Evaluates a ast.Call node of a QRegister and returns its value.

        local_vars is a dictionary of symbol -> value bindings
        '''
        if not is_qbit_create(node):
            NodeError.error_msg(
                node,
                "Attempted to create a QRegister from an invalid AST node [%s]."
                % ast2str(node))

        # convert args into values
        arg_values = []
        for arg in node.value.args:
            if isinstance(arg, ast.Num):
                arg_values.append(arg.n)
            elif isinstance(arg, ast.Str):
                arg_values.append(arg.s)
            elif isinstance(arg, ast.Name) and arg.id in local_vars:
                arg_values.append(local_vars[arg.id])
            elif is_qbit_subscript(arg, local_vars):
                # evaluate the subscript to extract the referenced qubits
                parent_qreg = local_vars[arg.value.id]
                try:
                    arg_val = eval(ast2str(arg), None, local_vars)
                except:
                    NodeError.error_msg(
                        node, "Unhandled qreg subscript [%s]" % ast2str(arg))
                sub_qubits = parent_qreg.qubits[arg_val.idx]
                if hasattr(sub_qubits, '__iter__'):
                    arg_values.extend("q" + str(n) for n in sub_qubits)
                else:
                    arg_values.append("q" + str(sub_qubits))
            else:
                NodeError.error_msg(
                    node,
                    "Unhandled argument to QRegister [%s]" % ast2str(arg))

        return QRegister(*arg_values)
Exemplo n.º 7
0
 def expand_arg(self, arg):
     '''
     Expands a single argument to a stub call. QRegisters are expanded
     to a list of constituent Qubits. QRegister subscripts are similar,
     except that the slice selects which Qubits to return. So, given
         a = QRegister(2)
         b = QRegister(1)
     we do (in AST shorthand):
         expand_arg("a") -> ["QBIT_1", "QBIT_2"]
         expand_arg("a[1]") -> ["QBIT_2"]
     '''
     expanded_args = []
     if isinstance(arg, ast.Name) and arg.id in self.allocated_qregs:
         qreg = self.allocated_qregs[arg.id]
         # add an argument for each constituent qubit in the QRegister
         for n in range(len(qreg)):
             new_arg = ast.Name(id=qreg.use_name(n), ctx=ast.Load())
             expanded_args.append(new_arg)
     elif (isinstance(arg, ast.Subscript)
           and arg.value.id in self.allocated_qregs):
         # add an argument for the subset of qubits referred to
         # by the QReference
         # eval the subscript to extract the slice
         qreg = self.allocated_qregs[arg.value.id]
         try:
             qref = eval(ast2str(arg), None, self.allocated_qregs)
         except:
             NodeError.error_msg(
                 arg, "Error evaluating QReference [%s]" % ast2str(arg))
         # convert the slice into a list of indices
         idx = range(len(qreg))[qref.idx]
         if not hasattr(idx, '__iter__'):
             idx = (idx, )
         for n in idx:
             new_arg = ast.Name(id=qreg.use_name(n), ctx=ast.Load())
             expanded_args.append(new_arg)
     else:
         # don't expand it
         expanded_args.append(arg)
     return expanded_args
Exemplo n.º 8
0
    def do_concur(self, body):

        for stmnt in body:
            if not is_seq(stmnt):
                # TODO: this error message is not helpful
                NodeError.fatal_msg(stmnt, 'expected a "with seq" block')
                return
            else:
                # TODO: The grouper should annotate the seq statement
                # so we don't have to find the qbits again.
                #
                qbits = find_all_channels(stmnt)
                if not qbits:
                    print('XXN body\n%s' % ast2str(stmnt))
                self.do_seq(qbits, stmnt.body)
Exemplo n.º 9
0
    def make_cgoto_call(self, label, node, cmp_operator, cmp_addr, value):
        """
        Create a conditional goto call
        """

        if isinstance(cmp_operator, ast.Eq):
            cmp_ast = expr2ast('CmpEq("%s", %s)' % (cmp_addr, str(value)))
        elif isinstance(cmp_operator, ast.NotEq):
            cmp_ast = expr2ast('CmpNeq("%s", %s)' % (cmp_addr, str(value)))
        elif isinstance(cmp_operator, ast.Gt):
            cmp_ast = expr2ast('CmpGt("%s", %s)' % (cmp_addr, str(value)))
        elif isinstance(cmp_operator, ast.Lt):
            cmp_ast = expr2ast('CmpLt("%s", %s)' % (cmp_addr, str(value)))
        else:
            NodeError.error_msg(
                node, 'Unallowed comparison operator [%s]' % ast2str(node))
            return None
        label_ast = expr2ast('Goto(BlockLabel(\'%s\'))' % label)

        pyqgl2.ast_util.copy_all_loc(cmp_ast, node, recurse=True)
        pyqgl2.ast_util.copy_all_loc(label_ast, node, recurse=True)

        return list([cmp_ast, label_ast])
Exemplo n.º 10
0
 def test_code(code_text):
     tree = ast.parse(code_text, mode='exec')
     sync = SynchronizeBlocks(tree)
     new = sync.visit(deepcopy(tree))
     print('ORIG\n%s\n=>\n%s' % (ast2str(tree), ast2str(new)))
Exemplo n.º 11
0
                self.preamble.append(stmnt)

            else:
                self.apply_name_mappings(stmnt)
                new_body.append(stmnt)

        node.body = new_body

        return node


if __name__ == '__main__':

    text = """
a = foo(1, 2)
X(a)
b = bar(a)
b = bar(b)
a = bar(a)
X90(a)
"""

    ptree = ast.parse(text, mode='exec')

    pre = Preamble()
    post = pre.separate(ptree)

    print('    %s' % ast2str(pre.preamble_module))
    print('POST\n%s' % ast2str(post))
Exemplo n.º 12
0
 def test_code(code_text):
     tree = ast.parse(code_text, mode='exec')
     flat = Flattener()
     new = flat.visit(deepcopy(tree))
     print('ORIG\n%s\n=>\n%s' % (ast2str(tree), ast2str(new)))
Exemplo n.º 13
0
    def visit_If(self, node):
        """
        flatten an "if" statement, returning a new list of
        expressions that represent the flattened sequence
        """

        # make sure that the test involves runtime values.
        # This is the only kind of test that should survive
        # to this point; classical test would have already
        # been executed.
        # FIXME add this check
        # Also, if the test contains a call, we should
        # move the evaluation of that call to a expression before
        # the comparison

        if (isinstance(node.test, ast.Name)
                or isinstance(node.test, ast.Call)):
            mask = 0
            cmp_addr = node.test.id
            cmp_operator = ast.NotEq()
        elif (isinstance(node.test, ast.UnaryOp)
              and isinstance(node.test.op, ast.Not)):
            mask = 0
            cmp_addr = node.test.operand.id
            cmp_operator = ast.Eq()
        elif isinstance(node.test, ast.Compare):
            # FIXME the value can be on either side of the comparison
            # this assumes that it is on the right
            mask = node.test.comparators[0].n
            cmp_addr = node.test.left.id
            cmp_operator = node.test.ops[0]
        else:
            NodeError.error_msg(
                node.test,
                'unhandled test expression [%s]' % ast2str(node.test))
            return node

        if_label, end_label = LabelManager.allocate_labels('if', 'if_end')

        cond_ast = self.make_cgoto_call(if_label, node.test, cmp_operator,
                                        cmp_addr, mask)

        # cond_ast is actually a list of AST nodes
        new_body = cond_ast

        end_goto_ast = self.make_ugoto_call(end_label)
        if_ast = self.make_label_call(if_label)
        end_label_ast = self.make_label_call(end_label)

        pyqgl2.ast_util.copy_all_loc(end_goto_ast, node, recurse=True)
        pyqgl2.ast_util.copy_all_loc(if_ast, node, recurse=True)
        pyqgl2.ast_util.copy_all_loc(end_label_ast, node, recurse=True)

        if node.orelse:
            new_body += self.flatten_body(node.orelse)

        new_body.append(end_goto_ast)
        new_body.append(if_ast)
        new_body += self.flatten_body(node.body)
        new_body.append(end_label_ast)

        return new_body
Exemplo n.º 14
0
    def emit_function(self, func_name='qgl1_main', setup=None):
        """
        Create a function that, when run, creates the context
        in which the sequence is evaluated, and evaluate it.

        func_name is the name for the function, if provided.
        I'm not certain that the name has any significance
        or whether this function will be, for all practical
        purposes, a lambda.
        """

        # assumes a standard 4-space indent; if the surrounding
        # context uses some other indentation scheme, the interpreter
        # may gripe, and pep8 certainly will
        #
        indent = '    '

        # FIXME: Ugliness
        # In the proper namespace we need to import all the QGL1 functions
        # that this method is using / might use
        # Here we include the imports matching stuff in qgl2.qgl1.py
        # Can we perhaps annotate all the stubs with the proper
        # import statement and use that to figure out what to include here?
        base_imports = """    from QGL.PulseSequencePlotter import plot_pulse_files
"""

        found_imports = ('\n' + indent).join(self.create_imports_list())

        # allow QBIT parameters to be overridden
        #
        preamble = 'def %s(**kwargs):\n' % func_name
        preamble += base_imports
        preamble += indent + found_imports
        preamble += '\n\n'

        for (sym_name, _use_name, node) in self.qbit_creates:
            preamble += indent + 'if \'' + sym_name + '\' in kwargs:\n'
            preamble += (2 * indent) + sym_name
            preamble += ' = kwargs[\'%s\']\n' % sym_name
            preamble += indent + 'else:\n'
            preamble += (2 * indent) + ast2str(node).strip() + '\n'

        for (sym_name, use_name, _node) in self.qbit_creates:
            preamble += indent + '%s = %s\n' % (use_name, sym_name)

        if setup:
            for stmnt in setup:
                preamble += indent + ('%s\n' % ast2str(stmnt).strip())

        seqs_def = indent + 'seqs = list()\n'
        seqs_str = ''
        seq_strs = list()

        for seq in self.sequences.values():
            #print("Looking at seq %s" % seq)
            sequence = [ast2str(item).strip() for item in seq]
            #print ("It is %s" % sequence)
            # TODO: check that this is always OK.
            #
            # HACK ALERT: this might not always be the right thing to do
            # but right now extraneous calls to Sync at the start of
            # program appear to cause a problem, and they don't serve
            # any known purpose, so skip them.
            #
            while sequence[0] == 'Sync()':
                sequence = sequence[1:]

            # TODO there must be a more elegant way to indent this properly
            seq_str = indent + 'seq = [\n' + 2 * indent
            seq_str += (',\n' + 2 * indent).join(sequence)
            seq_str += '\n' + indent + ']\n'
            seq_strs.append(seq_str)

        for seq_str in seq_strs:
            seqs_str += seq_str

            # That was a single sequence. We want a list of sequences
            # FIXME: Really, we want a new sequence every time the source code used Init()
            seqs_str += indent + 'seqs += [seq]\n'

        postamble = indent + 'return seqs\n'

        res = preamble + seqs_def + seqs_str + postamble
        return res
Exemplo n.º 15
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
Exemplo n.º 16
0
    def visit(self, node):

        if not hasattr(node, 'qgl2_referenced_qbits'):
            return node

        # Special case: if we're called on a function def,
        # then use add_barriers instead, because function
        # definitions are different than ordinary statements
        #
        if isinstance(node, ast.FunctionDef):
            return self.add_barriers(node)

        # Save a copy of the current state, so we can
        # restore it later (even if we don't actually
        # modify the state)
        #
        prev_state = self.in_concur
        prev_qbits = self.referenced_qbits

        # Figure out if we might be entering a new execution mode
        # (with-concur or with-infunc).  If we are, then we need
        # to change the state variables before recursing.
        #
        entering_concur = is_concur(node)
        entering_infunc = is_infunc(node)

        if entering_concur or entering_infunc:
            self.referenced_qbits = node.qgl2_referenced_qbits

        if entering_concur:
            self.in_concur = True
        elif entering_infunc:
            self.in_concur = False

        if hasattr(node, 'body'):
            node.body, new_refs = self.expand_body(node.body)
            if new_refs:
                node.qgl2_referenced_qbits = new_refs

            for stmnt in node.body:
                if not stmnt.qgl2_referenced_qbits:
                    stmnt.qgl2_referenced_qbits = new_refs

            # if we're entering a concur block and have
            # more than one qbit to protect, then add a
            # begin and end barrier
            #
            if entering_concur and len(self.referenced_qbits) > 0:
                bid = BarrierIdentifier.next_bid()

                beg_barrier = self.make_barrier_ast(self.referenced_qbits,
                                                    node,
                                                    name='concur_beg',
                                                    bid=bid)
                end_barrier = self.make_barrier_ast(self.referenced_qbits,
                                                    node,
                                                    name='concur_end',
                                                    bid=bid)
                node.body = [beg_barrier] + node.body + [end_barrier]

        if hasattr(node, 'orelse') and node.orelse:
            node.orelse, new_refs = self.expand_body(node.orelse)
            if new_refs:
                node.qgl2_referenced_qbits = new_refs

        if DebugMsg.ACTIVE_LEVEL < 3:
            print('NODE %s => %s %s' %
                  (ast2str(node).strip(), str(
                      node.qgl2_referenced_qbits), str(self.referenced_qbits)))

        # put things back the way they were (even if we didn't
        # change anything)
        #
        self.in_concur = prev_state
        self.referenced_qbits = prev_qbits

        return node