# Copyright 2021-2024 Cambridge Quantum Computing Ltd.## 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."""Interface with discopy======================Module containing the functions to convert from and to discopy.This work is based on DisCoPy (https://discopy.org/) which is releasedunder the BSD 3-Clause "New" or "Revised" License."""from__future__importannotationsfromtypingimportcast,Type,TypeVar,Unionfrompackagingimportversionfromlambeq.backendimportgrammaraslgfromlambeq.backendimportquantumaslqfromlambeq.backendimporttensorasltMIN_DISCOPY_VERSION='1.1.0'try:importdiscopyexceptImportErrorasie:raiseImportError('`import discopy` failed. Please install discopy by 'f'running `pip install "discopy>={MIN_DISCOPY_VERSION}"`.')fromieelse:ifversion.parse(discopy.__version__)<version.parse(MIN_DISCOPY_VERSION):raiseDeprecationWarning('Conversion from lambeq to discopy and vice versa 'f'requires discopy>={MIN_DISCOPY_VERSION}. Please update discopy 'f'by running `pip install "discopy>={MIN_DISCOPY_VERSION}"`.')fromdiscopyimportquantumasdq# noqa: E402,I100fromdiscopyimporttensorasdt# noqa: E402fromdiscopy.grammarimportpregroupasdg# noqa: E402_LAMBEQ_QUANTUM_BOX_TY=Union[type[lq.Box],lq.Box]_DISCOPY_QUANTUM_BOX_TY=Union[type[dq.Box],dq.Box]_QUANTUM_MAP_L2D_TY=dict[_LAMBEQ_QUANTUM_BOX_TY,_DISCOPY_QUANTUM_BOX_TY]_QUANTUM_MAP_D2L_TY=dict[_DISCOPY_QUANTUM_BOX_TY,_LAMBEQ_QUANTUM_BOX_TY]QUANTUM_MAPPINGS_L2D:_QUANTUM_MAP_L2D_TY={lq.Discard:dq.Discard,lq.Encode:dq.Encode,lq.Measure:dq.Measure,lq.Bra:dq.Bra,lq.Ket:dq.Ket,lq.Sqrt:dq.gates.Sqrt,lq.Scalar:dq.gates.Scalar,lq.SWAP:dq.SWAP,lq.H:dq.H,lq.S:dq.S,lq.T:dq.T,lq.X:dq.X,lq.Y:dq.Y,lq.Z:dq.Z,lq.Rx:dq.Rx,lq.Ry:dq.Ry,lq.Rz:dq.Rz}QUANTUM_MAPPINGS_D2L:_QUANTUM_MAP_D2L_TY={val:keyforkey,valinQUANTUM_MAPPINGS_L2D.items()}_LAMBEQ_DIAGRAM_TY=Union[lg.Diagram,lq.Diagram,lt.Diagram]_DISCOPY_DIAGRAM_TY=Union[dg.Diagram,dq.Circuit,dt.Diagram]_DISCOPY_TY_VAR=TypeVar('_DISCOPY_TY_VAR',dg.Ty,dq.Ty,dt.Dim)_LAMBEQ_TY_VAR=TypeVar('_LAMBEQ_TY_VAR',lg.Ty,lq.Ty,lt.Dim)_DISCOPY_BOX_VAR=TypeVar('_DISCOPY_BOX_VAR',dg.Box,dq.Box,dt.Box)_LAMBEQ_BOX_VAR=TypeVar('_LAMBEQ_BOX_VAR',lg.Box,lq.Box,lt.Box,lq.Diagram)_DISCOPY_ENTITY=TypeVar('_DISCOPY_ENTITY',dg.Ty,dq.Ty,dt.Dim,dg.Box,dq.Box,dt.Box)def_unwind_discopy_entity(entity:_DISCOPY_ENTITY)->_DISCOPY_ENTITY:"""Unwind a discopy entity."""ifentity.zin(None,0):returnentityfor_inrange(abs(entity.z)):entity=entity.lifentity.z>0elseentity.rreturnentitydef_wind_discopy_entity(entity:_DISCOPY_ENTITY,z:int)->_DISCOPY_ENTITY:"""Wind a discopy entity."""for_inrange(abs(z)):entity=entity.rifz>0elseentity.lreturnentity
[docs]defconvert_quantum_l2d(box:lq.Box)->dq.Box:dq_box:_DISCOPY_QUANTUM_BOX_TYifisinstance(box,lq.Daggered):op=convert_quantum_l2d(box.dagger()).dagger()elifisinstance(box,lq.Controlled):op=dq.Controlled(controlled=convert_quantum_l2d(box.controlled),distance=box.distance)elifisinstance(box,(lq.Rx,lq.Ry,lq.Rz,lq.Scalar,lq.Sqrt)):dq_box=cast(type[Union[dq.Rx,dq.Ry,dq.Rz,dq.gates.Scalar,dq.gates.Sqrt]],QUANTUM_MAPPINGS_L2D[type(box)])op=dq_box(box.data)elifisinstance(box,(lq.Bra,lq.Ket)):dq_box=cast(type[Union[dq.Bra,dq.Ket]],QUANTUM_MAPPINGS_L2D[type(box)])op=dq_box(box.bit)elifisinstance(box,(lq.Discard,lq.Encode,lq.Measure)):dq_box=cast(type[Union[dq.Discard,dq.Encode,dq.Measure]],QUANTUM_MAPPINGS_L2D[type(box)])op=dq_box()else:try:op=cast(dq.Box,QUANTUM_MAPPINGS_L2D[box.unwind()])# Need to catch these `z` values because# `discopy.quantum.circuit.Box.rotate` rotates# even if passed arg is `None` or `0`.ifbox.znotin(None,0):op=op.rotate(box.z)exceptKeyError:# pragma: no coverraiseNotImplementedError(box)returnop
[docs]defconvert_quantum_d2l(box:dq.Box)->lq.Box|lq.Diagram:lq_box:_LAMBEQ_QUANTUM_BOX_TYifbox.is_dagger:op=convert_quantum_d2l(box.dagger()).dagger()elifisinstance(box,dq.Controlled):controlled=convert_quantum_d2l(box.controlled)ifisinstance(controlled,lq.Diagram):op=controlledelse:op=lq.Controlled(controlled=controlled,distance=box.distance)elifisinstance(box,(dq.Rx,dq.Ry,dq.Rz,dq.gates.Scalar,dq.gates.Sqrt)):lq_box=cast(type[Union[lq.Rx,lq.Ry,lq.Rz,lq.Scalar,lq.Sqrt]],QUANTUM_MAPPINGS_D2L[type(box)])op=lq_box(cast(float,box.data))elifisinstance(box,(dq.Bra,dq.Ket)):ifbox.bitstring:lq_box=cast(type[Union[lq.Bra,lq.Ket]],QUANTUM_MAPPINGS_D2L[type(box)])op=lq_box(*box.bitstring)else:op=lq.Id()elifisinstance(box,(dq.Discard,dq.Encode)):lq_box=cast(type[Union[lq.Discard,lq.Encode]],QUANTUM_MAPPINGS_D2L[type(box)])op=lq_box()elifisinstance(box,dq.Measure):ifnotbox.destructive:raiseNotImplementedError(f'Non-destructive measurement {box} ''not supported.')lq_box=cast(type[lq.Measure],QUANTUM_MAPPINGS_D2L[type(box)])op=lq_box()else:try:op=cast(lq.Box,QUANTUM_MAPPINGS_D2L[_unwind_discopy_entity(box)])op=op.rotate(box.z)exceptKeyError:# pragma: no coverraiseNotImplementedError(box)returnop
[docs]defconvert_tensor_l2d(box:lt.Box)->dt.Box:ifisinstance(box,lt.Daggered):undaggered=cast(lt.Box,box.dagger())op=convert_tensor_l2d(undaggered).dagger()elifisinstance(box,(lt.Cap,lt.Cup)):cups_caps={lt.Cap:dt.Cap,lt.Cup:dt.Cup}op=cups_caps[type(box)](left=ty_l2d(box.left,dt.Dim),right=ty_l2d(box.right,dt.Dim))elifisinstance(box,lt.Swap):op=dt.Swap(left=ty_l2d(box.left,dt.Dim),right=ty_l2d(box.right,dt.Dim))elifisinstance(box,lt.Spider):op=dt.Spider(n_legs_in=box.n_legs_in,n_legs_out=box.n_legs_out,typ=ty_l2d(box.type,dt.Dim))elifisinstance(box,lt.Box):op=dt.Box(name=box.name,dom=ty_l2d(box.dom,dt.Dim),cod=ty_l2d(box.cod,dt.Dim),data=box.data,z=box.z)else:# pragma: no coverraiseNotImplementedError(box)returnop
[docs]defconvert_tensor_d2l(box:dt.Box)->lt.Box:ifbox.is_dagger:op=convert_tensor_d2l(box.dagger()).dagger()elifisinstance(box,dt.Cap):left=ty_d2l(box.left,lt.Dim)right=ty_d2l(box.right,lt.Dim)op=lt.Cap(left=left,right=right,is_reversed=left==right.l)elifisinstance(box,dt.Cup):left=ty_d2l(box.left,lt.Dim)right=ty_d2l(box.right,lt.Dim)op=lt.Cup(left=left,right=right,is_reversed=left==right.r)elifisinstance(box,dt.Swap):op=lt.Swap(left=ty_d2l(box.left,lt.Dim),right=ty_d2l(box.right,lt.Dim))elifisinstance(box,dt.Spider):op=lt.Spider(type=ty_d2l(box.typ,lt.Dim),n_legs_in=len(box.dom),n_legs_out=len(box.cod))elifisinstance(box,dt.Box):op=lt.Box(name=box.name,dom=ty_d2l(box.dom,lt.Dim),cod=ty_d2l(box.cod,lt.Dim),data=box.data,z=box.z)else:# pragma: no coverraiseNotImplementedError(box)returnop# type: ignore[no-any-return]
[docs]defconvert_grammar_l2d(box:lg.Box)->dg.Box:ifisinstance(box,lg.Daggered):op=convert_grammar_l2d(box.dagger()).dagger()elifisinstance(box,(lg.Cap,lg.Cup)):cups_caps={lg.Cap:dg.Cap,lg.Cup:dg.Cup}op=cups_caps[type(box)](left=ty_l2d(box.left,dg.Ty),right=ty_l2d(box.right,dg.Ty))elifisinstance(box,lg.Swap):op=dg.Swap(left=ty_l2d(box.left,dg.Ty),right=ty_l2d(box.right,dg.Ty))elifisinstance(box,lg.Spider):op=dg.Spider(n_legs_in=box.n_legs_in,n_legs_out=box.n_legs_out,typ=ty_l2d(box.type,dg.Ty))elifisinstance(box,lg.Word):op=dg.Word(name=box.name,cod=ty_l2d(box.cod,dg.Ty),z=box.z)elifisinstance(box,lg.Box):op=dg.Box(name=box.name,dom=ty_l2d(box.dom,dg.Ty),cod=ty_l2d(box.cod,dg.Ty),z=box.z)else:# pragma: no coverraiseNotImplementedError(box)returnop
[docs]defconvert_grammar_d2l(box:dg.Box)->lg.Box:ifbox.is_dagger:op=convert_grammar_d2l(box.dagger()).dagger()elifisinstance(box,dg.Cap):left=ty_d2l(box.left,lg.Ty)right=ty_d2l(box.right,lg.Ty)op=lg.Cap(left=left,right=right,is_reversed=left==right.l)elifisinstance(box,dg.Cup):left=ty_d2l(box.left,lg.Ty)right=ty_d2l(box.right,lg.Ty)op=lg.Cup(left=left,right=right,is_reversed=left==right.r)elifisinstance(box,dg.Swap):op=lg.Swap(left=ty_d2l(box.left,lg.Ty),right=ty_d2l(box.right,lg.Ty))elifisinstance(box,dg.Spider):op=lg.Spider(type=ty_d2l(box.typ,lg.Ty),n_legs_in=len(box.dom),n_legs_out=len(box.cod))elifisinstance(box,dg.Word):op=lg.Word(name=box.name,cod=ty_d2l(box.cod,lg.Ty),z=box.z)elifisinstance(box,dg.Box):op=lg.Box(name=box.name,dom=ty_d2l(box.dom,lg.Ty),cod=ty_d2l(box.cod,lg.Ty),z=box.z)else:# pragma: no coverraiseNotImplementedError(box)returnop
[docs]defto_discopy(diagram:_LAMBEQ_DIAGRAM_TY)->_DISCOPY_DIAGRAM_TY:"""Takes a :class:`lambeq.backend.grammar.Diagram`, :class:`lambeq.backend.quantum.Diagram`, or :class:`lambeq.backend.tensor.Diagram`, and converts it to a :class:`discopy.grammar.pregroup.Diagram`, :class:`discopy.quantum.Diagram`, or :class:`discopy.tensor.Diagram`, respectively. Parameters ---------- diagram : `lambeq.backend.grammar.Diagram` | :class:`lambeq.backend.quantum.Diagram` | :class:`lambeq.backend.tensor.Diagram` The diagram to convert. Returns ------- :class::class:`discopy.grammar.pregroup.Diagram` | :class:`discopy.quantum.Diagram` | :class:`discopy.tensor.Diagram` The converted diagram. """ifisinstance(diagram,lq.Diagram):fromdiscopy.quantumimportBox,Ty,Idelifisinstance(diagram,lt.Diagram):fromdiscopy.tensorimportBox,DimasTy,Idelifisinstance(diagram,lg.Diagram):fromdiscopy.grammar.pregroupimportBox,Ty,Idelse:raiseNotImplementedError(diagram)box_factory=Boxty_factory=Tyid_factory=Iddcp_circ=id_factory(ty_l2d(diagram.dom,ty_factory))forlayerindiagram.layers:left,box,right=layer.unpack()converted_left=ty_l2d(left,ty_factory)converted_right=ty_l2d(right,ty_factory)converted_box=box_l2d(box,box_factory)dcp_layer=converted_left@converted_box@converted_rightdcp_circ>>=dcp_layerreturndcp_circ
[docs]deffrom_discopy(diagram:_DISCOPY_DIAGRAM_TY)->_LAMBEQ_DIAGRAM_TY:"""Takes a :class:`discopy.grammar.pregroup.Diagram`, :class:`discopy.quantum.Diagram`, or :class:`discopy.tensor.Diagram`, and converts it to a :class:`lambeq.backend.grammar.Diagram`, :class:`lambeq.backend.quantum.Diagram`, or :class:`lambeq.backend.tensor.Diagram`, respectively. Parameters ---------- diagram : :class:`discopy.grammar.pregroup.Diagram` | :class:`discopy.quantum.Diagram` | :class:`discopy.tensor.Diagram` The diagram to convert. Returns ------- :class:`lambeq.backend.grammar.Diagram` | :class:`lambeq.backend.quantum.Diagram` | :class:`lambeq.backend.tensor.Diagram` The converted diagram. """ifversion.parse(discopy.__version__)<version.parse(MIN_DISCOPY_VERSION):raiseDeprecationWarning('Conversion from discopy to lambeq'f'requires discopy>={MIN_DISCOPY_VERSION}.')ifisinstance(diagram,dq.Circuit):fromlambeq.backend.quantumimportBox,Ty,Idelifisinstance(diagram,dt.Diagram):fromlambeq.backend.tensorimport(Box,# type: ignore[assignment]DimasTy,Id)elifisinstance(diagram,dg.Diagram):fromlambeq.backend.grammarimport(Box,# type: ignore[assignment]Ty,Id)else:raiseNotImplementedError(diagram)box_factory=Boxty_factory=Tyid_factory=Idlam_circ=id_factory(ty_d2l(diagram.dom,ty_factory))forleft,box,rightindiagram:converted_left=ty_d2l(left,ty_factory)converted_right=ty_d2l(right,ty_factory)converted_box=box_d2l(box,box_factory)lam_layer=converted_left@converted_box@converted_rightlam_circ>>=lam_layerreturnlam_circ