Skip to content

Commit da99f02

Browse files
committed
migrate to ZXCircuit
1 parent 8a99695 commit da99f02

9 files changed

Lines changed: 384 additions & 23 deletions

File tree

src/ZX/ZX.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export FusionRule, XToZRule,
3131

3232
export rewrite!, simplify!
3333

34-
export convert_to_chain, convert_to_zxd, convert_to_zxwd
34+
export convert_to_chain, convert_to_circuit, convert_to_zxd, convert_to_zxwd
3535
export clifford_simplification, full_reduction, circuit_extraction, phase_teleportation
3636
export plot
3737
export concat!, dagger, contains_only_bare_wires, verify_equality

src/ZX/circuit_extraction.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Extract a quantum circuit from a general ZX-graph even without a gflow.
88
This function can handle ZX-graphs that don't have a generalised flow (gflow),
99
and will introduce post-selection operators as needed.
1010
11-
Returns a `ZXDiagram` representing the extracted circuit.
11+
Returns a `ZXCircuit` representing the extracted circuit.
1212
"""
1313
function ancilla_extraction(zxg::Union{ZXGraph, ZXCircuit})
1414
nzxg = copy(zxg)
@@ -35,7 +35,7 @@ function ancilla_extraction(zxg::Union{ZXGraph, ZXCircuit})
3535
end
3636

3737
frontiers = copy(outs)
38-
circ = ZXDiagram(nbits)
38+
circ = ZXCircuit(nbits)
3939
unextracts = Set(spiders(nzxg))
4040
qubit_map = Dict{Int, Int}()
4141
for i in eachindex(ins)

src/ZX/equality.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,28 @@ function verify_equality(zxd_1::ZXDiagram, zxd_2::ZXDiagram)
1818
return contains_only_bare_wires(m_simple)
1919
end
2020

21+
"""
22+
$(TYPEDSIGNATURES)
23+
24+
Check the equivalence of two different ZX-circuits.
25+
26+
This function concatenates the first circuit with the dagger of the second,
27+
then performs full reduction. If the result contains only bare wires, the
28+
circuits are equivalent.
29+
30+
Returns `true` if the circuits are equivalent, `false` otherwise.
31+
"""
32+
function verify_equality(circ_1::ZXCircuit, circ_2::ZXCircuit)
33+
merged_circuit = copy(circ_1)
34+
merged_circuit = concat!(merged_circuit, dagger(circ_2))
35+
m_simple = full_reduction(merged_circuit)
36+
return contains_only_bare_wires(m_simple)
37+
end
38+
39+
# Mixed type comparisons
40+
verify_equality(c1::ZXCircuit, c2::ZXDiagram) = verify_equality(c1, ZXCircuit(c2))
41+
verify_equality(c1::ZXDiagram, c2::ZXCircuit) = verify_equality(ZXCircuit(c1), c2)
42+
2143
function contains_only_bare_wires(zxd::Union{ZXDiagram, ZXGraph})
2244
return all(is_in_or_out_spider(st[2]) for st in zxd.st)
2345
end
Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,118 @@
11
# Circuit Interface Implementation for ZXCircuit
22

3-
nqubits(circ::ZXCircuit) = max(length(circ.inputs), length(circ.outputs))
3+
nqubits(circ::ZXCircuit) = circ.layout.nbits
44
get_inputs(circ::ZXCircuit) = circ.inputs
55
get_outputs(circ::ZXCircuit) = circ.outputs
66

7-
# Gate operations - these will be defined in gate_ops.jl for ZXDiagram
8-
# but ZXCircuit doesn't implement them directly yet
9-
# For now, we define stubs that throw NotImplementedError
7+
# Gate operations for ZXCircuit
8+
# These operate on the underlying ZXGraph
109

11-
function push_gate!(circ::ZXCircuit, args...)
12-
error("push_gate! not yet implemented for ZXCircuit. Use ZXDiagram for gate operations.")
10+
function push_gate!(circ::ZXCircuit{T, P}, ::Val{:Z}, loc::T, phase=zero(P); autoconvert::Bool=true) where {T, P}
11+
@inbounds out_id = get_outputs(circ)[loc]
12+
@inbounds bound_id = neighbors(circ, out_id)[1]
13+
rphase = autoconvert ? safe_convert(P, phase) : phase
14+
insert_spider!(circ, bound_id, out_id, SpiderType.Z, rphase)
15+
return circ
1316
end
1417

15-
function pushfirst_gate!(circ::ZXCircuit, args...)
16-
error("pushfirst_gate! not yet implemented for ZXCircuit. Use ZXDiagram for gate operations.")
18+
function push_gate!(circ::ZXCircuit{T, P}, ::Val{:X}, loc::T, phase=zero(P); autoconvert::Bool=true) where {T, P}
19+
@inbounds out_id = get_outputs(circ)[loc]
20+
@inbounds bound_id = neighbors(circ, out_id)[1]
21+
rphase = autoconvert ? safe_convert(P, phase) : phase
22+
insert_spider!(circ, bound_id, out_id, SpiderType.X, rphase)
23+
return circ
24+
end
25+
26+
function push_gate!(circ::ZXCircuit{T, P}, ::Val{:H}, loc::T) where {T, P}
27+
@inbounds out_id = get_outputs(circ)[loc]
28+
@inbounds bound_id = neighbors(circ, out_id)[1]
29+
insert_spider!(circ, bound_id, out_id, SpiderType.H)
30+
return circ
31+
end
32+
33+
function push_gate!(circ::ZXCircuit{T, P}, ::Val{:SWAP}, locs::Vector{T}) where {T, P}
34+
q1, q2 = locs
35+
push_gate!(circ, Val{:Z}(), q1)
36+
push_gate!(circ, Val{:Z}(), q2)
37+
push_gate!(circ, Val{:Z}(), q1)
38+
push_gate!(circ, Val{:Z}(), q2)
39+
v1, v2, bound_id1, bound_id2 = (sort!(spiders(circ)))[(end - 3):end]
40+
rem_edge!(circ, v1, bound_id1)
41+
rem_edge!(circ, v2, bound_id2)
42+
add_edge!(circ, v1, bound_id2)
43+
add_edge!(circ, v2, bound_id1)
44+
return circ
45+
end
46+
47+
function push_gate!(circ::ZXCircuit{T, P}, ::Val{:CNOT}, loc::T, ctrl::T) where {T, P}
48+
push_gate!(circ, Val{:Z}(), ctrl)
49+
push_gate!(circ, Val{:X}(), loc)
50+
@inbounds v1, v2 = (sort!(spiders(circ)))[(end - 1):end]
51+
add_edge!(circ, v1, v2)
52+
add_power!(circ, 1)
53+
return circ
54+
end
55+
56+
function push_gate!(circ::ZXCircuit{T, P}, ::Val{:CZ}, loc::T, ctrl::T) where {T, P}
57+
push_gate!(circ, Val{:Z}(), ctrl)
58+
push_gate!(circ, Val{:Z}(), loc)
59+
@inbounds v1, v2 = (sort!(spiders(circ)))[(end - 1):end]
60+
add_edge!(circ, v1, v2)
61+
insert_spider!(circ, v1, v2, SpiderType.H)
62+
add_power!(circ, 1)
63+
return circ
64+
end
65+
66+
function pushfirst_gate!(circ::ZXCircuit{T, P}, ::Val{:Z}, loc::T, phase::P=zero(P)) where {T, P}
67+
@inbounds in_id = get_inputs(circ)[loc]
68+
@inbounds bound_id = neighbors(circ, in_id)[1]
69+
insert_spider!(circ, in_id, bound_id, SpiderType.Z, phase)
70+
return circ
71+
end
72+
73+
function pushfirst_gate!(circ::ZXCircuit{T, P}, ::Val{:X}, loc::T, phase::P=zero(P)) where {T, P}
74+
@inbounds in_id = get_inputs(circ)[loc]
75+
@inbounds bound_id = neighbors(circ, in_id)[1]
76+
insert_spider!(circ, in_id, bound_id, SpiderType.X, phase)
77+
return circ
78+
end
79+
80+
function pushfirst_gate!(circ::ZXCircuit{T, P}, ::Val{:H}, loc::T) where {T, P}
81+
@inbounds in_id = get_inputs(circ)[loc]
82+
@inbounds bound_id = neighbors(circ, in_id)[1]
83+
insert_spider!(circ, in_id, bound_id, SpiderType.H)
84+
return circ
85+
end
86+
87+
function pushfirst_gate!(circ::ZXCircuit{T, P}, ::Val{:SWAP}, locs::Vector{T}) where {T, P}
88+
q1, q2 = locs
89+
pushfirst_gate!(circ, Val{:Z}(), q1)
90+
pushfirst_gate!(circ, Val{:Z}(), q2)
91+
pushfirst_gate!(circ, Val{:Z}(), q1)
92+
pushfirst_gate!(circ, Val{:Z}(), q2)
93+
v1, v2, bound_id1, bound_id2 = (sort!(spiders(circ)))[1:4]
94+
rem_edge!(circ, v1, bound_id1)
95+
rem_edge!(circ, v2, bound_id2)
96+
add_edge!(circ, v1, bound_id2)
97+
add_edge!(circ, v2, bound_id1)
98+
return circ
99+
end
100+
101+
function pushfirst_gate!(circ::ZXCircuit{T, P}, ::Val{:CNOT}, loc::T, ctrl::T) where {T, P}
102+
pushfirst_gate!(circ, Val{:X}(), loc)
103+
pushfirst_gate!(circ, Val{:Z}(), ctrl)
104+
@inbounds v1, v2 = (sort!(spiders(circ)))[1:2]
105+
add_edge!(circ, v1, v2)
106+
add_power!(circ, 1)
107+
return circ
108+
end
109+
110+
function pushfirst_gate!(circ::ZXCircuit{T, P}, ::Val{:CZ}, loc::T, ctrl::T) where {T, P}
111+
pushfirst_gate!(circ, Val{:Z}(), loc)
112+
pushfirst_gate!(circ, Val{:Z}(), ctrl)
113+
@inbounds v1, v2 = (sort!(spiders(circ)))[1:2]
114+
add_edge!(circ, v1, v2)
115+
insert_spider!(circ, v1, v2, SpiderType.H)
116+
add_power!(circ, 1)
117+
return circ
17118
end

src/ZX/implementations/zx_circuit/type.jl

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,58 @@ function ZXCircuit(zxg::ZXGraph{T, P}) where {T, P}
7171
return ZXCircuit(zxg, inputs, outputs, layout, phase_ids, nothing)
7272
end
7373

74+
"""
75+
$(TYPEDSIGNATURES)
76+
77+
Construct an empty ZX-circuit with `nbits` qubits.
78+
79+
Each qubit is represented by a pair of In/Out spiders connected by a simple edge.
80+
This creates a minimal circuit structure ready for gate insertion.
81+
82+
# Example
83+
```julia
84+
julia> circ = ZXCircuit(3)
85+
ZXCircuit with 3 inputs and 3 outputs...
86+
```
87+
"""
88+
function ZXCircuit(nbits::T) where {T <: Integer}
89+
mg = Multigraph(2*nbits)
90+
st = Dict{T, SpiderType.SType}()
91+
ps = Dict{T, Phase}()
92+
et = Dict{Tuple{T, T}, EdgeType.EType}()
93+
spider_q = Dict{T, Rational{Int}}()
94+
spider_col = Dict{T, Rational{Int}}()
95+
96+
inputs = Vector{T}()
97+
outputs = Vector{T}()
98+
99+
for i in 1:nbits
100+
in_id = T(2*i-1)
101+
out_id = T(2*i)
102+
add_edge!(mg, in_id, out_id)
103+
104+
st[in_id] = SpiderType.In
105+
st[out_id] = SpiderType.Out
106+
ps[in_id] = Phase(0//1)
107+
ps[out_id] = Phase(0//1)
108+
et[(in_id, out_id)] = EdgeType.SIM
109+
110+
spider_q[in_id] = i
111+
spider_col[in_id] = 0
112+
spider_q[out_id] = i
113+
spider_col[out_id] = 1
114+
115+
push!(inputs, in_id)
116+
push!(outputs, out_id)
117+
end
118+
119+
layout = ZXLayout(nbits, spider_q, spider_col)
120+
zxg = ZXGraph{T, Phase}(mg, ps, st, et, Scalar{Phase}())
121+
phase_ids = Dict{T, Tuple{T, Int}}()
122+
123+
return ZXCircuit(zxg, inputs, outputs, layout, phase_ids)
124+
end
125+
74126
function ZXDiagram(circ::ZXCircuit{T, P}) where {T, P}
75127
layout = circ.layout
76128
phase_ids = circ.phase_ids

src/ZX/implementations/zx_diagram/composition_ops.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Circuit Composition Operations for ZXDiagram (Legacy)
22

33
"""
4-
import_non_in_out!(d1::ZXDiagram{T,P}, d2::ZXDiagram{T,P}, v2tov1::Dict{T,T}) where {T,P}
4+
$(TYPEDSIGNATURES)
55
66
Add non input and output spiders of d2 to d1, modify d1. Record the mapping of vertex indices.
77
"""
@@ -30,7 +30,7 @@ function import_non_in_out!(
3030
end
3131

3232
"""
33-
import_edges!(d1::ZXDiagram{T,P}, d2::ZXDiagram{T,P}, v2tov1::Dict{T,T}) where {T,P}
33+
$(TYPEDSIGNATURES)
3434
3535
Import edges of d2 to d1, modify d1
3636
"""
@@ -42,7 +42,7 @@ function import_edges!(d1::ZXDiagram{T, P}, d2::ZXDiagram{T, P}, v2tov1::Dict{T,
4242
end
4343

4444
"""
45-
concat!(zxd_1::ZXDiagram{T,P}, zxd_2::ZXDiagram{T,P})::ZXDiagram{T,P} where {T,P}
45+
$(TYPEDSIGNATURES)
4646
4747
Appends two diagrams, where the second diagram is inverted
4848
"""
@@ -94,12 +94,12 @@ function stype_to_val(st)::Val
9494
end
9595

9696
"""
97-
dagger(zxd::ZXDiagram{T,P})::ZXDiagram{T,P} where {T,P}
97+
$(TYPEDSIGNATURES)
9898
9999
Dagger of a ZXDiagram by swapping input and outputs and negating the values of the phases
100100
"""
101101
function dagger(zxd::ZXDiagram{T, P})::ZXDiagram{T, P} where {T, P}
102-
ps_i = Dict([k => -v for (k, v) in zxd.ps])
102+
ps_i = Dict([k => -v for (k, v) in phases(zxd)])
103103
zxd_dg = ZXDiagram{T, P}(
104104
copy(zxd.mg),
105105
copy(zxd.st),

src/ZX/ir.jl

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,42 @@
1-
ZXDiagram(bir::BlockIR) = convert_to_zxd(bir)
1+
using DocStringExtensions
2+
3+
"""
4+
$(TYPEDSIGNATURES)
5+
6+
Convert a BlockIR to a ZXCircuit.
7+
8+
This function converts YaoHIR's BlockIR representation to a ZXCircuit by translating
9+
each gate operation to the corresponding ZX-diagram representation.
10+
11+
Returns a `ZXCircuit` containing the circuit representation.
12+
13+
See also: [`convert_to_zxd`](@ref)
14+
"""
15+
function convert_to_circuit(root::YaoHIR.BlockIR)
16+
circ = ZXCircuit(root.nqubits)
17+
circuit = canonicalize_single_location(root.circuit)
18+
return gates_to_circ(circ, circuit, root)
19+
end
20+
21+
"""
22+
$(TYPEDSIGNATURES)
23+
24+
Convert a BlockIR to a ZXDiagram.
25+
26+
!!! warning "Deprecated"
27+
`convert_to_zxd` is deprecated. Use [`convert_to_circuit`](@ref) instead.
28+
This function internally converts to ZXCircuit and then wraps it in ZXDiagram.
29+
30+
Returns a `ZXDiagram` for backward compatibility.
31+
"""
32+
function convert_to_zxd(root::YaoHIR.BlockIR)
33+
Base.depwarn("convert_to_zxd is deprecated, use convert_to_circuit instead", :convert_to_zxd)
34+
circ = convert_to_circuit(root)
35+
return ZXDiagram(circ)
36+
end
37+
38+
ZXDiagram(bir::BlockIR) = ZXDiagram(convert_to_circuit(bir))
39+
ZXCircuit(bir::BlockIR) = convert_to_circuit(bir)
240
YaoHIR.Chain(zxd::ZXDiagram) = convert_to_chain(zxd)
341

442
convert_to_gate(::Val{:X}, loc) = Gate(X, Locations(loc))
@@ -132,12 +170,6 @@ function gates_to_circ(circ, circuit, root)
132170
return circ
133171
end
134172

135-
function convert_to_zxd(root::YaoHIR.BlockIR)
136-
diagram = ZXDiagram(root.nqubits)
137-
circuit = canonicalize_single_location(root.circuit)
138-
return gates_to_circ(diagram, circuit, root)
139-
end
140-
141173
function push_spider_to_chain!(qc, q, ps, st)
142174
if ps != 0
143175
if st == SpiderType.Z

0 commit comments

Comments
 (0)