Skip to content

Commit 5622740

Browse files
author
Will Simmons
authored
Floating-point bug in ZX fixed by taking phases mod 2 (#2105)
* Floating-point bug in ZX fixed by taking phases mod 2 * Switch example circuit to smaller example * Bump tket version * Add changelog entry
1 parent 11be581 commit 5622740

15 files changed

Lines changed: 5262 additions & 64 deletions

‎TKET_VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.1.69
1+
2.1.70

‎pytket/docs/changelog.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
- Fix bug in `ZXGraphlikeOptimisation` where floating-point errors prevented identifying Clifford generators when angles had large absolute values.
6+
37
## 2.11.0 (November 2025)
48

59
- Fix segfault from `Circuit::get_resources()` with 0-qubit gates.

‎tket/include/tket/ZX/ZXGenerator.hpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ class ZXGen {
184184
static ZXGen_ptr create_gen(
185185
ZXType type, QuantumType qtype = QuantumType::Quantum);
186186
static ZXGen_ptr create_gen(
187-
ZXType type, const Expr& param, QuantumType qtype = QuantumType::Quantum);
187+
ZXType type, Expr param, QuantumType qtype = QuantumType::Quantum);
188188
static ZXGen_ptr create_gen(
189189
ZXType type, bool param, QuantumType qtype = QuantumType::Quantum);
190190

‎tket/src/ZX/MBQCRewrites.cpp‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ bool Rewrite::extend_for_PX_outputs_fun(ZXDiagram& diag) {
2727
!(diag.get_zxtype(n) == ZXType::PX &&
2828
!diag.get_vertex_ZXGen<CliffordGen>(n).get_param())) {
2929
// Extend output
30-
ZXGen_ptr px = std::make_shared<CliffordGen>(ZXType::PX, false);
30+
ZXGen_ptr px = ZXGen::create_gen(ZXType::PX, false);
3131
ZXVert n1 = diag.add_vertex(px);
3232
ZXVert n2 = diag.add_vertex(px);
3333
diag.remove_wire(diag.adj_wires(o).front());
@@ -87,12 +87,12 @@ bool Rewrite::internalise_gadgets_fun(ZXDiagram& diag) {
8787
Expr new_param =
8888
(*axis_clifford == 0) ? -current_param : current_param;
8989
diag.set_vertex_ZXGen_ptr(
90-
axis, std::make_shared<PhasedGen>(ZXType::YZ, new_param));
90+
axis, ZXGen::create_gen(ZXType::YZ, new_param));
9191
} else {
9292
Expr new_param =
9393
(*axis_clifford == 1) ? current_param : -current_param;
9494
diag.set_vertex_ZXGen_ptr(
95-
axis, std::make_shared<PhasedGen>(ZXType::XZ, new_param));
95+
axis, ZXGen::create_gen(ZXType::XZ, new_param));
9696
}
9797
to_remove.push_back(v);
9898
break;
@@ -101,7 +101,7 @@ bool Rewrite::internalise_gadgets_fun(ZXDiagram& diag) {
101101
Expr current_param = diag.get_vertex_ZXGen<PhasedGen>(v).get_param();
102102
Expr new_param = axis_XY_angle - current_param;
103103
diag.set_vertex_ZXGen_ptr(
104-
axis, std::make_shared<PhasedGen>(ZXType::XY, new_param));
104+
axis, ZXGen::create_gen(ZXType::XY, new_param));
105105
to_remove.push_back(v);
106106
break;
107107
}
@@ -113,12 +113,12 @@ bool Rewrite::internalise_gadgets_fun(ZXDiagram& diag) {
113113
Expr new_param = (*axis_clifford == 0) ? 0.5 - current_param
114114
: current_param - 0.5;
115115
diag.set_vertex_ZXGen_ptr(
116-
axis, std::make_shared<PhasedGen>(ZXType::XZ, new_param));
116+
axis, ZXGen::create_gen(ZXType::XZ, new_param));
117117
} else {
118118
Expr new_param = (*axis_clifford == 1) ? 0.5 - current_param
119119
: current_param - 0.5;
120120
diag.set_vertex_ZXGen_ptr(
121-
axis, std::make_shared<PhasedGen>(ZXType::YZ, new_param));
121+
axis, ZXGen::create_gen(ZXType::YZ, new_param));
122122
}
123123
to_remove.push_back(v);
124124
break;
@@ -127,7 +127,7 @@ bool Rewrite::internalise_gadgets_fun(ZXDiagram& diag) {
127127
bool current_param =
128128
diag.get_vertex_ZXGen<CliffordGen>(v).get_param();
129129
diag.set_vertex_ZXGen_ptr(
130-
axis, std::make_shared<CliffordGen>(ZXType::PZ, current_param));
130+
axis, ZXGen::create_gen(ZXType::PZ, current_param));
131131
to_remove.push_back(v);
132132
break;
133133
}
@@ -138,14 +138,14 @@ bool Rewrite::internalise_gadgets_fun(ZXDiagram& diag) {
138138
Expr new_param =
139139
current_param ? axis_XY_angle + 0.5 : axis_XY_angle - 0.5;
140140
diag.set_vertex_ZXGen_ptr(
141-
axis, std::make_shared<PhasedGen>(ZXType::XY, new_param));
141+
axis, ZXGen::create_gen(ZXType::XY, new_param));
142142
} else if (*axis_clifford % 2 == 0) {
143143
diag.set_vertex_ZXGen_ptr(
144-
axis, std::make_shared<CliffordGen>(
144+
axis, ZXGen::create_gen(
145145
ZXType::PY, current_param ^ (*axis_clifford == 0)));
146146
} else {
147147
diag.set_vertex_ZXGen_ptr(
148-
axis, std::make_shared<CliffordGen>(
148+
axis, ZXGen::create_gen(
149149
ZXType::PX, !current_param ^ (*axis_clifford == 1)));
150150
}
151151
to_remove.push_back(v);
@@ -157,14 +157,14 @@ bool Rewrite::internalise_gadgets_fun(ZXDiagram& diag) {
157157
if (!axis_clifford) {
158158
Expr new_param = current_param ? axis_XY_angle + 1. : axis_XY_angle;
159159
diag.set_vertex_ZXGen_ptr(
160-
axis, std::make_shared<PhasedGen>(ZXType::XY, new_param));
160+
axis, ZXGen::create_gen(ZXType::XY, new_param));
161161
} else if (*axis_clifford % 2 == 0) {
162162
diag.set_vertex_ZXGen_ptr(
163-
axis, std::make_shared<CliffordGen>(
163+
axis, ZXGen::create_gen(
164164
ZXType::PX, !current_param ^ (*axis_clifford == 0)));
165165
} else {
166166
diag.set_vertex_ZXGen_ptr(
167-
axis, std::make_shared<CliffordGen>(
167+
axis, ZXGen::create_gen(
168168
ZXType::PY, !current_param ^ (*axis_clifford == 1)));
169169
}
170170
to_remove.push_back(v);

‎tket/src/ZX/ZXDExpansions.cpp‎

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ ZXDiagram ZXDiagram::to_doubled_diagram() const {
5757
case ZXType::Input:
5858
case ZXType::Output:
5959
case ZXType::Open: {
60-
orig_op = std::make_shared<const BoundaryGen>(
61-
op->get_type(), QuantumType::Classical);
60+
orig_op = ZXGen::create_gen(op->get_type(), QuantumType::Classical);
6261
conj_op = orig_op;
6362
break;
6463
}
@@ -67,47 +66,47 @@ ZXDiagram ZXDiagram::to_doubled_diagram() const {
6766
case ZXType::XY:
6867
case ZXType::YZ: {
6968
const PhasedGen& bg = static_cast<const PhasedGen&>(*op);
70-
orig_op = std::make_shared<const PhasedGen>(
69+
orig_op = ZXGen::create_gen(
7170
op->get_type(), bg.get_param(), QuantumType::Classical);
72-
conj_op = std::make_shared<const PhasedGen>(
71+
conj_op = ZXGen::create_gen(
7372
op->get_type(), -bg.get_param(), QuantumType::Classical);
7473
break;
7574
}
7675
case ZXType::XZ: {
7776
const PhasedGen& bg = static_cast<const PhasedGen&>(*op);
78-
orig_op = std::make_shared<const PhasedGen>(
77+
orig_op = ZXGen::create_gen(
7978
op->get_type(), bg.get_param(), QuantumType::Classical);
8079
conj_op = orig_op;
8180
break;
8281
}
8382
case ZXType::PX:
8483
case ZXType::PZ: {
8584
const CliffordGen& bg = static_cast<const CliffordGen&>(*op);
86-
orig_op = std::make_shared<const CliffordGen>(
85+
orig_op = ZXGen::create_gen(
8786
op->get_type(), bg.get_param(), QuantumType::Classical);
8887
conj_op = orig_op;
8988
break;
9089
}
9190
case ZXType::PY: {
9291
const CliffordGen& bg = static_cast<const CliffordGen&>(*op);
93-
orig_op = std::make_shared<const CliffordGen>(
92+
orig_op = ZXGen::create_gen(
9493
op->get_type(), bg.get_param(), QuantumType::Classical);
95-
conj_op = std::make_shared<const CliffordGen>(
94+
conj_op = ZXGen::create_gen(
9695
op->get_type(), !bg.get_param(), QuantumType::Classical);
9796
break;
9897
}
9998
case ZXType::Hbox: {
10099
const PhasedGen& bg = static_cast<const PhasedGen&>(*op);
101-
orig_op = std::make_shared<const PhasedGen>(
100+
orig_op = ZXGen::create_gen(
102101
op->get_type(), bg.get_param(), QuantumType::Classical);
103-
conj_op = std::make_shared<const PhasedGen>(
102+
conj_op = ZXGen::create_gen(
104103
op->get_type(), SymEngine::conjugate(bg.get_param()),
105104
QuantumType::Classical);
106105
break;
107106
}
108107
case ZXType::Triangle: {
109-
orig_op = std::make_shared<const DirectedGen>(
110-
ZXType::Triangle, QuantumType::Classical);
108+
orig_op =
109+
ZXGen::create_gen(ZXType::Triangle, QuantumType::Classical);
111110
conj_op = orig_op;
112111
break;
113112
}
@@ -200,8 +199,8 @@ ZXDiagram ZXDiagram::to_quantum_embedding() const {
200199
if (embedding.get_qtype(b) == QuantumType::Classical) {
201200
ZXVert new_b =
202201
embedding.add_vertex(embedding.get_zxtype(b), QuantumType::Quantum);
203-
ZXGen_ptr id = std::make_shared<const PhasedGen>(
204-
ZXType::ZSpider, 0., QuantumType::Classical);
202+
ZXGen_ptr id =
203+
ZXGen::create_gen(ZXType::ZSpider, Expr(0.), QuantumType::Classical);
205204
embedding.set_vertex_ZXGen_ptr(b, id);
206205
embedding.add_wire(new_b, b, ZXWireType::Basic, QuantumType::Quantum);
207206
b = new_b;

‎tket/src/ZX/ZXGenerator.cpp‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,19 @@ ZXGen_ptr ZXGen::create_gen(ZXType type, QuantumType qtype) {
125125
return op;
126126
}
127127

128-
ZXGen_ptr ZXGen::create_gen(ZXType type, const Expr& param, QuantumType qtype) {
128+
ZXGen_ptr ZXGen::create_gen(ZXType type, Expr param, QuantumType qtype) {
129129
ZXGen_ptr op;
130130
switch (type) {
131131
case ZXType::ZSpider:
132132
case ZXType::XSpider:
133133
case ZXType::XY:
134134
case ZXType::XZ:
135-
case ZXType::YZ:
135+
case ZXType::YZ: {
136+
std::optional<double> mod = eval_expr_mod(param);
137+
if (mod) param = *mod;
138+
op = std::make_shared<const PhasedGen>(type, param, qtype);
139+
break;
140+
}
136141
case ZXType::Hbox: {
137142
op = std::make_shared<const PhasedGen>(type, param, qtype);
138143
break;
@@ -255,7 +260,7 @@ SymSet PhasedGen::free_symbols() const { return expr_free_symbols(param_); }
255260

256261
ZXGen_ptr PhasedGen::symbol_substitution(
257262
const SymEngine::map_basic_basic& sub_map) const {
258-
return std::make_shared<const PhasedGen>(type_, param_.subs(sub_map), qtype_);
263+
return ZXGen::create_gen(type_, param_.subs(sub_map), qtype_);
259264
}
260265

261266
std::string PhasedGen::get_name(bool) const {

‎tket/src/ZX/ZXRWAxioms.cpp‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ bool Rewrite::red_to_green_fun(ZXDiagram& diag) {
3434
}
3535
// Replace X spider with Z spider
3636
const PhasedGen& x = diag.get_vertex_ZXGen<PhasedGen>(v);
37-
ZXGen_ptr z = std::make_shared<const PhasedGen>(
38-
ZXType::ZSpider, x.get_param(), *x.get_qtype());
37+
ZXGen_ptr z =
38+
ZXGen::create_gen(ZXType::ZSpider, x.get_param(), *x.get_qtype());
3939
diag.set_vertex_ZXGen_ptr(v, z);
4040
}
4141
return success;
@@ -74,7 +74,7 @@ bool Rewrite::spider_fusion_fun(ZXDiagram& diag) {
7474
// search the neighbours
7575
const PhasedGen& vspid = diag.get_vertex_ZXGen<PhasedGen>(v);
7676
const PhasedGen& uspid = diag.get_vertex_ZXGen<PhasedGen>(u);
77-
ZXGen_ptr new_spid = std::make_shared<const PhasedGen>(
77+
ZXGen_ptr new_spid = ZXGen::create_gen(
7878
vtype, vspid.get_param() + uspid.get_param(),
7979
(vspid.get_qtype() == QuantumType::Classical ||
8080
uspid.get_qtype() == QuantumType::Classical)
@@ -147,8 +147,8 @@ bool Rewrite::self_loop_removal_fun(ZXDiagram& diag) {
147147
}
148148
if ((n_pis % 2) == 1) {
149149
const PhasedGen& spid = diag.get_vertex_ZXGen<PhasedGen>(v);
150-
ZXGen_ptr new_spid = std::make_shared<const PhasedGen>(
151-
vtype, spid.get_param() + 1., vqtype);
150+
ZXGen_ptr new_spid =
151+
ZXGen::create_gen(vtype, spid.get_param() + 1., vqtype);
152152
diag.set_vertex_ZXGen_ptr(v, new_spid);
153153
}
154154
}

‎tket/src/ZX/ZXRWDecompositions.cpp‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ Rewrite Rewrite::decompose_boxes() { return Rewrite(decompose_boxes_fun); }
4848

4949
bool Rewrite::basic_wires_fun(ZXDiagram& diag) {
5050
ZXGen_ptr qhad =
51-
std::make_shared<const PhasedGen>(ZXType::Hbox, -1, QuantumType::Quantum);
52-
ZXGen_ptr chad = std::make_shared<const PhasedGen>(
53-
ZXType::Hbox, -1, QuantumType::Classical);
51+
ZXGen::create_gen(ZXType::Hbox, Expr(-1), QuantumType::Quantum);
52+
ZXGen_ptr chad =
53+
ZXGen::create_gen(ZXType::Hbox, Expr(-1), QuantumType::Classical);
5454
WireVec targets;
5555
BGL_FORALL_EDGES(w, *diag.graph, ZXGraph) {
5656
if (diag.get_wire_type(w) == ZXWireType::H) targets.push_back(w);

‎tket/src/ZX/ZXRWGraphLikeForm.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ bool Rewrite::separate_boundaries_fun(ZXDiagram& diag) {
3939
}
4040
// New wires will inherit `w`'s `qtype`
4141
QuantumType wq = diag.get_qtype(w);
42-
ZXGen_ptr id = std::make_shared<const PhasedGen>(ZXType::ZSpider, 0., wq);
42+
ZXGen_ptr id = ZXGen::create_gen(ZXType::ZSpider, Expr(0.), wq);
4343
ZXVert z_at_b = diag.add_vertex(id);
4444
diag.add_wire(b, z_at_b, ZXWireType::Basic, wq);
4545
ZXVert z_at_o;

‎tket/src/ZX/ZXRWGraphLikeSimplification.cpp‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ bool Rewrite::remove_interior_cliffords_fun(ZXDiagram& diag) {
7777
*xi_op.get_qtype() == QuantumType::Classical)
7878
continue;
7979
// Update phase information
80-
ZXGen_ptr xi_new_op = std::make_shared<const PhasedGen>(
80+
ZXGen_ptr xi_new_op = ZXGen::create_gen(
8181
ZXType::ZSpider, xi_op.get_param() - spid.get_param(),
8282
*xi_op.get_qtype());
8383
diag.set_vertex_ZXGen_ptr(*xi, xi_new_op);
@@ -98,7 +98,7 @@ static void add_phase_to_vertices(
9898
ZXDiagram& diag, const ZXVertSeqSet& verts, const Expr& phase) {
9999
for (const ZXVert& v : verts.get<TagSeq>()) {
100100
const PhasedGen& old_spid = diag.get_vertex_ZXGen<PhasedGen>(v);
101-
ZXGen_ptr new_spid = std::make_shared<const PhasedGen>(
101+
ZXGen_ptr new_spid = ZXGen::create_gen(
102102
ZXType::ZSpider, old_spid.get_param() + phase, *old_spid.get_qtype());
103103
diag.set_vertex_ZXGen_ptr(v, new_spid);
104104
}
@@ -256,9 +256,9 @@ bool Rewrite::gadgetise_interior_paulis_fun(ZXDiagram& diag) {
256256
std::optional<unsigned> pi2_mult = equiv_Clifford(v_spid.get_param());
257257
Expr new_phase = ((*pi2_mult % 4 == 0) ? 1. : -1.) * u_spid.get_param();
258258
diag.set_vertex_ZXGen_ptr(
259-
u, std::make_shared<PhasedGen>(ZXType::ZSpider, new_phase, vqtype));
259+
u, ZXGen::create_gen(ZXType::ZSpider, new_phase, vqtype));
260260
diag.set_vertex_ZXGen_ptr(
261-
v, std::make_shared<PhasedGen>(ZXType::ZSpider, 0., vqtype));
261+
v, ZXGen::create_gen(ZXType::ZSpider, Expr(0.), vqtype));
262262

263263
// Because `can_complement_neighbourhood` checks all neighbours,
264264
// v and u have the same QuantumType
@@ -310,8 +310,7 @@ bool Rewrite::extend_at_boundary_paulis_fun(ZXDiagram& diag) {
310310
// extend it
311311
ZXGen_ptr u_op = diag.get_vertex_ZXGen_ptr(u);
312312
QuantumType qtype = *u_op->get_qtype();
313-
ZXGen_ptr id =
314-
std::make_shared<const PhasedGen>(ZXType::ZSpider, 0., qtype);
313+
ZXGen_ptr id = ZXGen::create_gen(ZXType::ZSpider, Expr(0.), qtype);
315314
ZXVert z1 = diag.add_vertex(id);
316315
ZXVert z2 = diag.add_vertex(u_op);
317316
diag.add_wire(u, z1, ZXWireType::H, qtype);
@@ -347,8 +346,8 @@ bool Rewrite::merge_gadgets_fun(ZXDiagram& diag) {
347346
diag.get_vertex_ZXGen<PhasedGen>(other_gadget).get_param();
348347
Expr this_param = diag.get_vertex_ZXGen<PhasedGen>(v).get_param();
349348
diag.set_vertex_ZXGen_ptr(
350-
other_gadget, std::make_shared<PhasedGen>(
351-
ZXType::ZSpider, other_param + this_param));
349+
other_gadget,
350+
ZXGen::create_gen(ZXType::ZSpider, other_param + this_param));
352351
to_remove.push_back(v);
353352
to_remove.push_back(axis);
354353
}

0 commit comments

Comments
 (0)