diff --git a/docs/examples/pyzx_cnots.ipynb b/docs/examples/pyzx_cnots.ipynb index 7434e3b..ff67998 100644 --- a/docs/examples/pyzx_cnots.ipynb +++ b/docs/examples/pyzx_cnots.ipynb @@ -64,7 +64,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -542,7 +542,7 @@ " \n", " \n", " \n", - " 2026-03-02T12:08:09.067235\n", + " 2026-03-10T13:36:38.202361\n", " image/svg+xml\n", " \n", " \n", @@ -1251,37 +1251,37 @@ "L 327.620242 225.941577 \n", "L 343.390957 230.417473 \n", "z\n", - "\" clip-path=\"url(#p32715cc1d8)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #ffffff; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #808080; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #d7a4a1; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", + "\" clip-path=\"url(#pfed5ae661d)\" style=\"fill: #b9cdff; stroke: #000000; stroke-width: 0.5\"/>\n", " \n", " \n", " \n", - " \n", + " \n", " \n", + "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\" id=\"image8c4a941d71\" transform=\"scale(1 -1) translate(0 -144)\" x=\"335.71102\" y=\"-370.08\" width=\"314.64\" height=\"144\"/>\n", " \n", " \n", " \n", @@ -2824,10 +2824,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2891,7 +2891,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -3349,7 +3349,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.3" + "version": "3.13.7" } }, "nbformat": 4, diff --git a/docs/examples/pyzx_random.py b/docs/examples/pyzx_random.py index 287f221..c51d008 100644 --- a/docs/examples/pyzx_random.py +++ b/docs/examples/pyzx_random.py @@ -67,7 +67,7 @@ def run_random(pyzx_graph: BaseGraph | GraphS, fig_data: matplotlib.figure.Figur kwargs = { "weights": VALUE_FUNCTION_HYPERPARAMS, "first_id_strategy": "first_spider", - "seed": None, + "seed": 42, "vis_options": ("final", None), "max_attempts": 1, "stop_on_first_success": True, @@ -77,7 +77,7 @@ def run_random(pyzx_graph: BaseGraph | GraphS, fig_data: matplotlib.figure.Figur # General description of circuit qubit_n = 5 - depth = 15 + depth = 100 circuit_name = f"random_{kwargs['seed'] if kwargs.get('seed') else 'noseed'}_{qubit_n}_{depth}" # Get a valid random PyZX circuit graph diff --git a/src/topologiq/core/beams.py b/src/topologiq/core/beams.py new file mode 100644 index 0000000..2075c9a --- /dev/null +++ b/src/topologiq/core/beams.py @@ -0,0 +1,146 @@ +"""Beams and related classes used across graph_manager and pathfinder. + +Usage: + Call any required class from a separate script. + +""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np + +from topologiq.core.pathfinder.utils import get_manhattan +from topologiq.kwargs import BEAMS_SHORT_LEN +from topologiq.utils.classes import StandardCoord + + +@dataclass +class BeamAxisComponent: + """Class representing the beam coordinates for any given axis. + + Attributes: + start: The starting point for the segment. + end: The end point for the segment (== start if segment is a point). + direction: Whether segment grows towards the positive or negative end of its axis. + + """ + + start: int | float = -np.inf + end: int | float = np.inf + direction: int = 0 if start == end else 1 if end > start else -1 + + def __hash__(self) -> int: + """Return start and end for hashing.""" + return hash((self.start, self.end, self.direction)) + + def __eq__(self, other: object) -> bool: + """Check equality against any other segments.""" + return ( + isinstance(other, BeamAxisComponent) + and self.start == other.start + and self.end == other.end + ) + + def __str__(self) -> str: + """Return a readable representation.""" + return f"[{self.start} => {self.end})" + + def contains(self, point: int) -> bool: + """Check if a given point is contained in the segment.""" + if self.direction == 0 and (self.start == point == self.end): + return True + if self.direction == 1 and (self.start < point <= self.end): + return True + if self.direction == -1 and (self.start > point >= self.end): + return True + + return False + + def to_array(self, len_of_materialised_beam: int) -> list[int] | None: + """Convert segment into an array of arbitrary length.""" + if self.direction != 0: + return [self.start + i * self.direction for i in range(len_of_materialised_beam)] + + def get_length(self) -> int: + """Get the length of the beam.""" + return abs(self.start - self.end) + + +@dataclass +class SingleBeam: + """Class representing a single beam. + + Attributes: + x: The beam for the x-axis (a point if x-axis has no beam). + y: The beam for the y-axis (a point if y-axis has no beam). + z: The beam for the y-axis (a point if z-axis has no beam). + + """ + + x: BeamAxisComponent + y: BeamAxisComponent + z: BeamAxisComponent + + def __post_init__(self) -> None: + """Ensure beam runs only along a single dimension.""" + if (abs(self.x.direction) + abs(self.y.direction) + abs(self.z.direction)) != 1: + raise ValueError("Malformed beam. Beam must run only along a single dimension.") + + def __hash__(self) -> int: + """Return start and end for hashing.""" + return hash(self.coords) + + def __eq__(self, other: object) -> bool: + """Check equality against any other segments.""" + return isinstance(other, SingleBeam) and self.coords == other.coords + + def __str__(self) -> str: + """Return a readable representation.""" + return f"({self.x!s}, {self.y!s}, {self.z!s})" + + def coords(self) -> StandardCoord: + """Return the beam coordinates across all axes.""" + return self.x.start, self.y.start, self.z.start + + def direction(self) -> StandardCoord: + """Return te beam direction as a coordinate tuple.""" + return self.x.direction, self.y.direction, self.z.direction + + def contains(self, coords_to_check: StandardCoord) -> bool: + """Check if beam contains a given coordinate.""" + x, y, z = coords_to_check + return self.x.contains(x) and self.y.contains(y) and self.z.contains(z) + + def intersects(self, other: SingleBeam, short_beams: bool = True) -> bool: + """Check if two beams intersect one another.""" + + # Get source coords for both beams + p1 = self.coords() + p2 = other.coords() + + # If checking on short mode, + # exit if beams' sources are further than LEN_SHORT_BEAMS + if short_beams and get_manhattan(p1, p2) > BEAMS_SHORT_LEN: + return False + + # Check if beams are parallel or orthogonal + # No clashes possible if beams are parallel + d1 = self.direction() + d2 = other.direction() + orientation = np.dot(d1, d2) + + if orientation != 0: + return False + + # Evaluate clash if beams are orthogonal + # Source of the other beam must be in the positive quadrant of the span of {d1, -d2} + # Sigma is the position of the source of the other beam relative to the source of this beam + sigma = np.subtract(p2, p1) + basis = np.subtract(d1, d2) + + return np.all((sigma == 0) | (np.sign(sigma) == np.sign(basis))) + + +CubeBeams = list[SingleBeam] diff --git a/src/topologiq/core/graph_manager/beams.py b/src/topologiq/core/graph_manager/beams.py index a180988..05920ae 100644 --- a/src/topologiq/core/graph_manager/beams.py +++ b/src/topologiq/core/graph_manager/beams.py @@ -8,9 +8,11 @@ import networkx as nx import numpy as np +from topologiq.core.beams import CubeBeams from topologiq.core.graph_manager.utils import get_node_degree -from topologiq.core.pathfinder.utils import get_manhattan -from topologiq.utils.classes import CubeBeams, StandardCoord + +# from topologiq.core.pathfinder.utils import get_manhattan +from topologiq.utils.classes import StandardCoord ################## @@ -164,8 +166,7 @@ def check_tgt_beam_clashes( for cube_beam in nx_g.nodes[cube_id]["beams_short"]: if nx_g.nodes[cube_id]["beams_short"]: intersections = [ - tgt_beam.intersects(cube_beam, kwargs["beams_len_short"]) - for tgt_beam in tgt_beams_short + tgt_beam.intersects(cube_beam) for tgt_beam in tgt_beams_short ] tgt_clash_tracker = tgt_clash_tracker + np.array(intersections) cube_clash_count += 1 if any(intersections) else 0 @@ -328,7 +329,6 @@ def check_need_twins_beams( if ( nx_g.nodes[out_id]["beams"] if strict else nx_g.nodes[out_id]["beams_short"] ) and nx_g.nodes[out_id]["coords"]: - out_coords = nx_g.nodes[out_id]["coords"] out_beams = nx_g.nodes[out_id]["beams"] if strict else nx_g.nodes[out_id]["beams_short"] out_beams_num = len(out_beams) out_degree = get_node_degree(nx_g, out_id) @@ -341,7 +341,6 @@ def check_need_twins_beams( if ( nx_g.nodes[out_id]["beams"] if strict else nx_g.nodes[in_id]["beams_short"] ) and nx_g.nodes[in_id]["coords"]: - in_coords = nx_g.nodes[in_id]["coords"] in_beams = ( nx_g.nodes[in_id]["beams"] if strict @@ -350,11 +349,10 @@ def check_need_twins_beams( in_beams_num = len(in_beams) in_degree = get_node_degree(nx_g, in_id) in_pending = in_degree - nx_g.nodes[in_id]["completed"] - manhattan_between = get_manhattan(out_coords, in_coords) for beam in in_beams: broken_beams = [ - beam.intersects(out_beam, manhattan_between) + beam.intersects(out_beam, short_beams=False) for out_beam in out_beams ] out_tracker = out_tracker + np.array(broken_beams) diff --git a/src/topologiq/core/graph_manager/callers.py b/src/topologiq/core/graph_manager/callers.py index 7b8a35d..52efbaa 100644 --- a/src/topologiq/core/graph_manager/callers.py +++ b/src/topologiq/core/graph_manager/callers.py @@ -10,9 +10,11 @@ import matplotlib import networkx as nx +from topologiq.core.beams import CubeBeams from topologiq.core.graph_manager.utils import reindex_path_dict from topologiq.core.pathfinder.pathfinder import pathfinder -from topologiq.utils.classes import CubeBeams, PathBetweenNodes, StandardBlock, StandardCoord +from topologiq.core.paths import PathBetweenNodes +from topologiq.utils.classes import StandardBlock, StandardCoord from topologiq.utils.read_write import prep_stats_n_log from topologiq.vis.animation import create_animation from topologiq.vis.blockgraph import vis_3d @@ -29,7 +31,7 @@ def call_pathfinder( taken: list[StandardCoord], tgt_block_info: StandardCoord | None = None, hdm: bool = False, - critical_beams: dict[StandardCoord, int, tuple[int, CubeBeams], tuple[int, CubeBeams]] = {}, + critical_beams: dict[int, tuple[StandardCoord, int, CubeBeams, CubeBeams]] = {}, src_tgt_ids: tuple[int, int] | None = None, **kwargs, ) -> tuple[ diff --git a/src/topologiq/core/graph_manager/edge_handlers.py b/src/topologiq/core/graph_manager/edge_handlers.py index fdec1b4..ba3f684 100644 --- a/src/topologiq/core/graph_manager/edge_handlers.py +++ b/src/topologiq/core/graph_manager/edge_handlers.py @@ -11,15 +11,15 @@ import matplotlib import networkx as nx +from topologiq.core.beams import CubeBeams from topologiq.core.graph_manager.beams import check_path_to_beam_clashes, check_tgt_beam_clashes from topologiq.core.graph_manager.callers import call_debug_vis, call_pathfinder from topologiq.core.graph_manager.utils import get_node_degree, prune_beams, update_edge_paths from topologiq.core.pathfinder.spatial import get_taken_coords from topologiq.core.pathfinder.symbolic import check_exits +from topologiq.core.paths import PathBetweenNodes from topologiq.utils.classes import ( Colors, - CubeBeams, - PathBetweenNodes, StandardBlock, StandardCoord, ) diff --git a/src/topologiq/core/graph_manager/utils.py b/src/topologiq/core/graph_manager/utils.py index 8bd15a3..a5ac20f 100644 --- a/src/topologiq/core/graph_manager/utils.py +++ b/src/topologiq/core/graph_manager/utils.py @@ -13,10 +13,10 @@ import networkx as nx from topologiq.core.pathfinder.spatial import get_taken_coords +from topologiq.core.paths import PathBetweenNodes from topologiq.input.simple_graphs import check_zx_types, get_zx_type_fam from topologiq.utils.classes import ( Colors, - PathBetweenNodes, SimpleDictGraph, StandardBlock, StandardCoord, diff --git a/src/topologiq/core/pathfinder/beams.py b/src/topologiq/core/pathfinder/beams.py index 61fb05e..4109920 100644 --- a/src/topologiq/core/pathfinder/beams.py +++ b/src/topologiq/core/pathfinder/beams.py @@ -12,8 +12,8 @@ import numpy as np -from topologiq.core.pathfinder.utils import get_manhattan -from topologiq.utils.classes import CubeBeams, StandardCoord +from topologiq.core.beams import CubeBeams +from topologiq.utils.classes import StandardCoord ################## # STANDARD EDGES # @@ -31,7 +31,7 @@ # CROSS EDGES # ############### def check_critical_beams( - critical_beams: dict[StandardCoord, int, tuple[int, CubeBeams], tuple[int, CubeBeams]], + critical_beams: dict[int, tuple[StandardCoord, int, CubeBeams, CubeBeams]], full_path_coords: list[StandardCoord], nxt_coords: StandardCoord, tgt_coords: StandardCoord, @@ -69,9 +69,7 @@ def check_critical_beams( for in_id, (_, in_min_exit_num, _, in_beams_short) in critical_beams.items(): in_clash_tracker = 0 for in_beam in in_beams_short: - intersections = [ - out_beam.intersects(in_beam, 9) for out_beam in out_beams_short - ] + intersections = [out_beam.intersects(in_beam) for out_beam in out_beams_short] # out_clash_tracker = out_clash_tracker + np.array(intersections) in_clash_tracker += any(intersections) @@ -93,11 +91,11 @@ def check_critical_beams( # CROSS EDGES NOT CURRENTLY IN USED BUT NOT DISCARDED YET # ########################################################### def split_critical_beams( - critical_beams: dict[StandardCoord, int, tuple[int, CubeBeams], tuple[int, CubeBeams]], - src_tgt_ids: tuple[int, int] | None, + critical_beams: dict[int, tuple[StandardCoord, int, CubeBeams, CubeBeams]], + src_tgt_ids: tuple[int, int], ) -> tuple[ - dict[StandardCoord, int, tuple[int, CubeBeams], tuple[int, CubeBeams]], - dict[StandardCoord, int, tuple[int, CubeBeams], tuple[int, CubeBeams]], + dict[int, tuple[StandardCoord, int, CubeBeams, CubeBeams]], + dict[int, tuple[StandardCoord, int, CubeBeams, CubeBeams]], ]: """Split critical beams into simple and verbose object containing different kinds of beams. @@ -143,10 +141,10 @@ def split_critical_beams( def check_unbreakable_beams( - unbreakable_beams: dict[StandardCoord, int, tuple[int, CubeBeams], tuple[int, CubeBeams]], + unbreakable_beams: dict[int, tuple[StandardCoord, int, CubeBeams, CubeBeams]], full_path_coords: list[StandardCoord], src_tgt_ids: tuple[int, int], -) -> bool: +) -> tuple[bool, list[StandardCoord]]: """Check that move does not break any beams of cubes that need all their exits. Args: @@ -177,63 +175,3 @@ def check_unbreakable_beams( broken_beams += 1 return True, clash_coords - - -def check_negotiable_beams( - negotiable_beams: dict[StandardCoord, int, tuple[int, CubeBeams], tuple[int, CubeBeams]], - full_path_coords: list[StandardCoord], - src_tgt_ids: tuple[int, int], -) -> bool: - """Check that move does not break any beams of cubes that need all their exits. - - Args: - negotiable_beams: A minified `critical_beams` object containing beams for nodes that can lose some beams. - full_path_coords: All coordinates occupied by current path. - src_tgt_ids: The exact IDs of the source and target cubes. - - Return: - (bool): True if move clears all checks, False otherwise. - - """ - - for node_id, ( - node_coords, - min_exit_num, - cube_beams, - cube_beams_short, - ) in negotiable_beams.items(): - # For each beam of current cube, check if path breaks the beam - out_broken_beams = 0 - for single_beam in cube_beams_short: - if any([single_beam.contains(coord) for coord in full_path_coords]): - out_broken_beams += 1 - - # If beam is broken, add pre-existing beam-to-beam clashes to consider previously-used allowances - for other_node_id, ( - other_node_coords, - other_min_exit_num, - other_cube_beams, - other_cube_beams_short, - ) in negotiable_beams.items(): - adjust = 1 if other_node_id in src_tgt_ids else 0 - manhattan_between = get_manhattan(node_coords, other_node_coords) - intersections = [ - single_beam.intersects(negotiable_beam, manhattan_between) - for negotiable_beam in other_cube_beams_short - ] - if intersections: - in_broken_beams = sum(intersections) - adjust - out_broken_beams += in_broken_beams - - # Flip check to false if number of broken beams exceeds tolerance - if len(other_cube_beams) - in_broken_beams < (other_min_exit_num - adjust): - return False - - # Adjust to consider the broken beam of outgoing/incoming edge in src and tgt cubes - adjust = 1 if node_id in src_tgt_ids else 0 - - # Flip check to false if number of broken beams exceeds tolerance - if len(cube_beams) - out_broken_beams < (min_exit_num - adjust): - return False - - return True diff --git a/src/topologiq/core/pathfinder/pathfinder.py b/src/topologiq/core/pathfinder/pathfinder.py index ad918e4..cb22851 100644 --- a/src/topologiq/core/pathfinder/pathfinder.py +++ b/src/topologiq/core/pathfinder/pathfinder.py @@ -22,6 +22,7 @@ from collections import deque +from topologiq.core.beams import CubeBeams from topologiq.core.pathfinder.spatial import ( check_skip_move, gen_bounding_box, @@ -40,7 +41,7 @@ get_max_manhattan, init_bfs, ) -from topologiq.utils.classes import CubeBeams, StandardBlock, StandardCoord +from topologiq.utils.classes import StandardBlock, StandardCoord from topologiq.utils.core import datetime_manager from topologiq.utils.read_write import prep_stats_n_log @@ -55,7 +56,7 @@ def pathfinder( tgt_block_info: tuple[StandardCoord | None, str | None] = (None, None), taken: list[StandardCoord] = [], hdm: bool = False, - critical_beams: dict[StandardCoord, int, tuple[int, CubeBeams], tuple[int, CubeBeams]] = {}, + critical_beams: dict[int, tuple[StandardCoord, int, CubeBeams, CubeBeams]] = {}, src_tgt_ids: tuple[int, int] | None = None, **kwargs, ) -> tuple[ @@ -172,7 +173,7 @@ def core_pathfinder_bfs( tent_tgt_kinds: list[str], taken: list[StandardCoord] = [], hdm: bool = False, - critical_beams: dict[StandardCoord, int, tuple[int, CubeBeams], tuple[int, CubeBeams]] = {}, + critical_beams: dict[int, tuple[StandardCoord, int, CubeBeams, CubeBeams]] = {}, src_tgt_ids: tuple[int, int] | None = None, **kwargs, ) -> tuple[ diff --git a/src/topologiq/core/pathfinder/spatial.py b/src/topologiq/core/pathfinder/spatial.py index de0f38b..23abfac 100644 --- a/src/topologiq/core/pathfinder/spatial.py +++ b/src/topologiq/core/pathfinder/spatial.py @@ -5,8 +5,11 @@ """ +import sys + +from topologiq.core.beams import CubeBeams from topologiq.core.pathfinder.beams import check_critical_beams -from topologiq.utils.classes import CubeBeams, StandardBlock, StandardCoord +from topologiq.utils.classes import StandardBlock, StandardCoord ####################### @@ -178,7 +181,7 @@ def check_skip_move( nxt_coords: StandardCoord, tgt_coords: list[StandardCoord], taken: list[StandardCoord], - critical_beams: dict[StandardCoord, int, tuple[int, CubeBeams], tuple[int, CubeBeams]], + critical_beams: dict[int, tuple[StandardCoord, int, CubeBeams, CubeBeams]], src_tgt_ids: tuple[int, int], second_pass: bool, bounding_box: dict[str, dict[str, int]], @@ -216,7 +219,7 @@ def check_skip_move( or nxt_y < bounding_box["y"]["min"] or nxt_y > bounding_box["y"]["max"] or nxt_z < bounding_box["z"]["min"] - or nxt_x > bounding_box["z"]["max"] + or nxt_z > bounding_box["z"]["max"] ): return True @@ -225,8 +228,11 @@ def check_skip_move( return True if critical_beams and "o" not in curr_kind: + if len(tgt_coords) > 1: + sys.stderr.write("Warning: check_skip_move tgt_coords > 1") + if not check_critical_beams( - critical_beams, full_path_coords, nxt_coords, tgt_coords, src_tgt_ids + critical_beams, full_path_coords, nxt_coords, tgt_coords[0], src_tgt_ids ): return True diff --git a/src/topologiq/core/pathfinder/symbolic.py b/src/topologiq/core/pathfinder/symbolic.py index b17ac49..075ff24 100644 --- a/src/topologiq/core/pathfinder/symbolic.py +++ b/src/topologiq/core/pathfinder/symbolic.py @@ -7,13 +7,8 @@ import numpy as np -from topologiq.utils.classes import ( - BeamAxisComponent, - CubeBeams, - SingleBeam, - StandardBlock, - StandardCoord, -) +from topologiq.core.beams import BeamAxisComponent, CubeBeams, SingleBeam +from topologiq.utils.classes import StandardBlock, StandardCoord #################### diff --git a/src/topologiq/core/paths.py b/src/topologiq/core/paths.py new file mode 100644 index 0000000..8e88d3b --- /dev/null +++ b/src/topologiq/core/paths.py @@ -0,0 +1,46 @@ +"""Paths and related classes used across graph manager and pathfinder. + +Usage: + Call any required class from a separate script. + +""" + +from dataclasses import dataclass + +from topologiq.core.beams import CubeBeams +from topologiq.utils.classes import StandardBlock, StandardCoord + + +# Edge path class with in-built value-function to enable path comparisons +@dataclass(order=True) +class PathBetweenNodes: + """A 3D path between the cubes corresponding to two nodes/spiders in the input ZX graph.""" + + tgt_coords: StandardCoord + tgt_kind: str + tgt_beams: CubeBeams + tgt_beams_short: CubeBeams + coords_in_path: list[StandardCoord] + all_nodes_in_path: list[StandardBlock] + beams_broken_by_path: int + len_of_path: int + tgt_unobstr_exit_n: int + + def weighed_value(self, **kwargs) -> int: + """Return the weighed value of a given path. + + This function returns the weighed value of a given PathBetweenNodes, + which can be used for comparing many paths. + + Args: + **kwargs: Only relevant kwargs listed below. + weights: A tuple (int, int) of weights used to pick the best of several paths when there are several valid alternatives. + + Returns: + (int): The weighed value of a path + + """ + + path_len_hp, beams_broken_hp = kwargs["weights"] + + return self.len_of_path * path_len_hp + self.beams_broken_by_path * beams_broken_hp diff --git a/src/topologiq/utils/classes.py b/src/topologiq/utils/classes.py index dce5769..f010f16 100644 --- a/src/topologiq/utils/classes.py +++ b/src/topologiq/utils/classes.py @@ -5,11 +5,8 @@ """ -from dataclasses import dataclass from typing import TypedDict -import numpy as np - # Types & class for input ZX graph GraphNode = tuple[int, str] GraphEdge = tuple[tuple[int, int], str] @@ -28,217 +25,6 @@ class SimpleDictGraph(TypedDict): StandardBeam = list[StandardCoord] -@dataclass -class BeamAxisComponent: - """Class representing the beam coordinates for any given axis. - - Attributes: - start: The starting point for the segment. - end: The end point for the segment (== start if segment is a point). - direction: Whether segment grows towards the positive or negative end of its axis. - - """ - - start: int | float = -np.inf - end: int | float = np.inf - direction: int = 0 if start == end else 1 if end > start else -1 - - def __hash__(self) -> int: - """Return start and end for hashing.""" - return hash((self.start, self.end, self.direction)) - - def __eq__(self, other: object) -> bool: - """Check equality against any other segments.""" - return ( - isinstance(other, BeamAxisComponent) - and self.start == other.start - and self.end == other.end - ) - - def __str__(self) -> str: - """Return a readable representation.""" - return f"[{self.start} => {self.end})" - - def is_parallel(self, other: object) -> bool: - """Check if two beams run parallel to one another (ignores collinearity).""" - return ( - self.x.is_point() == other.x.is_point() - and self.y.is_point() == other.y.is_point() - and self.z.is_point() == other.z.is_point() - ) - - def contains(self, point: int) -> bool: - """Check if a given point is contained in the segment.""" - if self.direction == 0 and (self.start == point == self.end): - return True - if self.direction == 1 and (self.start < point <= self.end): - return True - if self.direction == -1 and (self.start > point >= self.end): - return True - - return False - - def to_array(self, len_of_materialised_beam: int) -> list[int] | None: - """Convert segment into an array of arbitrary length.""" - if self.direction != 0: - return [self.start + i * self.direction for i in range(len_of_materialised_beam)] - - def get_length(self) -> int: - """Get the length of the beam.""" - return abs(self.start - self.end) - - -@dataclass -class SingleBeam: - """Class representing a single beam. - - Attributes: - x: The beam for the x-axis (a point if x-axis has no beam). - y: The beam for the y-axis (a point if y-axis has no beam). - z: The beam for the y-axis (a point if z-axis has no beam). - - """ - - x: BeamAxisComponent - y: BeamAxisComponent - z: BeamAxisComponent - - def __post_init__(self) -> None: - """Ensure beam runs only along a single dimension.""" - if (abs(self.x.direction) + abs(self.y.direction) + abs(self.z.direction)) != 1: - raise ValueError("Malformed beam. Beam must run only along a single dimension.") - - def __hash__(self) -> int: - """Return start and end for hashing.""" - return hash(self.coords) - - def __eq__(self, other: object) -> bool: - """Check equality against any other segments.""" - return isinstance(other, SingleBeam) and self.coords == other.coords - - def __str__(self) -> str: - """Return a readable representation.""" - return f"({self.x!s}, {self.y!s}, {self.z!s})" - - def coords(self) -> tuple[BeamAxisComponent, BeamAxisComponent, BeamAxisComponent]: - """Return the beam coordinates across all axes.""" - return (self.x, self.y, self.z) - - def contains(self, coords_to_check: StandardCoord) -> bool: - """Check if beam contains a given coordinate.""" - x, y, z = coords_to_check - return self.x.contains(x) and self.y.contains(y) and self.z.contains(z) - - def to_array(self, len_of_materialised_beam: int) -> list[StandardCoord]: - """Convert beam into an array of 3D coordinates of arbitrary length.""" - - if self.x.direction != 0: - y_start = self.y.start - z_start = self.z.start - return [(i, y_start, z_start) for i in self.x.to_array(len_of_materialised_beam)] - - if self.y.direction != 0: - x_start = self.x.start - z_start = self.z.start - return [(x_start, i, z_start) for i in self.y.to_array(len_of_materialised_beam)] - - if self.z.direction != 0: - x_start = self.x.start - y_start = self.y.start - return [(x_start, y_start, i) for i in self.z.to_array(len_of_materialised_beam)] - - def check_co_planarity(self, other: object) -> tuple[bool, int | None]: - """Check if two beams are co-planar.""" - co_planarity_checks = [ - self.x.direction == other.x.direction == 0, - self.y.direction == other.y.direction == 0, - self.z.direction == other.z.direction == 0, - ] - - if sum(co_planarity_checks) == 1: - return True, co_planarity_checks.index(True) - - return False, None - - def intersects(self, other: object, len_of_materialised_beam: int) -> bool: - """Check if two beams intersect one another.""" - - other_as_array = other.to_array(len_of_materialised_beam) - return any([self.contains(c) for c in other_as_array]) - - def intersects_co_planarity(self, other: object) -> bool: - """Check if two beams intersect one another.""" - - beams_are_co_planar, co_planarity_idx = self.check_co_planarity(other) - if beams_are_co_planar: - if co_planarity_idx == 0: - ok = ( - (self.y.contains(other.y.start) and other.z.contains(self.z.start)) - or (self.z.contains(other.z.start) and other.y.contains(self.y.start)) - or (self.y.contains(other.z.start) and other.z.contains(self.y.start)) - or (self.z.contains(other.y.start) and other.y.contains(self.z.start)) - ) - return ok - - elif co_planarity_idx == 1: - ok = ( - (self.x.contains(other.x.start) and other.z.contains(self.z.start)) - or (self.z.contains(other.z.start) and other.x.contains(self.x.start)) - or (self.x.contains(other.z.start) and other.z.contains(self.x.start)) - or (self.z.contains(other.x.start) and other.x.contains(self.z.start)) - ) - return ok - - elif co_planarity_idx == 2: - ok = ( - (self.x.contains(other.x.start) and other.y.contains(self.y.start)) - or (self.y.contains(other.y.start) and other.x.contains(self.x.start)) - or (self.x.contains(other.y.start) and other.y.contains(self.x.start)) - or (self.y.contains(other.x.start) and other.x.contains(self.y.start)) - ) - return ok - - return False - - -CubeBeams = list[SingleBeam] - - -# Edge path class with in-built value-function to enable path comparisons -@dataclass(order=True) -class PathBetweenNodes: - """A 3D path between the cubes corresponding to two nodes/spiders in the input ZX graph.""" - - tgt_coords: StandardCoord - tgt_kind: str - tgt_beams: CubeBeams - tgt_beams_short: CubeBeams - coords_in_path: list[StandardCoord] - all_nodes_in_path: list[StandardBlock] - beams_broken_by_path: int - len_of_path: int - tgt_unobstr_exit_n: int - - def weighed_value(self, **kwargs) -> int: - """Return the weighed value of a given path. - - This function returns the weighed value of a given PathBetweenNodes, - which can be used for comparing many paths. - - Args: - **kwargs: Only relevant kwargs listed below. - weights: A tuple (int, int) of weights used to pick the best of several paths when there are several valid alternatives. - - Returns: - (int): The weighed value of a path - - """ - - path_len_hp, beams_broken_hp = kwargs["weights"] - - return self.len_of_path * path_len_hp + self.beams_broken_by_path * beams_broken_hp - - # Misc classes class Colors: """Colours to use in printouts.""" diff --git a/src/topologiq/vis/blockgraph.py b/src/topologiq/vis/blockgraph.py index 5206592..8f4e683 100644 --- a/src/topologiq/vis/blockgraph.py +++ b/src/topologiq/vis/blockgraph.py @@ -19,8 +19,9 @@ from mpl_toolkits.mplot3d.art3d import Line3DCollection from PIL import Image +from topologiq.core.paths import PathBetweenNodes from topologiq.input.simple_graphs import kind_to_zx_type -from topologiq.utils.classes import PathBetweenNodes, StandardBlock, StandardCoord +from topologiq.utils.classes import StandardBlock, StandardCoord from topologiq.vis.common import ( edge_paths_to_nx_graph, figure_to_png,