# Copyright 2019-2024 Cambridge Quantum Computing## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.fromdataclassesimportdataclassimportosimportreimportuuidimportitertoolsfromcollectionsimportOrderedDictfromimportlibimportimport_modulefromitertoolsimportchain,groupbyfromdecimalimportDecimalfromtypingimport(Any,Callable,Dict,Generator,Iterable,Iterator,List,NewType,Optional,Sequence,Set,TextIO,Tuple,Type,TypeVar,Union,cast,)fromsympyimportSymbol,pi,ExprfromlarkimportDiscard,Lark,Token,Transformer,Treefrompytket._tket.circuitimport(ClassicalExpBox,Command,Conditional,RangePredicateOp,SetBitsOp,CopyBitsOp,MultiBitOp,WASMOp,BarrierOp,ClExprOp,WiredClExpr,ClExpr,)frompytket._tket.unit_idimport_TEMP_BIT_NAME,_TEMP_BIT_REG_BASEfrompytket.circuitimport(Bit,BitRegister,Circuit,Op,OpType,Qubit,QubitRegister,UnitID,)frompytket.circuit.clexprimporthas_reg_output,wired_clexpr_from_logic_expfrompytket.circuit.decompose_classicalimportint_to_boolsfrompytket.circuit.logic_expimport(BitLogicExp,BitWiseOp,PredicateExp,LogicExp,RegEq,RegLogicExp,RegNeg,RegWiseOp,create_predicate_exp,create_logic_exp,)frompytket.qasm.grammarimportgrammarfrompytket.passesimportAutoRebase,DecomposeBoxes,RemoveRedundanciesfrompytket.wasmimportWasmFileHandlerclassQASMParseError(Exception):"""Error while parsing QASM input."""def__init__(self,msg:str,line:Optional[int]=None,fname:Optional[str]=None):self.msg=msgself.line=lineself.fname=fnamectx=""iffnameisNoneelsef"\nFile:{fname}: "ctx+=""iflineisNoneelsef"\nLine:{line}. "super().__init__(f"{msg}{ctx}")classQASMUnsupportedError(Exception):passValue=Union[int,float,str]T=TypeVar("T")_BITOPS=set(op.valueforopinBitWiseOp)_BITOPS.update(("+","-"))# both are parsed to XOR_REGOPS=set(op.valueforopinRegWiseOp)Arg=Union[List,str]NOPARAM_COMMANDS={"CX":OpType.CX,# built-in gate equivalent to "cx""cx":OpType.CX,"x":OpType.X,"y":OpType.Y,"z":OpType.Z,"h":OpType.H,"s":OpType.S,"sdg":OpType.Sdg,"t":OpType.T,"tdg":OpType.Tdg,"sx":OpType.SX,"sxdg":OpType.SXdg,"cz":OpType.CZ,"cy":OpType.CY,"ch":OpType.CH,"csx":OpType.CSX,"ccx":OpType.CCX,"c3x":OpType.CnX,"c4x":OpType.CnX,"ZZ":OpType.ZZMax,"measure":OpType.Measure,"reset":OpType.Reset,"id":OpType.noop,"barrier":OpType.Barrier,"swap":OpType.SWAP,"cswap":OpType.CSWAP,}PARAM_COMMANDS={"p":OpType.U1,# alias. https://github.com/Qiskit/qiskit-terra/pull/4765"u":OpType.U3,# alias. https://github.com/Qiskit/qiskit-terra/pull/4765"U":OpType.U3,# built-in gate equivalent to "u3""u3":OpType.U3,"u2":OpType.U2,"u1":OpType.U1,"rx":OpType.Rx,"rxx":OpType.XXPhase,"ry":OpType.Ry,"rz":OpType.Rz,"RZZ":OpType.ZZPhase,"rzz":OpType.ZZPhase,"Rz":OpType.Rz,"U1q":OpType.PhasedX,"crz":OpType.CRz,"crx":OpType.CRx,"cry":OpType.CRy,"cu1":OpType.CU1,"cu3":OpType.CU3,"Rxxyyzz":OpType.TK2,}NOPARAM_EXTRA_COMMANDS={"v":OpType.V,"vdg":OpType.Vdg,"cv":OpType.CV,"cvdg":OpType.CVdg,"csxdg":OpType.CSXdg,"bridge":OpType.BRIDGE,"iswapmax":OpType.ISWAPMax,"zzmax":OpType.ZZMax,"ecr":OpType.ECR,"cs":OpType.CS,"csdg":OpType.CSdg,}PARAM_EXTRA_COMMANDS={"tk2":OpType.TK2,"iswap":OpType.ISWAP,"phasediswap":OpType.PhasedISWAP,"yyphase":OpType.YYPhase,"xxphase3":OpType.XXPhase3,"eswap":OpType.ESWAP,"fsim":OpType.FSim,}_tk_to_qasm_noparams=dict(((item[1],item[0])foriteminNOPARAM_COMMANDS.items()))_tk_to_qasm_noparams[OpType.CX]="cx"# prefer "cx" to "CX"_tk_to_qasm_params=dict(((item[1],item[0])foriteminPARAM_COMMANDS.items()))_tk_to_qasm_params[OpType.U3]="u3"# prefer "u3" to "U"_tk_to_qasm_params[OpType.Rz]="rz"# prefer "rz" to "Rz"_tk_to_qasm_extra_noparams=dict(((item[1],item[0])foriteminNOPARAM_EXTRA_COMMANDS.items()))_tk_to_qasm_extra_params=dict(((item[1],item[0])foriteminPARAM_EXTRA_COMMANDS.items()))_classical_gatestr_map={"AND":"&","OR":"|","XOR":"^"}_all_known_gates=(set(NOPARAM_COMMANDS.keys()).union(PARAM_COMMANDS.keys()).union(PARAM_EXTRA_COMMANDS.keys()).union(NOPARAM_EXTRA_COMMANDS.keys()))_all_string_maps={key:val.nameforkey,valinchain(PARAM_COMMANDS.items(),NOPARAM_COMMANDS.items(),PARAM_EXTRA_COMMANDS.items(),NOPARAM_EXTRA_COMMANDS.items(),)}unit_regex=re.compile(r"([a-z][a-zA-Z0-9_]*)\[([\d]+)\]")regname_regex=re.compile(r"^[a-z][a-zA-Z0-9_]*$")def_extract_reg(var:Token)->Tuple[str,int]:match=unit_regex.match(var.value)ifmatchisNone:raiseQASMParseError(f"Invalid register definition '{var.value}'. Register definitions ""must follow the pattern '<name> [<size in integer>]'. ""For example, 'q [5]'. QASM register names must begin with a ""lowercase letter and may only contain lowercase and uppercase ""letters, numbers, and underscores.")returnmatch.group(1),int(match.group(2))def_load_include_module(header_name:str,flter:bool,decls_only:bool)->Dict[str,Dict]:try:ifdecls_only:include_def:Dict[str,Dict]=import_module(f"pytket.qasm.includes._{header_name}_decls")._INCLUDE_DECLSelse:include_def=import_module(f"pytket.qasm.includes._{header_name}_defs")._INCLUDE_DEFSexceptModuleNotFoundErrorase:raiseQASMParseError(f"Header {header_name} is not known and cannot be loaded.")fromereturn{gate:include_def[gate]forgateininclude_defifnotflterorgatenotin_all_known_gates}def_bin_par_exp(op:"str")->Callable[["CircuitTransformer",List[str]],str]:deff(self:"CircuitTransformer",vals:List[str])->str:returnf"({vals[0]}{op}{vals[1]})"returnfdef_un_par_exp(op:"str")->Callable[["CircuitTransformer",List[str]],str]:deff(self:"CircuitTransformer",vals:List[str])->str:returnf"({op}{vals[0]})"returnfdef_un_call_exp(op:"str")->Callable[["CircuitTransformer",List[str]],str]:deff(self:"CircuitTransformer",vals:List[str])->str:returnf"{op}({vals[0]})"returnfdef_hashable_uid(arg:List)->Tuple[str,int]:returnarg[0],arg[1][0]Reg=NewType("Reg",str)CommandDict=Dict[str,Any]@dataclassclassParsMap:pars:Iterable[str]def__iter__(self)->Iterable[str]:returnself.parsclassCircuitTransformer(Transformer):def__init__(self,return_gate_dict:bool=False,maxwidth:int=32,use_clexpr:bool=False,)->None:super().__init__()self.q_registers:Dict[str,int]={}self.c_registers:Dict[str,int]={}self.gate_dict:Dict[str,Dict]={}self.wasm:Optional[WasmFileHandler]=Noneself.include=""self.return_gate_dict=return_gate_dictself.maxwidth=maxwidthself.use_clexpr=use_clexprdef_fresh_temp_bit(self)->List:if_TEMP_BIT_NAMEinself.c_registers:idx=self.c_registers[_TEMP_BIT_NAME]else:idx=0self.c_registers[_TEMP_BIT_NAME]=idx+1return[_TEMP_BIT_NAME,[idx]]def_reset_context(self,reset_wasm:bool=True)->None:self.q_registers={}self.c_registers={}self.gate_dict={}self.include=""ifreset_wasm:self.wasm=Nonedef_get_reg(self,name:str)->Reg:returnReg(name)def_get_uid(self,iarg:Token)->List:name,idx=_extract_reg(iarg)return[name,[idx]]def_get_arg(self,arg:Token)->Arg:ifarg.type=="IARG":returnself._get_uid(arg)else:returnself._get_reg(arg.value)defunroll_all_args(self,args:Iterable[Arg])->Iterator[List[Any]]:forarginargs:ifisinstance(arg,str):size=(self.q_registers[arg]ifarginself.q_registerselseself.c_registers[arg])yield[[arg,[idx]]foridxinrange(size)]else:yield[arg]defmargs(self,tree:Iterable[Token])->Iterator[Arg]:returnmap(self._get_arg,tree)defiargs(self,tree:Iterable[Token])->Iterator[List]:returnmap(self._get_uid,tree)defargs(self,tree:Iterable[Token])->Iterator[List]:return([tok.value,[0]]fortokintree)defcreg(self,tree:List[Token])->None:name,size=_extract_reg(tree[0])ifsize>self.maxwidth:raiseQASMUnsupportedError(f"Circuit contains classical register {name} of size {size} > "f"{self.maxwidth}: try setting the `maxwidth` parameter to a larger ""value.")self.c_registers[Reg(name)]=sizedefqreg(self,tree:List[Token])->None:name,size=_extract_reg(tree[0])self.q_registers[Reg(name)]=sizedefmeas(self,tree:List[Token])->Iterable[CommandDict]:forargsinzip(*self.unroll_all_args(self.margs(tree))):yield{"args":list(args),"op":{"type":"Measure"}}defbarr(self,tree:List[Arg])->Iterable[CommandDict]:args=[qforqsinself.unroll_all_args(tree[0])forqinqs]signature:List[str]=[]forarginargs:ifarg[0]inself.c_registers:signature.append("C")elifarg[0]inself.q_registers:signature.append("Q")else:raiseQASMParseError("UnitID "+str(arg)+" in Barrier arguments is not declared.")yield{"args":args,"op":{"signature":signature,"type":"Barrier"},}defreset(self,tree:List[Token])->Iterable[CommandDict]:forqbinnext(self.unroll_all_args(self.margs(tree))):yield{"args":[qb],"op":{"type":"Reset"}}defpars(self,vals:Iterable[str])->ParsMap:returnParsMap(map(str,vals))defmixedcall(self,tree:List)->Iterator[CommandDict]:child_iter=iter(tree)optoken=next(child_iter)opstr=optoken.valuenext_tree=next(child_iter)try:args=next(child_iter)pars=cast(ParsMap,next_tree).parsexceptStopIteration:args=next_treepars=[]treat_as_barrier=["sleep","order2","order3","order4","order5","order6","order7","order8","order9","order10","order11","order12","order13","order14","order15","order16","order17","order18","order19","order20","group2","group3","group4","group5","group6","group7","group8","group9","group10","group11","group12","group13","group14","group15","group16","group17","group18","group19","group20",]# other opaque gates, which are not handled as barrier# ["RZZ", "Rxxyyzz", "Rxxyyzz_zphase", "cu", "cp", "rccx", "rc3x", "c3sqrtx"]args=list(args)ifopstrintreat_as_barrier:params=[f"{par}"forparinpars]else:params=[f"({par})/pi"forparinpars]ifopstrinself.gate_dict:op:Dict[str,Any]={}ifopstrintreat_as_barrier:op["type"]="Barrier"param_sorted=",".join(params)op["data"]=f"{opstr}({param_sorted})"op["signature"]=[arg[0]forarginargs]else:gdef=self.gate_dict[opstr]op["type"]="CustomGate"box={"type":"CustomGate","id":str(uuid.uuid4()),"gate":gdef,}box["params"]=paramsop["box"]=boxparams=[]# to stop duplication in to opelse:try:optype=_all_string_maps[opstr]exceptKeyErrorase:raiseQASMParseError("Cannot parse gate of type: {}".format(opstr),optoken.line)fromeop={"type":optype}ifparams:op["params"]=params# Operations needing special handling:ifoptype.startswith("Cn"):# n-controlled rotations have variable signatureop["n_qb"]=len(args)elifoptype=="Barrier":op["signature"]=["Q"]*len(args)forarginzip(*self.unroll_all_args(args)):yield{"args":list(arg),"op":op}defgatecall(self,tree:List)->Iterable[CommandDict]:returnself.mixedcall(tree)defexp_args(self,tree:Iterable[Token])->Iterable[Reg]:forargintree:ifarg.type=="ARG":yieldself._get_reg(arg.value)else:raiseQASMParseError("Non register arguments not supported for extern call.",arg.line)def_logic_exp(self,tree:List,opstr:str)->LogicExp:args,line=self._get_logic_args(tree)openum:Union[Type[BitWiseOp],Type[RegWiseOp]]ifopstrin_BITOPSandopstrnotin_REGOPS:openum=BitWiseOpelifopstrin_REGOPSandopstrnotin_BITOPS:openum=RegWiseOpelifall(isinstance(arg,int)forarginargs):openum=RegWiseOpelifall(isinstance(arg,(Bit,BitLogicExp,int))forarginargs):ifall(argin(0,1)forarginargsifisinstance(arg,int)):openum=BitWiseOpelse:raiseQASMParseError("Bits can only be operated with (0, 1) literals."f" Incomaptible arguments {args}",line,)else:openum=RegWiseOpifopenumisBitWiseOpandopstrin("+","-"):op:Union[BitWiseOp,RegWiseOp]=BitWiseOp.XORelse:op=openum(opstr)returncreate_logic_exp(op,args)def_get_logic_args(self,tree:Sequence[Union[Token,LogicExp]])->Tuple[List[Union[LogicExp,Bit,BitRegister,int]],Optional[int]]:args:List[Union[LogicExp,Bit,BitRegister,int]]=[]line=Nonefortokintree:ifisinstance(tok,LogicExp):args.append(tok)elifisinstance(tok,Token):line=tok.lineiftok.type=="INT":args.append(int(tok.value))eliftok.type=="IARG":args.append(Bit(*_extract_reg(tok)))eliftok.type=="ARG":args.append(BitRegister(tok.value,self.c_registers[tok.value]))else:raiseQASMParseError(f"Could not pass argument {tok}")else:raiseQASMParseError(f"Could not pass argument {tok}")returnargs,linepar_add=_bin_par_exp("+")par_sub=_bin_par_exp("-")par_mul=_bin_par_exp("*")par_div=_bin_par_exp("/")par_pow=_bin_par_exp("**")par_neg=_un_par_exp("-")sqrt=_un_call_exp("sqrt")sin=_un_call_exp("sin")cos=_un_call_exp("cos")tan=_un_call_exp("tan")ln=_un_call_exp("ln")b_and=lambdaself,tree:self._logic_exp(tree,"&")b_not=lambdaself,tree:self._logic_exp(tree,"~")b_or=lambdaself,tree:self._logic_exp(tree,"|")xor=lambdaself,tree:self._logic_exp(tree,"^")lshift=lambdaself,tree:self._logic_exp(tree,"<<")rshift=lambdaself,tree:self._logic_exp(tree,">>")add=lambdaself,tree:self._logic_exp(tree,"+")sub=lambdaself,tree:self._logic_exp(tree,"-")mul=lambdaself,tree:self._logic_exp(tree,"*")div=lambdaself,tree:self._logic_exp(tree,"/")ipow=lambdaself,tree:self._logic_exp(tree,"**")defneg(self,tree:List[Union[Token,LogicExp]])->RegNeg:arg=self._get_logic_args(tree)[0][0]assertisinstance(arg,(RegLogicExp,BitRegister,int))returnRegNeg(arg)defcond(self,tree:List[Token])->PredicateExp:op:Union[BitWiseOp,RegWiseOp]arg:Union[Bit,BitRegister]iftree[1].type=="IARG":arg=Bit(*_extract_reg(tree[1]))op=BitWiseOp(str(tree[2]))else:arg=BitRegister(tree[1].value,self.c_registers[tree[1].value])op=RegWiseOp(str(tree[2]))returncreate_predicate_exp(op,[arg,int(tree[3].value)])defifc(self,tree:Sequence)->Iterable[CommandDict]:condition=cast(PredicateExp,tree[0])var,val=condition.argscondition_bits=[]ifisinstance(var,Bit):assertcondition.opin(BitWiseOp.EQ,BitWiseOp.NEQ)assertvalin(0,1)condition_bits=[var.to_list()]else:assertisinstance(var,BitRegister)reg_bits=next(self.unroll_all_args([var.name]))ifisinstance(condition,RegEq):# special case for base qasmcondition_bits=reg_bitselse:pred_val=cast(int,val)minval=0maxval=(1<<self.maxwidth)-1ifcondition.op==RegWiseOp.LT:maxval=pred_val-1elifcondition.op==RegWiseOp.GT:minval=pred_val+1ifcondition.opin(RegWiseOp.LEQ,RegWiseOp.EQ,RegWiseOp.NEQ):maxval=pred_valifcondition.opin(RegWiseOp.GEQ,RegWiseOp.EQ,RegWiseOp.NEQ):minval=pred_valcondition_bit=self._fresh_temp_bit()yield{"args":reg_bits+[condition_bit],"op":{"classical":{"lower":minval,"n_i":len(reg_bits),"upper":maxval,},"type":"RangePredicate",},}condition_bits=[condition_bit]val=int(condition.op!=RegWiseOp.NEQ)forcominfilter(lambdax:xisnotNoneandxisnotDiscard,tree[1]):com["args"]=condition_bits+com["args"]com["op"]={"conditional":{"op":com["op"],"value":val,"width":len(condition_bits),},"type":"Conditional",}yieldcomdefcop(self,tree:Sequence[Iterable[CommandDict]])->Iterable[CommandDict]:returntree[0]def_calc_exp_io(self,exp:LogicExp,out_args:List)->Tuple[List[List],Dict[str,Any]]:all_inps:list[Tuple[str,int]]=[]forinpinexp.all_inputs_ordered():ifisinstance(inp,Bit):all_inps.append((inp.reg_name,inp.index[0]))else:assertisinstance(inp,BitRegister)forbitininp:all_inps.append((bit.reg_name,bit.index[0]))outs=(_hashable_uid(arg)forarginout_args)o=[]io=[]foroutinouts:ifoutinall_inps:all_inps.remove(out)io.append(out)else:o.append(out)exp_args=list(map(lambdax:[x[0],[x[1]]],chain.from_iterable((all_inps,io,o))))numbers_dict={"n_i":len(all_inps),"n_io":len(io),"n_o":len(o),}returnexp_args,numbers_dictdef_cexpbox_dict(self,exp:LogicExp,args:List[List])->CommandDict:box={"exp":exp.to_dict(),"id":str(uuid.uuid4()),"type":"ClassicalExpBox",}args,numbers=self._calc_exp_io(exp,args)box.update(numbers)return{"args":args,"op":{"box":box,"type":"ClassicalExpBox",},}def_clexpr_dict(self,exp:LogicExp,out_args:List[List])->CommandDict:# Convert the LogicExp to a serialization of a command containing the# corresponding ClExprOp.wexpr,args=wired_clexpr_from_logic_exp(exp,[Bit.from_list(arg)forarginout_args])return{"op":{"type":"ClExpr","expr":wexpr.to_dict(),},"args":[arg.to_list()forarginargs],}def_logic_exp_as_cmd_dict(self,exp:LogicExp,out_args:List[List])->CommandDict:return(self._clexpr_dict(exp,out_args)ifself.use_clexprelseself._cexpbox_dict(exp,out_args))defassign(self,tree:List)->Iterable[CommandDict]:child_iter=iter(tree)out_args=list(next(child_iter))args_uids=list(self.unroll_all_args(out_args))exp_tree=next(child_iter)exp:Union[str,List,LogicExp,int]=""line=Noneifisinstance(exp_tree,Token):ifexp_tree.type=="INT":exp=int(exp_tree.value)elifexp_tree.typein("ARG","IARG"):exp=self._get_arg(exp_tree)line=exp_tree.lineelifisinstance(exp_tree,Generator):# assume to be extern (wasm) callchained_uids=list(chain.from_iterable(args_uids))com=next(exp_tree)com["args"].pop()# remove the wasmstate from the argscom["args"]+=chained_uidscom["args"].append(["_w",[0]])com["op"]["wasm"]["n"]+=len(chained_uids)com["op"]["wasm"]["width_o_parameter"]=[self.c_registers[reg]forreginout_args]yieldcomreturnelse:exp=exp_treeassertlen(out_args)==1out_arg=out_args[0]args=args_uids[0]ifisinstance(out_arg,List):ifisinstance(exp,LogicExp):yieldself._logic_exp_as_cmd_dict(exp,args)elifisinstance(exp,(int,bool)):assertexpin(0,1,True,False)yield{"args":args,"op":{"classical":{"values":[bool(exp)]},"type":"SetBits"},}elifisinstance(exp,List):yield{"args":[exp]+args,"op":{"classical":{"n_i":1},"type":"CopyBits"},}else:raiseQASMParseError(f"Unexpected expression in assignment {exp}",line)else:reg=out_argifisinstance(exp,RegLogicExp):yieldself._logic_exp_as_cmd_dict(exp,args)elifisinstance(exp,BitLogicExp):yieldself._logic_exp_as_cmd_dict(exp,args[:1])elifisinstance(exp,int):yield{"args":args,"op":{"classical":{"values":int_to_bools(exp,self.c_registers[reg])},"type":"SetBits",},}elifisinstance(exp,str):width=min(self.c_registers[exp],len(args))yield{"args":[[exp,[i]]foriinrange(width)]+args[:width],"op":{"classical":{"n_i":width},"type":"CopyBits"},}else:raiseQASMParseError(f"Unexpected expression in assignment {exp}",line)defextern(self,tree:List[Any])->Any:# TODO parse extern defsreturnDiscarddefccall(self,tree:List)->Iterable[CommandDict]:returnself.cce_call(tree)defcce_call(self,tree:List)->Iterable[CommandDict]:nam=tree[0].valueparams=list(tree[1])ifself.wasmisNone:raiseQASMParseError("Cannot include extern calls without a wasm module specified.",tree[0].line,)n_i_vec=[self.c_registers[reg]forreginparams]wasm_args=list(chain.from_iterable(self.unroll_all_args(params)))wasm_args.append(["_w",[0]])yield{"args":wasm_args,"op":{"type":"WASM","wasm":{"func_name":nam,"ww_n":1,"n":sum(n_i_vec),"width_i_parameter":n_i_vec,"width_o_parameter":[],# this will be set in the assign function"wasm_file_uid":str(self.wasm),},},}deftransform(self,tree:Tree)->Dict[str,Any]:self._reset_context()returncast(Dict[str,Any],super().transform(tree))defgdef(self,tree:List)->None:child_iter=iter(tree)gate=next(child_iter).valuenext_tree=next(child_iter)symbols,args=[],[]ifisinstance(next_tree,ParsMap):symbols=list(next_tree.pars)args=list(next(child_iter))else:args=list(next_tree)symbol_map={sym:sym*piforsyminmap(Symbol,symbols)}rename_map={Qubit.from_list(qb):Qubit("q",i)fori,qbinenumerate(args)}new=CircuitTransformer(maxwidth=self.maxwidth)circ_dict=new.prog(child_iter)circ_dict["qubits"]=argsgate_circ=Circuit.from_dict(circ_dict)# check to see whether gate definition was generated by pytket converter# if true, add op as pytket Opexisting_op:bool=FalseifgateinNOPARAM_EXTRA_COMMANDS:qubit_args=[Qubit(gate+"q"+str(index),0)forindexinlist(range(len(args)))]comparison_circ=_get_gate_circuit(NOPARAM_EXTRA_COMMANDS[gate],qubit_args)ifcircuit_to_qasm_str(comparison_circ,maxwidth=self.maxwidth)==circuit_to_qasm_str(gate_circ,maxwidth=self.maxwidth):existing_op=TrueelifgateinPARAM_EXTRA_COMMANDS:qubit_args=[Qubit(gate+"q"+str(index),0)forindexinlist(range(len(args)))]comparison_circ=_get_gate_circuit(PARAM_EXTRA_COMMANDS[gate],qubit_args,[Symbol("param"+str(index)+"/pi")forindexinrange(len(symbols))],)# checks that each command has same stringexisting_op=all(str(g)==str(c)forg,cinzip(gate_circ.get_commands(),comparison_circ.get_commands()))ifnotexisting_op:gate_circ.symbol_substitution(symbol_map)gate_circ.rename_units(cast(Dict[UnitID,UnitID],rename_map))self.gate_dict[gate]={"definition":gate_circ.to_dict(),"args":symbols,"name":gate,}opaq=gdefdefoqasm(self,tree:List)->Any:returnDiscarddefincl(self,tree:List[Token])->None:self.include=str(tree[0].value).split(".")[0]self.gate_dict.update(_load_include_module(self.include,True,False))defprog(self,tree:Iterable)->Dict[str,Any]:outdict:Dict[str,Any]={"commands":list(chain.from_iterable(filter(lambdax:xisnotNoneandxisnotDiscard,tree)))}ifself.return_gate_dict:returnself.gate_dictoutdict["qubits"]=[[reg,[i]]forreg,sizeinself.q_registers.items()foriinrange(size)]outdict["bits"]=[[reg,[i]]forreg,sizeinself.c_registers.items()foriinrange(size)]outdict["implicit_permutation"]=[[q,q]forqinoutdict["qubits"]]outdict["phase"]="0.0"self._reset_context()returnoutdictdefparser(maxwidth:int,use_clexpr:bool)->Lark:returnLark(grammar,start="prog",debug=False,parser="lalr",cache=True,transformer=CircuitTransformer(maxwidth=maxwidth,use_clexpr=use_clexpr),)g_parser=Noneg_maxwidth=32g_use_clexpr=Falsedefset_parser(maxwidth:int,use_clexpr:bool)->None:globalg_parser,g_maxwidth,g_use_clexprif(g_parserisNone)or(g_maxwidth!=maxwidth)org_use_clexpr!=use_clexpr:# type: ignoreg_parser=parser(maxwidth=maxwidth,use_clexpr=use_clexpr)g_maxwidth=maxwidthg_use_clexpr=use_clexpr
[docs]defcircuit_from_qasm(input_file:Union[str,"os.PathLike[Any]"],encoding:str="utf-8",maxwidth:int=32,use_clexpr:bool=False,)->Circuit:"""A method to generate a tket Circuit from a qasm file. :param input_file: path to qasm file; filename must have ``.qasm`` extension :param encoding: file encoding (default utf-8) :param maxwidth: maximum allowed width of classical registers (default 32) :param use_clexpr: whether to use ClExprOp to represent classical expressions :return: pytket circuit """ext=os.path.splitext(input_file)[-1]ifext!=".qasm":raiseTypeError("Can only convert .qasm files")withopen(input_file,"r",encoding=encoding)asf:try:circ=circuit_from_qasm_io(f,maxwidth=maxwidth,use_clexpr=use_clexpr)exceptQASMParseErrorase:raiseQASMParseError(e.msg,e.line,str(input_file))returncirc
[docs]defcircuit_from_qasm_str(qasm_str:str,maxwidth:int=32,use_clexpr:bool=False)->Circuit:"""A method to generate a tket Circuit from a qasm string. :param qasm_str: qasm string :param maxwidth: maximum allowed width of classical registers (default 32) :param use_clexpr: whether to use ClExprOp to represent classical expressions :return: pytket circuit """globalg_parserset_parser(maxwidth=maxwidth,use_clexpr=use_clexpr)assertg_parserisnotNonecast(CircuitTransformer,g_parser.options.transformer)._reset_context(reset_wasm=False)returnCircuit.from_dict(g_parser.parse(qasm_str))# type: ignore[arg-type]
[docs]defcircuit_from_qasm_io(stream_in:TextIO,maxwidth:int=32,use_clexpr:bool=False)->Circuit:"""A method to generate a tket Circuit from a qasm text stream"""returncircuit_from_qasm_str(stream_in.read(),maxwidth=maxwidth,use_clexpr=use_clexpr)
[docs]defcircuit_from_qasm_wasm(input_file:Union[str,"os.PathLike[Any]"],wasm_file:Union[str,"os.PathLike[Any]"],encoding:str="utf-8",maxwidth:int=32,use_clexpr:bool=False,)->Circuit:"""A method to generate a tket Circuit from a qasm string and external WASM module. :param input_file: path to qasm file; filename must have ``.qasm`` extension :param wasm_file: path to WASM file containing functions used in qasm :param encoding: encoding of qasm file (default utf-8) :param maxwidth: maximum allowed width of classical registers (default 32) :return: pytket circuit """globalg_parserwasm_module=WasmFileHandler(str(wasm_file))set_parser(maxwidth=maxwidth,use_clexpr=use_clexpr)assertg_parserisnotNonecast(CircuitTransformer,g_parser.options.transformer).wasm=wasm_modulereturncircuit_from_qasm(input_file,encoding=encoding,maxwidth=maxwidth,use_clexpr=use_clexpr)
[docs]defcircuit_to_qasm(circ:Circuit,output_file:str,header:str="qelib1",maxwidth:int=32)->None:"""Convert a Circuit to QASM and write it to a file. Classical bits in the pytket circuit must be singly-indexed. Note that this will not account for implicit qubit permutations in the Circuit. :param circ: pytket circuit :param output_file: path to output qasm file :param header: qasm header (default "qelib1") :param maxwidth: maximum allowed width of classical registers (default 32) """withopen(output_file,"w")asout:circuit_to_qasm_io(circ,out,header=header,maxwidth=maxwidth)
def_filtered_qasm_str(qasm:str)->str:# remove any c registers starting with _TEMP_BIT_NAME# that are not being used somewhere elselines=qasm.split("\n")def_matcher=re.compile(r"creg ({}\_*\d*)\[\d+\]".format(_TEMP_BIT_NAME))arg_matcher=re.compile(r"({}\_*\d*)\[\d+\]".format(_TEMP_BIT_NAME))unused_regs=dict()fori,lineinenumerate(lines):ifreg:=def_matcher.match(line):# Mark a reg temporarily as unusedunused_regs[reg.group(1)]=ielifargs:=arg_matcher.findall(line):# If the line contains scratch bits that are used as arguments# mark these regs as usedforarginargs:ifarginunused_regs:unused_regs.pop(arg)# remove unused reg defsredundant_lines=sorted(unused_regs.values(),reverse=True)forline_indexinredundant_lines:dellines[line_index]return"\n".join(lines)defis_empty_customgate(op:Op)->bool:returnop.type==OpType.CustomGateandop.get_circuit().n_gates==0# type: ignoredefcheck_can_convert_circuit(circ:Circuit,header:str,maxwidth:int)->None:ifany(circ.n_gates_of_type(typ)fortypin(OpType.RangePredicate,OpType.MultiBit,OpType.ExplicitPredicate,OpType.ExplicitModifier,OpType.SetBits,OpType.CopyBits,OpType.ClassicalExpBox,))and(nothqs_header(header)):raiseQASMUnsupportedError("Complex classical gates not supported with qelib1: try converting with ""`header=hqslib1`")ifany(bit.index[0]>=maxwidthforbitincirc.bits):raiseQASMUnsupportedError(f"Circuit contains a classical register larger than {maxwidth}: try ""setting the `maxwidth` parameter to a higher value.")forcmdincirc:ifis_empty_customgate(cmd.op)or(isinstance(cmd.op,Conditional)andis_empty_customgate(cmd.op.op)):raiseQASMUnsupportedError(f"Empty CustomGates and opaque gates are not supported.")
[docs]defcircuit_to_qasm_str(circ:Circuit,header:str="qelib1",include_gate_defs:Optional[Set[str]]=None,maxwidth:int=32,)->str:"""Convert a Circuit to QASM and return the string. Classical bits in the pytket circuit must be singly-indexed. Note that this will not account for implicit qubit permutations in the Circuit. :param circ: pytket circuit :param header: qasm header (default "qelib1") :param output_file: path to output qasm file :param include_gate_defs: optional set of gates to include :param maxwidth: maximum allowed width of classical registers (default 32) :return: qasm string """check_can_convert_circuit(circ,header,maxwidth)qasm_writer=QasmWriter(circ.qubits,circ.bits,header,include_gate_defs,maxwidth)circ1=circ.copy()DecomposeBoxes().apply(circ1)forcommandincirc1:assertisinstance(command,Command)qasm_writer.add_op(command.op,command.args)returnqasm_writer.finalize()
TypeReg=TypeVar("TypeReg",BitRegister,QubitRegister)def_retrieve_registers(units:List[UnitID],reg_type:Type[TypeReg])->Dict[str,TypeReg]:ifany(len(unit.index)!=1forunitinunits):raiseNotImplementedError("OPENQASM registers must use a single index")maxunits=map(lambdax:max(x[1]),groupby(units,key=lambdaun:un.reg_name))return{maxunit.reg_name:reg_type(maxunit.reg_name,maxunit.index[0]+1)formaxunitinmaxunits}def_parse_range(minval:int,maxval:int,maxwidth:int)->Tuple[str,int]:ifmaxwidth>64:raiseNotImplementedError("Register width exceeds maximum of 64.")REGMAX=(1<<maxwidth)-1ifminval>REGMAX:raiseNotImplementedError("Range's lower bound exceeds register capacity.")elifminval>maxval:raiseNotImplementedError("Range's lower bound exceeds upper bound.")elifmaxval>REGMAX:maxval=REGMAXifminval==maxval:return("==",minval)elifminval==0:return("<=",maxval)elifmaxval==REGMAX:return(">=",minval)else:raiseNotImplementedError("Range can only be bounded on one side.")def_negate_comparator(comparator:str)->str:ifcomparator=="==":return"!="elifcomparator=="!=":return"=="elifcomparator=="<=":return">"elifcomparator==">":return"<="elifcomparator==">=":return"<"else:assertcomparator=="<"return">="def_get_optype_and_params(op:Op)->Tuple[OpType,Optional[List[Union[float,Expr]]]]:optype=op.typeparams=(op.paramsif(optypein_tk_to_qasm_params)or(optypein_tk_to_qasm_extra_params)elseNone)ifoptype==OpType.TK1:# convert to U3optype=OpType.U3params=[op.params[1],op.params[0]-0.5,op.params[2]+0.5]elifoptype==OpType.CustomGate:params=op.paramsreturnoptype,paramsdef_get_gate_circuit(optype:OpType,qubits:List[Qubit],symbols:Optional[List[Symbol]]=None)->Circuit:# create Circuit for constructing qasm fromunitids=cast(List[UnitID],qubits)gate_circ=Circuit()forqinqubits:gate_circ.add_qubit(q)ifsymbols:exprs=[symbol.as_expr()forsymbolinsymbols]gate_circ.add_gate(optype,exprs,unitids)else:gate_circ.add_gate(optype,unitids)AutoRebase({OpType.CX,OpType.U3}).apply(gate_circ)RemoveRedundancies().apply(gate_circ)returngate_circdefhqs_header(header:str)->bool:returnheaderin["hqslib1","hqslib1_dev"]@dataclassclassConditionString:variable:str# variable, e.g. "c[1]"comparator:str# comparator, e.g. "=="value:int# value, e.g. "1"classLabelledStringList:""" Wrapper class for an ordered sequence of strings, where each string has a unique label, returned when the string is added, and a string may be removed from the sequence given its label. There is a method to retrieve the concatenation of all strings in order. The conditions (e.g. "if(c[0]==1)") for some strings are stored separately in `conditions`. These conditions will be converted to text when retrieving the full string. """def__init__(self)->None:self.strings:OrderedDict[int,str]=OrderedDict()self.conditions:Dict[int,ConditionString]=dict()self.label=0defadd_string(self,string:str)->int:label=self.labelself.strings[label]=stringself.label+=1returnlabeldefget_string(self,label:int)->Optional[str]:returnself.strings.get(label,None)defdel_string(self,label:int)->None:self.strings.pop(label,None)defget_full_string(self)->str:strings=[]forl,sinself.strings.items():condition=self.conditions.get(l)ifconditionisnotNone:strings.append(f"if({condition.variable}{condition.comparator}{condition.value}) "+s)else:strings.append(s)return"".join(strings)defmake_params_str(params:Optional[List[Union[float,Expr]]])->str:s=""ifparamsisnotNone:n_params=len(params)s+="("foriinrange(n_params):reduced=Truetry:p:Union[float,Expr]=float(params[i])exceptTypeError:reduced=Falsep=params[i]ifi<n_params-1:ifreduced:s+="{}*pi,".format(p)else:s+="({})*pi,".format(p)else:ifreduced:s+="{}*pi)".format(p)else:s+="({})*pi)".format(p)s+=" "returnsdefmake_args_str(args:Sequence[UnitID])->str:s=""foriinrange(len(args)):s+=f"{args[i]}"ifi<len(args)-1:s+=","else:s+=";\n"returns@dataclassclassScratchPredicate:variable:str# variable, e.g. "c[1]"comparator:str# comparator, e.g. "=="value:int# value, e.g. "1"dest:str# destination bit, e.g. "tk_SCRATCH_BIT[0]"def_vars_overlap(v:str,w:str)->bool:"""check if two variables have overlapping bits"""v_split=v.split("[")w_split=w.split("[")ifv_split[0]!=w_split[0]:# different registersreturnFalse# e.g. (a[1], a), (a, a[1]), (a[1], a[1]), (a, a)returnlen(v_split)!=len(w_split)orv==wdef_var_appears(v:str,s:str)->bool:"""check if variable v appears in string s"""v_split=v.split("[")iflen(v_split)==1:# check if v appears in s and is not surrounded by word characters# e.g. a = a & b or a = a[1] & b[1]returnbool(re.search(r"(?<!\w)"+re.escape(v)+r"(?![\w])",s))else:ifre.search(r"(?<!\w)"+re.escape(v),s):# check if v appears in s and is not proceeded by word characters# e.g. a[1] = a[1]returnTrue# check the register of v appears in s# e.g. a[1] = a & breturnbool(re.search(r"(?<!\w)"+re.escape(v_split[0])+r"(?![\[\w])",s))classQasmWriter:""" Helper class for converting a sequence of TKET Commands to QASM, and retrieving the final QASM string afterwards. """def__init__(self,qubits:List[Qubit],bits:List[Bit],header:str="qelib1",include_gate_defs:Optional[Set[str]]=None,maxwidth:int=32,):self.header=headerself.maxwidth=maxwidthself.added_gate_definitions:Set[str]=set()self.include_module_gates={"measure","reset","barrier"}self.include_module_gates.update(_load_include_module(header,False,True).keys())self.prefix=""self.gatedefs=""self.strings=LabelledStringList()# Record of `RangePredicate` operations that set a "scratch" bit to 0 or 1# depending on the value of the predicate. This map is consulted when we# encounter a `Conditional` operation to see if the condition bit is one of# these scratch bits, which we can then replace with the original.self.range_preds:Dict[int,ScratchPredicate]=dict()ifinclude_gate_defsisNone:self.include_gate_defs=self.include_module_gatesself.include_gate_defs.update(NOPARAM_EXTRA_COMMANDS.keys())self.include_gate_defs.update(PARAM_EXTRA_COMMANDS.keys())self.prefix='OPENQASM 2.0;\ninclude "{}.inc";\n\n'.format(header)self.qregs=_retrieve_registers(cast(list[UnitID],qubits),QubitRegister)self.cregs=_retrieve_registers(cast(list[UnitID],bits),BitRegister)forreginself.qregs.values():ifregname_regex.match(reg.name)isNone:raiseQASMUnsupportedError(f"Invalid register name '{reg.name}'. QASM register names must ""begin with a lowercase letter and may only contain lowercase ""and uppercase letters, numbers, and underscores. ""Try renaming the register with `rename_units` first.")forbit_reginself.cregs.values():ifregname_regex.match(bit_reg.name)isNone:raiseQASMUnsupportedError(f"Invalid register name '{bit_reg.name}'. QASM register names ""must begin with a lowercase letter and may only contain ""lowercase and uppercase letters, numbers, and underscores. ""Try renaming the register with `rename_units` first.")else:# gate definition, no header necessary for fileself.include_gate_defs=include_gate_defsself.cregs={}self.qregs={}self.cregs_as_bitseqs=set(tuple(creg)forcreginself.cregs.values())# for holding condition values when writing Conditional blocks# the size changes when adding and removing scratch bitsself.scratch_reg=BitRegister(next(f"{_TEMP_BIT_REG_BASE}_{i}"foriinitertools.count()iff"{_TEMP_BIT_REG_BASE}_{i}"notinself.qregs),0,)# if a string writes to some classical variables, the string label and# the affected variables will be recorded.self.variable_writes:Dict[int,List[str]]=dict()deffresh_scratch_bit(self)->Bit:self.scratch_reg=BitRegister(self.scratch_reg.name,self.scratch_reg.size+1)returnBit(self.scratch_reg.name,self.scratch_reg.size-1)defremove_last_scratch_bit(self)->None:assertself.scratch_reg.size>0self.scratch_reg=BitRegister(self.scratch_reg.name,self.scratch_reg.size-1)defwrite_params(self,params:Optional[List[Union[float,Expr]]])->None:params_str=make_params_str(params)self.strings.add_string(params_str)defwrite_args(self,args:Sequence[UnitID])->None:args_str=make_args_str(args)self.strings.add_string(args_str)defmake_gate_definition(self,n_qubits:int,opstr:str,optype:OpType,n_params:Optional[int]=None,)->str:s="gate "+opstr+" "symbols:Optional[List[Symbol]]=Noneifn_paramsisnotNone:# need to add parameters to gate definitions+="("symbols=[Symbol("param"+str(index)+"/pi")forindexinrange(n_params)]symbols_header=[Symbol("param"+str(index))forindexinrange(n_params)]forsymbolinsymbols_header[:-1]:s+=symbol.name+", "s+=symbols_header[-1].name+") "# add qubits to gate definitionqubit_args=[Qubit(opstr+"q"+str(index))forindexinlist(range(n_qubits))]forqbinqubit_args[:-1]:s+=str(qb)+","s+=str(qubit_args[-1])+" {\n"# get rebased circuit for constructing qasmgate_circ=_get_gate_circuit(optype,qubit_args,symbols)# write circuit to qasms+=circuit_to_qasm_str(gate_circ,self.header,self.include_gate_defs,self.maxwidth)s+="}\n"returnsdefmark_as_written(self,label:int,written_variable:str)->None:iflabelinself.variable_writes:self.variable_writes[label].append(written_variable)else:self.variable_writes[label]=[written_variable]defadd_range_predicate(self,op:RangePredicateOp,args:List[Bit])->None:comparator,value=_parse_range(op.lower,op.upper,self.maxwidth)if(nothqs_header(self.header))andcomparator!="==":raiseQASMUnsupportedError("OpenQASM conditions must be on a register's fixed value.")bits=args[:-1]variable=args[0].reg_namedest_bit=str(args[-1])ifnothqs_header(self.header):assertisinstance(variable,str)ifop.n_inputs!=self.cregs[variable].size:raiseQASMUnsupportedError("OpenQASM conditions must be an entire classical register")ifbits!=self.cregs[variable].to_list():raiseQASMUnsupportedError("OpenQASM conditions must be a single classical register")label=self.strings.add_string("".join([f"if({variable}{comparator}{value}) "+f"{dest_bit} = 1;\n",f"if({variable}{_negate_comparator(comparator)}{value}) "+f"{dest_bit} = 0;\n",]))# Record this operation.# Later if we find a conditional based on dest_bit, we can replace dest_bit with# (variable, comparator, value), provided that variable hasn't been written to# in the mean time. (So we must watch for that, and remove the record from the# list if it is.)# Note that we only perform such rewrites for internal scratch bits.ifdest_bit.startswith(_TEMP_BIT_NAME):self.range_preds[label]=ScratchPredicate(variable,comparator,value,dest_bit)defreplace_condition(self,pred_label:int)->bool:"""Given the label of a predicate p=(var, comp, value, dest, label) we scan the lines after p: 1.if dest is the condition of a conditional line we replace dest with the predicate and do 2 for the inner command. 2.if either the variable or the dest gets written, we stop. returns true if a replacement is made. """assertpred_labelinself.range_predssuccess=Falsepred=self.range_preds[pred_label]line_labels=[]forlabelinrange(pred_label+1,self.strings.label):string=self.strings.get_string(label)ifstringisNone:continueline_labels.append(label)if"\n"notinstring:continuewritten_variables:List[str]=[]# (label, condition)conditions:List[Tuple[int,ConditionString]]=[]forlinline_labels:written_variables.extend(self.variable_writes.get(l,[]))cond=self.strings.conditions.get(l)ifcond:conditions.append((l,cond))iflen(conditions)==1andpred.dest==conditions[0][1].variable:# if the condition is dest, replace the condition with predsuccess=Trueifconditions[0][1].value==1:self.strings.conditions[conditions[0][0]]=ConditionString(pred.variable,pred.comparator,pred.value)else:assertconditions[0][1].value==0self.strings.conditions[conditions[0][0]]=ConditionString(pred.variable,_negate_comparator(pred.comparator),pred.value,)ifany(_vars_overlap(pred.dest,v)forvinwritten_variables)orany(_vars_overlap(pred.variable,v)forvinwritten_variables):returnsuccessline_labels.clear()conditions.clear()written_variables.clear()returnsuccessdefremove_unused_predicate(self,pred_label:int)->bool:"""Given the label of a predicate p=(var, comp, value, dest, label), we remove p if dest never appears after p."""assertpred_labelinself.range_predspred=self.range_preds[pred_label]forlabelinrange(pred_label+1,self.strings.label):string=self.strings.get_string(label)ifstringisNone:continueif(_var_appears(pred.dest,string)orlabelinself.strings.conditionsand_vars_overlap(pred.dest,self.strings.conditions[label].variable)):returnFalseself.range_preds.pop(pred_label)self.strings.del_string(pred_label)returnTruedefadd_conditional(self,op:Conditional,args:Sequence[UnitID])->None:control_bits=args[:op.width]ifop.width==1andhqs_header(self.header):variable=str(control_bits[0])else:variable=control_bits[0].reg_nameif(hqs_header(self.header)andcontrol_bits!=self.cregs[variable].to_list()):raiseQASMUnsupportedError("hqslib1 QASM conditions must be an entire classical ""register or a single bit")ifnothqs_header(self.header):ifop.width!=self.cregs[variable].size:raiseQASMUnsupportedError("OpenQASM conditions must be an entire classical register")ifcontrol_bits!=self.cregs[variable].to_list():raiseQASMUnsupportedError("OpenQASM conditions must be a single classical register")ifop.op.type==OpType.Phase:# Conditional phase is ignored.returnifop.op.type==OpType.RangePredicate:raiseQASMUnsupportedError("Conditional RangePredicate is currently unsupported.")# we assign the condition to a scratch bit, which we will later remove# if the condition variable is unchanged.scratch_bit=self.fresh_scratch_bit()pred_label=self.strings.add_string(f"if({variable}=={op.value}) "+f"{scratch_bit} = 1;\n")self.range_preds[pred_label]=ScratchPredicate(variable,"==",op.value,str(scratch_bit))# we will later add condition to all lines starting from next_labelnext_label=self.strings.labelself.add_op(op.op,args[op.width:])# add conditions to the lines after the predicateis_new_line=Trueforlabelinrange(next_label,self.strings.label):string=self.strings.get_string(label)assertstringisnotNoneifis_new_lineandstring!="\n":self.strings.conditions[label]=ConditionString(str(scratch_bit),"==",1)is_new_line="\n"instringifself.replace_condition(pred_label)andself.remove_unused_predicate(pred_label):# remove the unused scratch bitself.remove_last_scratch_bit()defadd_set_bits(self,op:SetBitsOp,args:List[Bit])->None:creg_name=args[0].reg_namebits,vals=zip(*sorted(zip(args,op.values)))# check if whole register can be set at onceifbits==tuple(self.cregs[creg_name].to_list()):value=int("".join(map(str,map(int,vals[::-1]))),2)label=self.strings.add_string(f"{creg_name} = {value};\n")self.mark_as_written(label,f"{creg_name}")else:forbit,valueinzip(bits,vals):label=self.strings.add_string(f"{bit} = {int(value)};\n")self.mark_as_written(label,f"{bit}")defadd_copy_bits(self,op:CopyBitsOp,args:List[Bit])->None:l_args=args[op.n_inputs:]r_args=args[:op.n_inputs]l_name=l_args[0].reg_namer_name=r_args[0].reg_name# check if whole register can be set at onceif(l_args==self.cregs[l_name].to_list()andr_args==self.cregs[r_name].to_list()):label=self.strings.add_string(f"{l_name} = {r_name};\n")self.mark_as_written(label,f"{l_name}")else:forbit_l,bit_rinzip(l_args,r_args):label=self.strings.add_string(f"{bit_l} = {bit_r};\n")self.mark_as_written(label,f"{bit_l}")defadd_multi_bit(self,op:MultiBitOp,args:List[Bit])->None:basic_op=op.basic_opbasic_n=basic_op.n_inputs+basic_op.n_outputs+basic_op.n_input_outputsn_args=len(args)assertn_args%basic_n==0arity=n_args//basic_n# If the operation is register-aligned we can write it more succinctly.poss_regs=[tuple(args[basic_n*i+j]foriinrange(arity))forjinrange(basic_n)]ifall(poss_reginself.cregs_as_bitseqsforposs_reginposs_regs):# The operation is register-aligned.self.add_op(basic_op,[poss_regs[j][0].reg_nameforjinrange(basic_n)])# type: ignoreelse:# The operation is not register-aligned.foriinrange(arity):basic_args=args[basic_n*i:basic_n*(i+1)]self.add_op(basic_op,basic_args)defadd_explicit_op(self,op:Op,args:List[Bit])->None:# &, ^ and | gatesopstr=str(op)ifopstrnotin_classical_gatestr_map:raiseQASMUnsupportedError(f"Classical gate {opstr} not supported.")label=self.strings.add_string(f"{args[-1]} = {args[0]}{_classical_gatestr_map[opstr]}{args[1]};\n")self.mark_as_written(label,f"{args[-1]}")defadd_classical_exp_box(self,op:ClassicalExpBox,args:List[Bit])->None:out_args=args[op.get_n_i():]iflen(out_args)==1:label=self.strings.add_string(f"{out_args[0]} = {str(op.get_exp())};\n")self.mark_as_written(label,f"{out_args[0]}")elif(out_args==self.cregs[out_args[0].reg_name].to_list()[:op.get_n_io()+op.get_n_o()]):label=self.strings.add_string(f"{out_args[0].reg_name} = {str(op.get_exp())};\n")self.mark_as_written(label,f"{out_args[0].reg_name}")else:raiseQASMUnsupportedError(f"ClassicalExpBox only supported"" for writing to a single bit or whole registers.")defadd_wired_clexpr(self,op:ClExprOp,args:List[Bit])->None:wexpr:WiredClExpr=op.expr# 1. Determine the mappings from bit variables to bits and from register# variables to registers.expr:ClExpr=wexpr.exprbit_posn:dict[int,int]=wexpr.bit_posnreg_posn:dict[int,list[int]]=wexpr.reg_posnoutput_posn:list[int]=wexpr.output_posninput_bits:dict[int,Bit]={i:args[j]fori,jinbit_posn.items()}input_regs:dict[int,BitRegister]={}all_cregs=set(self.cregs.values())fori,posnsinreg_posn.items():reg_args=[args[j]forjinposns]forcreginall_cregs:ifcreg.to_list()==reg_args:input_regs[i]=cregbreakelse:raiseQASMUnsupportedError(f"ClExprOp ({wexpr}) contains a register variable (r{i}) ""that is not wired to any BitRegister in the circuit.")# 2. Write the left-hand side of the assignment.output_repr:Optional[str]=Noneoutput_args:list[Bit]=[args[j]forjinoutput_posn]n_output_args=len(output_args)expect_reg_output=has_reg_output(expr.op)ifn_output_args==0:raiseQASMUnsupportedError("Expression has no output.")elifn_output_args==1:output_arg=output_args[0]output_repr=output_arg.reg_nameifexpect_reg_outputelsestr(output_arg)else:ifnotexpect_reg_output:raiseQASMUnsupportedError("Unexpected output for operation.")forcreginall_cregs:ifcreg.to_list()==output_args:output_repr=creg.nameself.strings.add_string(f"{output_repr} = ")# 3. Write the right-hand side of the assignment.self.strings.add_string(expr.as_qasm(input_bits=input_bits,input_regs=input_regs))self.strings.add_string(";\n")defadd_wasm(self,op:WASMOp,args:List[Bit])->None:inputs:List[str]=[]outputs:List[str]=[]forreglist,sizesin[(inputs,op.input_widths),(outputs,op.output_widths)]:forin_widthinsizes:bits=args[:in_width]args=args[in_width:]regname=bits[0].reg_nameifbits!=list(self.cregs[regname]):QASMUnsupportedError("WASM ops must act on entire registers.")reglist.append(regname)ifoutputs:label=self.strings.add_string(f"{', '.join(outputs)} = ")self.strings.add_string(f"{op.func_name}({', '.join(inputs)});\n")forvariableinoutputs:self.mark_as_written(label,variable)defadd_measure(self,args:Sequence[UnitID])->None:label=self.strings.add_string(f"measure {args[0]} -> {args[1]};\n")self.mark_as_written(label,f"{args[1]}")defadd_zzphase(self,param:Union[float,Expr],args:Sequence[UnitID])->None:# as op.params returns reduced parameters, we can assume# that 0 <= param < 4ifparam>1:# first get in to 0 <= param < 2 rangeparam=Decimal(str(param))%Decimal("2")# then flip 1 <= param < 2 range into# -1 <= param < 0ifparam>1:param=-2+paramself.strings.add_string("RZZ")self.write_params([param])self.write_args(args)defadd_data(self,op:BarrierOp,args:Sequence[UnitID])->None:ifop.data=="":opstr=_tk_to_qasm_noparams[OpType.Barrier]else:opstr=op.dataself.strings.add_string(opstr)self.strings.add_string(" ")self.write_args(args)defadd_gate_noparams(self,op:Op,args:Sequence[UnitID])->None:self.strings.add_string(_tk_to_qasm_noparams[op.type])self.strings.add_string(" ")self.write_args(args)defadd_gate_params(self,op:Op,args:Sequence[UnitID])->None:optype,params=_get_optype_and_params(op)self.strings.add_string(_tk_to_qasm_params[optype])self.write_params(params)self.write_args(args)defadd_extra_noparams(self,op:Op,args:Sequence[UnitID])->Tuple[str,str]:optype=op.typeopstr=_tk_to_qasm_extra_noparams[optype]gatedefstr=""ifopstrnotinself.added_gate_definitions:self.added_gate_definitions.add(opstr)gatedefstr=self.make_gate_definition(op.n_qubits,opstr,optype)mainstr=opstr+" "+make_args_str(args)returngatedefstr,mainstrdefadd_extra_params(self,op:Op,args:Sequence[UnitID])->Tuple[str,str]:optype,params=_get_optype_and_params(op)assertparamsisnotNoneopstr=_tk_to_qasm_extra_params[optype]gatedefstr=""ifopstrnotinself.added_gate_definitions:self.added_gate_definitions.add(opstr)gatedefstr=self.make_gate_definition(op.n_qubits,opstr,optype,len(params))mainstr=opstr+make_params_str(params)+make_args_str(args)returngatedefstr,mainstrdefadd_op(self,op:Op,args:Sequence[UnitID])->None:optype,_params=_get_optype_and_params(op)ifoptype==OpType.RangePredicate:assertisinstance(op,RangePredicateOp)self.add_range_predicate(op,cast(List[Bit],args))elifoptype==OpType.Conditional:assertisinstance(op,Conditional)self.add_conditional(op,args)elifoptype==OpType.Phase:# global phase is ignored in QASMpasselifoptype==OpType.SetBits:assertisinstance(op,SetBitsOp)self.add_set_bits(op,cast(List[Bit],args))elifoptype==OpType.CopyBits:assertisinstance(op,CopyBitsOp)self.add_copy_bits(op,cast(List[Bit],args))elifoptype==OpType.MultiBit:assertisinstance(op,MultiBitOp)self.add_multi_bit(op,cast(List[Bit],args))elifoptypein(OpType.ExplicitPredicate,OpType.ExplicitModifier):self.add_explicit_op(op,cast(List[Bit],args))elifoptype==OpType.ClassicalExpBox:assertisinstance(op,ClassicalExpBox)self.add_classical_exp_box(op,cast(List[Bit],args))elifoptype==OpType.ClExpr:assertisinstance(op,ClExprOp)self.add_wired_clexpr(op,cast(List[Bit],args))elifoptype==OpType.WASM:assertisinstance(op,WASMOp)self.add_wasm(op,cast(List[Bit],args))elifoptype==OpType.Measure:self.add_measure(args)elifhqs_header(self.header)andoptype==OpType.ZZPhase:# special handling for zzphaseassertlen(op.params)==1self.add_zzphase(op.params[0],args)elifoptype==OpType.Barrierandself.header=="hqslib1_dev":assertisinstance(op,BarrierOp)self.add_data(op,args)elif(optypein_tk_to_qasm_noparamsand_tk_to_qasm_noparams[optype]inself.include_module_gates):self.add_gate_noparams(op,args)elif(optypein_tk_to_qasm_paramsand_tk_to_qasm_params[optype]inself.include_module_gates):self.add_gate_params(op,args)elifoptypein_tk_to_qasm_extra_noparams:gatedefstr,mainstr=self.add_extra_noparams(op,args)self.gatedefs+=gatedefstrself.strings.add_string(mainstr)elifoptypein_tk_to_qasm_extra_params:gatedefstr,mainstr=self.add_extra_params(op,args)self.gatedefs+=gatedefstrself.strings.add_string(mainstr)else:raiseQASMUnsupportedError("Cannot print command of type: {}".format(op.get_name()))deffinalize(self)->str:# try removing unused predicatespred_labels=list(self.range_preds.keys())forlabelinpred_labels:# try replacing conditions with a predicateself.replace_condition(label)# try removing the predicateself.remove_unused_predicate(label)reg_strings=LabelledStringList()forreginself.qregs.values():reg_strings.add_string(f"qreg {reg.name}[{reg.size}];\n")forbit_reginself.cregs.values():reg_strings.add_string(f"creg {bit_reg.name}[{bit_reg.size}];\n")ifself.scratch_reg.size>0:reg_strings.add_string(f"creg {self.scratch_reg.name}[{self.scratch_reg.size}];\n")return(self.prefix+self.gatedefs+_filtered_qasm_str(reg_strings.get_full_string()+self.strings.get_full_string()))
[docs]defcircuit_to_qasm_io(circ:Circuit,stream_out:TextIO,header:str="qelib1",include_gate_defs:Optional[Set[str]]=None,maxwidth:int=32,)->None:"""Convert a Circuit to QASM and write to a text stream. Classical bits in the pytket circuit must be singly-indexed. Note that this will not account for implicit qubit permutations in the Circuit. :param circ: pytket circuit :param stream_out: text stream to be written to :param header: qasm header (default "qelib1") :param include_gate_defs: optional set of gates to include :param maxwidth: maximum allowed width of classical registers (default 32) """stream_out.write(circuit_to_qasm_str(circ,header=header,include_gate_defs=include_gate_defs,maxwidth=maxwidth))