# 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."""Collection of methods to calculate symbolic statevectors and unitaries,for symbolic circuits. This uses the sympy.physics.quantum module and producessympy objects. The implementations are slow and scale poorly, so this isonly suitable for very small (up to 5 qubit) circuits."""fromtypingimportCallable,Dict,List,Optional,Type,Union,castimportnumpyasnpimportsympyfromsympyimport(BlockDiagMatrix,BlockMatrix,Expr,Identity,ImmutableMatrix,Matrix,Mul,I,diag,eye,zeros,)fromsympy.physics.quantumimportgateassymgatefromsympy.physics.quantumimportrepresentfromsympy.physics.quantum.tensorproductimportmatrix_tensor_productfromsympy.physics.quantum.qapplyimportqapplyfromsympy.physics.quantum.qubitimportQubit,matrix_to_qubitfrompytket.circuitimportCircuit,Op,OpType# gates that have an existing definition in sympy_FIXED_GATE_MAP:Dict[OpType,Type[symgate.Gate]]={OpType.H:symgate.HadamardGate,OpType.S:symgate.PhaseGate,OpType.CX:symgate.CNotGate,OpType.SWAP:symgate.SwapGate,OpType.T:symgate.TGate,OpType.X:symgate.XGate,OpType.Y:symgate.YGate,OpType.Z:symgate.ZGate,}ParamsType=List[Union[Expr,float]]# Make sure the return matrix is Immutable https://github.com/sympy/sympy/issues/18733SymGateFunc=Callable[[ParamsType],ImmutableMatrix]SymGateMap=Dict[OpType,SymGateFunc]# Begin matrix definitions for symbolic OpTypes# matches internal TKET definitions# see OpType documentationdefsymb_controlled(target:SymGateFunc)->SymGateFunc:returnlambdax:ImmutableMatrix(BlockDiagMatrix(Identity(2),target(x)))defsymb_rz(params:ParamsType)->ImmutableMatrix:returnImmutableMatrix([[sympy.exp(-I*(sympy.pi/2)*params[0]),0],[0,sympy.exp(I*(sympy.pi/2)*params[0])],])defsymb_rx(params:ParamsType)->ImmutableMatrix:costerm=sympy.cos((sympy.pi/2)*params[0])sinterm=-I*sympy.sin((sympy.pi/2)*params[0])returnImmutableMatrix([[costerm,sinterm],[sinterm,costerm],])defsymb_ry(params:ParamsType)->ImmutableMatrix:costerm=sympy.cos((sympy.pi/2)*params[0])sinterm=sympy.sin((sympy.pi/2)*params[0])returnImmutableMatrix([[costerm,-sinterm],[sinterm,costerm],])defsymb_u3(params:ParamsType)->ImmutableMatrix:theta,phi,lam=paramscosterm=sympy.cos((sympy.pi/2)*theta)sinterm=sympy.sin((sympy.pi/2)*theta)returnImmutableMatrix([[costerm,-sinterm*sympy.exp(I*sympy.pi*lam)],[sinterm*sympy.exp(I*sympy.pi*phi),costerm*sympy.exp(I*sympy.pi*(phi+lam)),],])defsymb_u2(params:ParamsType)->ImmutableMatrix:returnsymb_u3([0.5]+params)defsymb_u1(params:ParamsType)->ImmutableMatrix:returnsymb_u3([0.0,0.0]+params)defsymb_tk1(params:ParamsType)->ImmutableMatrix:returnsymb_rz([params[0]])*symb_rx([params[1]])*symb_rz([params[2]])defsymb_tk2(params:ParamsType)->ImmutableMatrix:return(symb_xxphase([params[0]])*symb_yyphase([params[1]])*symb_zzphase([params[2]]))defsymb_iswap(params:ParamsType)->ImmutableMatrix:alpha=params[0]costerm=sympy.cos((sympy.pi/2)*alpha)sinterm=sympy.sin((sympy.pi/2)*alpha)returnImmutableMatrix([[1,0,0,0],[0,costerm,I*sinterm,0],[0,I*sinterm,costerm,0],[0,0,0,1],])defsymb_phasediswap(params:ParamsType)->ImmutableMatrix:p,alpha=paramscosterm=sympy.cos((sympy.pi/2)*alpha)sinterm=I*sympy.sin((sympy.pi/2)*alpha)phase=sympy.exp(2*I*sympy.pi*p)returnImmutableMatrix([[1,0,0,0],[0,costerm,sinterm*phase,0],[0,sinterm/phase,costerm,0],[0,0,0,1],])defsymb_xxphase(params:ParamsType)->ImmutableMatrix:alpha=params[0]c=sympy.cos((sympy.pi/2)*alpha)s=-I*sympy.sin((sympy.pi/2)*alpha)returnImmutableMatrix([[c,0,0,s],[0,c,s,0],[0,s,c,0],[s,0,0,c],])defsymb_yyphase(params:ParamsType)->ImmutableMatrix:alpha=params[0]c=sympy.cos((sympy.pi/2)*alpha)s=I*sympy.sin((sympy.pi/2)*alpha)returnImmutableMatrix([[c,0,0,s],[0,c,-s,0],[0,-s,c,0],[s,0,0,c],])defsymb_zzphase(params:ParamsType)->ImmutableMatrix:alpha=params[0]t=sympy.exp(I*(sympy.pi/2)*alpha)returnImmutableMatrix(diag(1/t,t,t,1/t))defsymb_xxphase3(params:ParamsType)->ImmutableMatrix:xxphase2=symb_xxphase(params)res1=matrix_tensor_product(xxphase2,eye(2))res2=Matrix(BlockMatrix([[xxphase2[:2,:2],zeros(2),xxphase2[:2,2:],zeros(2)],[zeros(2),xxphase2[:2,:2],zeros(2),xxphase2[:2,2:]],[xxphase2[2:,:2],zeros(2),xxphase2[2:,2:],zeros(2)],[zeros(2),xxphase2[2:,:2],zeros(2),xxphase2[2:,2:]],]))res3=matrix_tensor_product(eye(2),xxphase2)res=ImmutableMatrix(res1*res2*res3)returnresdefsymb_phasedx(params:ParamsType)->ImmutableMatrix:alpha,beta=paramsreturnsymb_rz([beta])*symb_rx([alpha])*symb_rz([-beta])defsymb_eswap(params:ParamsType)->ImmutableMatrix:alpha=params[0]c=sympy.cos((sympy.pi/2)*alpha)s=-I*sympy.sin((sympy.pi/2)*alpha)t=sympy.exp(-I*(sympy.pi/2)*alpha)returnImmutableMatrix([[t,0,0,0],[0,c,s,0],[0,s,c,0],[0,0,0,t],])defsymb_fsim(params:ParamsType)->ImmutableMatrix:alpha,beta=paramsc=sympy.cos(sympy.pi*alpha)s=-I*sympy.sin(sympy.pi*alpha)t=sympy.exp(-I*sympy.pi*beta)returnImmutableMatrix([[1,0,0,0],[0,c,s,0],[0,s,c,0],[0,0,0,t],])defsymb_gpi(params:ParamsType)->ImmutableMatrix:t=sympy.exp(I*sympy.pi*params[0])returnImmutableMatrix([[0,1/t],[t,0],])defsymb_gpi2(params:ParamsType)->ImmutableMatrix:t=sympy.exp(I*sympy.pi*params[0])c=1/sympy.sqrt(2)returnc*ImmutableMatrix([[1,-I/t],[-I*t,1],])defsymb_aams(params:ParamsType)->ImmutableMatrix:alpha,beta,gamma=paramsc=sympy.cos(sympy.pi/2*alpha)s=sympy.sin(sympy.pi/2*alpha)s1=-I*sympy.exp(I*sympy.pi*(-beta-gamma))*ss2=-I*sympy.exp(I*sympy.pi*(-beta+gamma))*ss3=-I*sympy.exp(I*sympy.pi*(beta-gamma))*ss4=-I*sympy.exp(I*sympy.pi*(beta+gamma))*sreturnImmutableMatrix([[c,0,0,s1],[0,c,s2,0],[0,s3,c,0],[s4,0,0,c],])# end symbolic matrix definitions
[docs]classSymGateRegister:"""Static class holding mapping from OpType to callable generating symbolic matrix. Allows users to add their own definitions, or override existing definitions."""_g_map:SymGateMap={OpType.Rx:symb_rx,OpType.Ry:symb_ry,OpType.Rz:symb_rz,OpType.TK1:symb_tk1,OpType.TK2:symb_tk2,OpType.U1:symb_u1,OpType.U2:symb_u2,OpType.U3:symb_u3,OpType.CRx:symb_controlled(symb_rx),OpType.CRy:symb_controlled(symb_ry),OpType.CRz:symb_controlled(symb_rz),OpType.CU1:symb_controlled(symb_u1),OpType.CU3:symb_controlled(symb_u3),OpType.ISWAP:symb_iswap,OpType.PhasedISWAP:symb_phasediswap,OpType.XXPhase:symb_xxphase,OpType.YYPhase:symb_yyphase,OpType.ZZPhase:symb_zzphase,OpType.XXPhase3:symb_xxphase3,OpType.PhasedX:symb_phasedx,OpType.ESWAP:symb_eswap,OpType.FSim:symb_fsim,OpType.GPI:symb_gpi,OpType.GPI2:symb_gpi2,OpType.AAMS:symb_aams,}
[docs]@classmethoddefregister_func(cls,typ:OpType,f:SymGateFunc,replace:bool=False)->None:"""Register a callable for an optype. :param typ: OpType to register :type typ: OpType :param f: Callable for generating symbolic matrix. :type f: SymGateFunc :param replace: Whether to replace existing entry, defaults to False :type replace: bool, optional """iftypnotincls._g_maporreplace:cls._g_map[typ]=f
[docs]@classmethoddefis_registered(cls,typ:OpType)->bool:"""Check if type has a callable registered."""returntypincls._g_map
def_op_to_sympy_gate(op:Op,targets:List[int])->symgate.Gate:# convert Op to sympy gateifop.typein_FIXED_GATE_MAP:return_FIXED_GATE_MAP[op.type](*targets)ifop.is_gate():# check if symbolic definition is neededfloat_params=all(isinstance(p,float)forpinop.params)else:raiseValueError(f"Circuit can only contain unitary gates, operation {op} not valid.")# pytket matrix basis indexing is in opposite order to sympytargets=targets[::-1]if(notfloat_params)andSymGateRegister.is_registered(op.type):u_mat=SymGateRegister.get_func(op.type)(op.params)else:try:# use internal tket unitary definitionu_mat=ImmutableMatrix(op.get_unitary())exceptRuntimeErrorase:# to catch tket failure to get Op unitary# most likely due to symbolic parameters.raiseValueError(f"{op.type} is not supported for symbolic conversion."" Try registering your own symbolic matrix representation"" with SymGateRegister.func.")fromegate=symgate.UGate(targets,u_mat)returngate
[docs]defcircuit_to_symbolic_gates(circ:Circuit)->Mul:"""Generate a multiplication expression of sympy gates from Circuit :param circ: Input circuit :type circ: Circuit :raises ValueError: If circ does not match a unitary operation. :return: Symbolic gate multiplication expression. :rtype: Mul """outmat=symgate.IdentityGate(0)nqb=circ.n_qubitsqubit_map={qb:nqb-1-ifori,qbinenumerate(circ.qubits)}forcomincirc:op=com.opifop.type==OpType.Barrier:continueargs=com.argstry:targs=[qubit_map[q]forqinargs]# type: ignoreexceptKeyErrorase:raiseValueError(f"Gates can only act on qubits. Operation {com} not valid.")fromegate=_op_to_sympy_gate(op,targs)outmat=gate*outmatforiinrange(len(qubit_map)):outmat=symgate.IdentityGate(i)*outmatreturnoutmat*sympy.exp((circ.phase*sympy.pi*I))
[docs]defcircuit_to_symbolic_unitary(circ:Circuit)->ImmutableMatrix:"""Generate a symbolic unitary from Circuit. Unitary matches pytket default ILO BasisOrder. :param circ: Input circuit :type circ: Circuit :return: Symbolic unitary. :rtype: ImmutableMatrix """gates=circuit_to_symbolic_gates(circ)nqb=circ.n_qubitstry:returncast(ImmutableMatrix,represent(gates,nqubits=circ.n_qubits))exceptNotImplementedError:# sympy can't represent n>1 qubit unitaries very well# so if it fails we will just calculate columns using the statevectors# for all possible input basis statesmatrix_dim=1<<nqbinput_states=(Qubit(f"{i:0{nqb}b}")foriinrange(matrix_dim))outmat=Matrix([])forcol,input_stateinenumerate(input_states):outmat=outmat.col_insert(col,represent(qapply(gates*input_state)))returnImmutableMatrix(outmat)
[docs]defcircuit_apply_symbolic_qubit(circ:Circuit,input_qb:Expr)->Qubit:"""Apply circuit to an input state to calculate output symbolic state. :param circ: Input Circuit. :type circ: Circuit :param input_qb: Sympy Qubit expression corresponding to a state. :type input_qb: Expr :return: Output state after circuit acts on input_qb. :rtype: Qubit """gates=circuit_to_symbolic_gates(circ)returncast(Qubit,qapply(gates*input_qb))
[docs]defcircuit_apply_symbolic_statevector(circ:Circuit,input_state:Optional[Union[np.ndarray,ImmutableMatrix]]=None)->ImmutableMatrix:"""Apply circuit to an optional input statevector to calculate output symbolic statevector. If no input statevector given, the all zero state is assumed. Statevector follows pytket default ILO BasisOrder. :param circ: Input Circuit. :type circ: Circuit :param input_state: Input statevector as a column vector, defaults to None. :type input_state: Optional[Union[np.ndarray, ImmutableMatrix]], optional :return: Symbolic state after circ acts on input_state. :rtype: ImmutableMatrix """ifinput_state:input_qb=matrix_to_qubit(input_state)else:input_qb=Qubit("0"*circ.n_qubits)returncast(ImmutableMatrix,represent(circuit_apply_symbolic_qubit(circ,cast(Qubit,input_qb))),)