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
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
def __init__(self, node): # The set all all channels observed in the input AST # self.all_channels = find_all_channels(node) self.blank_barrier_ast = expr2ast('Barrier()')
def make_barrier_ast(qbits, node, name='seq', bid=None): if not bid: bid = BarrierIdentifier.next_bid() arg_names = sorted(list(qbits)) arg_list = '[%s]' % ', '.join(arg_names) barrier_name = '%s_%d' % (name, bid) barrier_ast = expr2ast('Barrier(\'%s\', %s)' % (barrier_name, arg_list)) # TODO: make sure that this gets all of the qbits. # It might need local scope info as well, which we don't # have here. # MarkReferencedQbits.marker(barrier_ast) copy_all_loc(barrier_ast, node, recurse=True) # Add an "implicit import" for the Barrier function # barrier_ast.value.qgl_implicit_import = ('Barrier', 'qgl2.qgl1control', 'Barrier') # print('MARKED %s [%s] %s' % # (barrier_name, # str(barrier_ast.qgl2_referenced_qbits), str(qbits))) return barrier_ast
def make_ugoto_call(self, label): """ Create an unconditional goto call """ # Instead of constructing the AST piece by piece, # construct a string containing the code we # want, and then parse that to create the AST. # goto_str = 'Goto(BlockLabel(\'%s\'))' % label goto_ast = expr2ast(goto_str) return goto_ast
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])
def make_qrepeat(iter_cnt, body, subname=''): """ Create a with-Qrepeat node with the given iter_cnt and body The subname string may be used to create variants on the basic with-Qrepeat node, to deal with embedded repeats (i.e. when part of a for loop can be rewritten as a Qrepeat, but the rest cannot). """ repeat_txt = 'with Qrepeat%s(%d):\n pass' % (subname, iter_cnt) repeat_ast = expr2ast(repeat_txt) copy_all_loc(repeat_ast, body[0], recurse=True) repeat_ast.body = body return repeat_ast
def concur_wait(self, node): """ Synchronize the start of each seq block within a concur block, Add seq blocks for any "missing" channels so we can add a Barrier instruction for each of them as well """ global BARRIER_CTR # This method will be destructive, unless we make a new # copy of the AST tree first # node = deepcopy(node) seen_channels = set() # Channels in this with_concur concur_channels = find_all_channels(node) # For creating the Barriers, we want QGL1 scoped variables that will be real channel instances. # We basically have that already. real_chans = set() for chan in concur_channels: real_chans.add(chan) start_barrier = BARRIER_CTR end_barrier = start_barrier + 1 BARRIER_CTR += 2 for stmnt in node.body: if not is_seq(stmnt): NodeError.error_msg(stmnt, 'non-seq block inside concur block?') return node seq_channels = find_all_channels(stmnt) if seq_channels.intersection(seen_channels): NodeError.error_msg(stmnt, 'seq blocks have overlapping channels') return node seen_channels = seen_channels.union(seq_channels) chan_name = ','.join(seq_channels) # mark stmnt with chan_name or seq_channels in another way if hasattr(stmnt, 'qgl_chan_list'): oldChanSet = set(stmnt.qgl_chan_list) newChanSet = seq_channels oldMissing = newChanSet - oldChanSet oldExtra = oldChanSet - newChanSet if len(oldMissing) > 0: NodeError.diag_msg( stmnt, 'marked chan list %s was missing %s' % (str(oldChanSet), str(oldMissing))) if len(oldExtra) > 0: NodeError.diag_msg( stmnt, 'marked chan list %s had extra %s' % (str(oldChanSet), str(oldExtra))) NodeError.diag_msg(stmnt, 'Marking chan list %s' % (str(seq_channels))) stmnt.qgl_chan_list = list(seq_channels) new_seq_body = list() # Helper to ensure the string we feed to AST doesn't put quotes around # our Qubit variable names def appendChans(bString, chans): bString += '[' first = True for chan in chans: if first: bString += str(chan) first = False else: bString += "," + str(chan) bString += ']' return bString # Add global ctr, chanlist=concur_channels # FIXME: Hold concur_channels as a string? List? bstring = 'Barrier("%s", ' % str(start_barrier) bstring = appendChans(bstring, list(real_chans)) bstring += ')\n' barrier_ast = expr2ast(bstring) # barrier_ast = expr2ast('Barrier(%s, %s)\n' % (str(start_barrier), list(real_chans))) copy_all_loc(barrier_ast, node) barrier_ast.channels = concur_channels # print("*****Start barrier: %s" % pyqgl2.ast_util.ast2str(barrier_ast)) new_seq_body.append(barrier_ast) new_seq_body += stmnt.body bstring = 'Barrier("%s", ' % str(end_barrier) bstring = appendChans(bstring, list(real_chans)) bstring += ')\n' end_barrier_ast = expr2ast(bstring) #end_barrier_ast = expr2ast('Barrier(%s, %s)\n' % (str(end_barrier), list(real_chans))) copy_all_loc(end_barrier_ast, node) # Add global ctr, chanlist=concur_channels end_barrier_ast.channels = concur_channels # print('End AST: %s' % ast2str(end_barrier_ast)) new_seq_body.append(end_barrier_ast) stmnt.body = new_seq_body # FIXME: In new thinking, is the proper unseen set the global one, # Or only those local to this with concur. I think only local for unseen_chan in concur_channels - seen_channels: #print('DIAG %s' % ast2str(stmnt)) NodeError.diag_msg( stmnt, 'channels unreferenced in concur: %s' % str(unseen_chan)) bstring = 'with seq:\n Barrier("%s", ' % str(start_barrier) bstring = appendChans(bstring, list(real_chans)) bstring += ')\n Barrier("%s",' % str(end_barrier) bstring = appendChans(bstring, list(real_chans)) bstring += ')\n' empty_seq_ast = expr2ast(bstring) # print('Empty AST: %s' % ast2str(empty_seq_ast)) # empty_seq_ast = expr2ast( # 'with seq:\n Barrier(%s, %s)\n Barrier(%s, %s)' % (str(start_barrier), list(real_chans), str(end_barrier), list(real_chans))) # Mark empty_seq_ast with unseen_chan empty_seq_ast.qgl_chan_list = [unseen_chan] copy_all_loc(empty_seq_ast, node) node.body.append(empty_seq_ast) return node
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