├── tests ├── .gitignore ├── bell │ ├── ref │ │ └── 1.png │ └── test.typ ├── labels │ ├── ref │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ └── test.typ ├── wire │ ├── mode │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ ├── wires │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ └── 3.png │ │ └── test.typ │ ├── control-wires │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ ├── plain-vertical-wire │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ └── targ-control-phase │ │ ├── ref │ │ └── 1.png │ │ └── test.typ ├── gates │ ├── meter │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ └── 5.png │ │ └── test.typ │ ├── nwire │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── mqgate │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ ├── permute │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ └── 4.png │ │ └── test.typ │ ├── custom-colors │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── custom-gate │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── custom-gate-size │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ └── inputs-and-outputs │ │ ├── ref │ │ └── 1.png │ │ └── test.typ ├── examples │ ├── qft │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── teleportation │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── phase-estimation │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── shor-nine-qubit-code │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── fault-tolerant-toffoli1 │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── fault-tolerant-toffoli2 │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ └── fault-tolerant-measurement │ │ ├── ref │ │ └── 1.png │ │ └── test.typ ├── layout │ ├── gutter │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── placed-items │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ └── 4.png │ │ └── test.typ │ ├── aligned-environment │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ └── relative-for-row-and-col-spacing │ │ ├── ref │ │ ├── 1.png │ │ └── 2.png │ │ └── test.typ ├── tequila │ ├── basic │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 10.png │ │ │ ├── 11.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ ├── 6.png │ │ │ ├── 7.png │ │ │ ├── 8.png │ │ │ └── 9.png │ │ └── test.typ │ ├── styling │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ ├── graph-state │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ ├── placement │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ └── 3.png │ │ └── test.typ │ └── ranges-with-two-qubit-gates │ │ ├── ref │ │ └── 1.png │ │ └── test.typ ├── decorations │ ├── slice │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── gategroup │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ ├── midstick │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ └── 3.png │ │ └── test.typ │ └── lstick-and-rstick │ │ ├── ref │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ └── 7.png │ │ └── test.typ └── README.md ├── .github ├── FUNDING.yml └── workflows │ └── run_tests.yml ├── .gitignore ├── docs ├── images │ ├── qft.typ │ ├── composition.typ │ ├── teleportation.typ │ ├── phase-estimation.typ │ ├── template.typ │ ├── bell.typ │ ├── generate-images.sh │ └── logo.typ ├── guide │ ├── references.bib │ ├── template.typ │ ├── gallery.typ │ └── quill-guide.typ └── architecture.md ├── examples ├── bell.typ ├── teleportation.typ ├── fault-tolerant-pi8.typ ├── composition.typ ├── fault-tolerant-toffoli1.typ ├── shor-nine-qubit-code.typ ├── phase-estimation.typ ├── fault-tolerant-toffoli2.typ ├── qft.typ └── fault-tolerant-measurement.typ ├── src ├── tequila.typ ├── length-helpers.typ ├── quill.typ ├── arrow.typ ├── utility.typ ├── process-args.typ ├── verifications.typ ├── layout.typ ├── decorations.typ ├── draw-functions.typ ├── tequila-impl.typ ├── gates.typ └── quantum-circuit.typ ├── typst.toml ├── LICENSE └── README.md /tests/.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | diff/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Mc-Zen] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.pdf 3 | /docs/images/output/** -------------------------------------------------------------------------------- /tests/bell/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/bell/ref/1.png -------------------------------------------------------------------------------- /tests/labels/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/labels/ref/1.png -------------------------------------------------------------------------------- /tests/labels/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/labels/ref/2.png -------------------------------------------------------------------------------- /tests/labels/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/labels/ref/3.png -------------------------------------------------------------------------------- /tests/labels/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/labels/ref/4.png -------------------------------------------------------------------------------- /tests/labels/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/labels/ref/5.png -------------------------------------------------------------------------------- /tests/labels/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/labels/ref/6.png -------------------------------------------------------------------------------- /tests/labels/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/labels/ref/7.png -------------------------------------------------------------------------------- /tests/labels/ref/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/labels/ref/8.png -------------------------------------------------------------------------------- /tests/labels/ref/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/labels/ref/9.png -------------------------------------------------------------------------------- /tests/wire/mode/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/wire/mode/ref/1.png -------------------------------------------------------------------------------- /tests/wire/mode/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/wire/mode/ref/2.png -------------------------------------------------------------------------------- /tests/bell/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #include "/examples/bell.typ" -------------------------------------------------------------------------------- /tests/gates/meter/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/meter/ref/1.png -------------------------------------------------------------------------------- /tests/gates/meter/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/meter/ref/2.png -------------------------------------------------------------------------------- /tests/gates/meter/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/meter/ref/3.png -------------------------------------------------------------------------------- /tests/gates/meter/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/meter/ref/4.png -------------------------------------------------------------------------------- /tests/gates/meter/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/meter/ref/5.png -------------------------------------------------------------------------------- /tests/gates/nwire/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/nwire/ref/1.png -------------------------------------------------------------------------------- /tests/wire/wires/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/wire/wires/ref/1.png -------------------------------------------------------------------------------- /tests/wire/wires/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/wire/wires/ref/2.png -------------------------------------------------------------------------------- /tests/wire/wires/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/wire/wires/ref/3.png -------------------------------------------------------------------------------- /docs/images/qft.typ: -------------------------------------------------------------------------------- 1 | #import "template.typ": * 2 | #show: doc-image 3 | 4 | #include "../../examples/qft.typ" -------------------------------------------------------------------------------- /tests/examples/qft/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/examples/qft/ref/1.png -------------------------------------------------------------------------------- /tests/gates/mqgate/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/mqgate/ref/1.png -------------------------------------------------------------------------------- /tests/gates/mqgate/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/mqgate/ref/2.png -------------------------------------------------------------------------------- /tests/gates/permute/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/permute/ref/1.png -------------------------------------------------------------------------------- /tests/gates/permute/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/permute/ref/2.png -------------------------------------------------------------------------------- /tests/gates/permute/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/permute/ref/3.png -------------------------------------------------------------------------------- /tests/gates/permute/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/permute/ref/4.png -------------------------------------------------------------------------------- /tests/layout/gutter/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/layout/gutter/ref/1.png -------------------------------------------------------------------------------- /tests/tequila/basic/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/basic/ref/1.png -------------------------------------------------------------------------------- /tests/tequila/basic/ref/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/basic/ref/10.png -------------------------------------------------------------------------------- /tests/tequila/basic/ref/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/basic/ref/11.png -------------------------------------------------------------------------------- /tests/tequila/basic/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/basic/ref/2.png -------------------------------------------------------------------------------- /tests/tequila/basic/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/basic/ref/3.png -------------------------------------------------------------------------------- /tests/tequila/basic/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/basic/ref/4.png -------------------------------------------------------------------------------- /tests/tequila/basic/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/basic/ref/5.png -------------------------------------------------------------------------------- /tests/tequila/basic/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/basic/ref/6.png -------------------------------------------------------------------------------- /tests/tequila/basic/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/basic/ref/7.png -------------------------------------------------------------------------------- /tests/tequila/basic/ref/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/basic/ref/8.png -------------------------------------------------------------------------------- /tests/tequila/basic/ref/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/basic/ref/9.png -------------------------------------------------------------------------------- /tests/tequila/styling/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/styling/ref/1.png -------------------------------------------------------------------------------- /tests/tequila/styling/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/styling/ref/2.png -------------------------------------------------------------------------------- /tests/decorations/slice/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/slice/ref/1.png -------------------------------------------------------------------------------- /tests/examples/qft/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | 3 | #include "/examples/qft.typ" -------------------------------------------------------------------------------- /tests/gates/custom-colors/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/custom-colors/ref/1.png -------------------------------------------------------------------------------- /tests/gates/custom-gate/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/custom-gate/ref/1.png -------------------------------------------------------------------------------- /tests/layout/placed-items/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/layout/placed-items/ref/1.png -------------------------------------------------------------------------------- /tests/layout/placed-items/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/layout/placed-items/ref/2.png -------------------------------------------------------------------------------- /tests/layout/placed-items/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/layout/placed-items/ref/3.png -------------------------------------------------------------------------------- /tests/layout/placed-items/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/layout/placed-items/ref/4.png -------------------------------------------------------------------------------- /tests/tequila/graph-state/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/graph-state/ref/1.png -------------------------------------------------------------------------------- /tests/tequila/graph-state/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/graph-state/ref/2.png -------------------------------------------------------------------------------- /tests/tequila/placement/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/placement/ref/1.png -------------------------------------------------------------------------------- /tests/tequila/placement/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/placement/ref/2.png -------------------------------------------------------------------------------- /tests/tequila/placement/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/placement/ref/3.png -------------------------------------------------------------------------------- /tests/wire/control-wires/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/wire/control-wires/ref/1.png -------------------------------------------------------------------------------- /tests/wire/control-wires/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/wire/control-wires/ref/2.png -------------------------------------------------------------------------------- /tests/decorations/gategroup/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/gategroup/ref/1.png -------------------------------------------------------------------------------- /tests/decorations/gategroup/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/gategroup/ref/2.png -------------------------------------------------------------------------------- /tests/decorations/midstick/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/midstick/ref/1.png -------------------------------------------------------------------------------- /tests/decorations/midstick/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/midstick/ref/2.png -------------------------------------------------------------------------------- /tests/decorations/midstick/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/midstick/ref/3.png -------------------------------------------------------------------------------- /docs/images/composition.typ: -------------------------------------------------------------------------------- 1 | #import "template.typ": * 2 | #show: doc-image 3 | 4 | #include "../../examples/composition.typ" -------------------------------------------------------------------------------- /docs/images/teleportation.typ: -------------------------------------------------------------------------------- 1 | #import "template.typ": * 2 | #show: doc-image 3 | 4 | #include "../../examples/teleportation.typ" -------------------------------------------------------------------------------- /tests/examples/teleportation/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/examples/teleportation/ref/1.png -------------------------------------------------------------------------------- /tests/gates/custom-gate-size/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/custom-gate-size/ref/1.png -------------------------------------------------------------------------------- /tests/gates/custom-gate-size/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/custom-gate-size/ref/2.png -------------------------------------------------------------------------------- /tests/gates/inputs-and-outputs/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/gates/inputs-and-outputs/ref/1.png -------------------------------------------------------------------------------- /tests/wire/plain-vertical-wire/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/wire/plain-vertical-wire/ref/1.png -------------------------------------------------------------------------------- /tests/wire/targ-control-phase/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/wire/targ-control-phase/ref/1.png -------------------------------------------------------------------------------- /tests/examples/phase-estimation/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/examples/phase-estimation/ref/1.png -------------------------------------------------------------------------------- /tests/layout/aligned-environment/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/layout/aligned-environment/ref/1.png -------------------------------------------------------------------------------- /docs/images/phase-estimation.typ: -------------------------------------------------------------------------------- 1 | #import "template.typ": * 2 | #show: doc-image 3 | 4 | #include "../../examples/phase-estimation.typ" -------------------------------------------------------------------------------- /tests/decorations/lstick-and-rstick/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/lstick-and-rstick/ref/1.png -------------------------------------------------------------------------------- /tests/decorations/lstick-and-rstick/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/lstick-and-rstick/ref/2.png -------------------------------------------------------------------------------- /tests/decorations/lstick-and-rstick/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/lstick-and-rstick/ref/3.png -------------------------------------------------------------------------------- /tests/decorations/lstick-and-rstick/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/lstick-and-rstick/ref/4.png -------------------------------------------------------------------------------- /tests/decorations/lstick-and-rstick/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/lstick-and-rstick/ref/5.png -------------------------------------------------------------------------------- /tests/decorations/lstick-and-rstick/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/lstick-and-rstick/ref/6.png -------------------------------------------------------------------------------- /tests/decorations/lstick-and-rstick/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/decorations/lstick-and-rstick/ref/7.png -------------------------------------------------------------------------------- /tests/examples/shor-nine-qubit-code/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/examples/shor-nine-qubit-code/ref/1.png -------------------------------------------------------------------------------- /tests/examples/teleportation/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | 3 | #include "/examples/teleportation.typ" -------------------------------------------------------------------------------- /tests/examples/phase-estimation/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | 3 | #include "/examples/phase-estimation.typ" -------------------------------------------------------------------------------- /tests/examples/fault-tolerant-toffoli1/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/examples/fault-tolerant-toffoli1/ref/1.png -------------------------------------------------------------------------------- /tests/examples/fault-tolerant-toffoli2/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/examples/fault-tolerant-toffoli2/ref/1.png -------------------------------------------------------------------------------- /tests/examples/shor-nine-qubit-code/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | 3 | #include "/examples/shor-nine-qubit-code.typ" -------------------------------------------------------------------------------- /tests/examples/fault-tolerant-measurement/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/examples/fault-tolerant-measurement/ref/1.png -------------------------------------------------------------------------------- /tests/tequila/ranges-with-two-qubit-gates/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/tequila/ranges-with-two-qubit-gates/ref/1.png -------------------------------------------------------------------------------- /tests/examples/fault-tolerant-toffoli1/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | 3 | #include "/examples/fault-tolerant-toffoli1.typ" -------------------------------------------------------------------------------- /tests/examples/fault-tolerant-toffoli2/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | 3 | #include "/examples/fault-tolerant-toffoli2.typ" -------------------------------------------------------------------------------- /tests/layout/relative-for-row-and-col-spacing/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/layout/relative-for-row-and-col-spacing/ref/1.png -------------------------------------------------------------------------------- /tests/layout/relative-for-row-and-col-spacing/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mc-Zen/quill/HEAD/tests/layout/relative-for-row-and-col-spacing/ref/2.png -------------------------------------------------------------------------------- /docs/images/template.typ: -------------------------------------------------------------------------------- 1 | #let doc-image(body) = { 2 | set page(width: auto, height: auto, margin: 5pt) 3 | show: scale.with(130%, reflow: true) 4 | body 5 | } -------------------------------------------------------------------------------- /tests/examples/fault-tolerant-measurement/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | 3 | #include "/examples/fault-tolerant-measurement.typ" -------------------------------------------------------------------------------- /tests/layout/aligned-environment/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #align(center + bottom, quantum-circuit(1, $X$, 1)) -------------------------------------------------------------------------------- /tests/wire/plain-vertical-wire/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit(1, ctrl(1, show-dot: false), 1, [\ ], 3) -------------------------------------------------------------------------------- /examples/bell.typ: -------------------------------------------------------------------------------- 1 | 2 | #{ 3 | import "../src/quill.typ" : * 4 | quantum-circuit( 5 | lstick($|0〉$), gate($H$), ctrl(1), rstick($(|00〉+|11〉)/√2$, n: 2), [\ ], 6 | lstick($|0〉$), 1, targ(), 1 7 | ) 8 | } -------------------------------------------------------------------------------- /src/tequila.typ: -------------------------------------------------------------------------------- 1 | #import "tequila-impl.typ": build, h, gate, mqgate, x, y, z, cx, cz, ca, s, sdg, sx, sxdg, t, tdg, p, rx, ry, rz, u, barrier, swap, meter, graph-state, qft, ccx, ccz, cca, cccx, multi-controlled-gate, measure -------------------------------------------------------------------------------- /tests/gates/nwire/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | 5 | #quantum-circuit( 6 | 1, nwire(1), nwire($2$), nwire($n$), nwire("p"), nwire($2n+1$), 2, 7 | ) -------------------------------------------------------------------------------- /tests/decorations/slice/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit( 5 | ctrl(), slice(), 1, [\ ], 6 | slice(), slice(x: 4, y: 0, n: 1, stroke: blue) 7 | ) -------------------------------------------------------------------------------- /src/length-helpers.typ: -------------------------------------------------------------------------------- 1 | 2 | 3 | #let get-length(len, container-length) = { 4 | if type(len) == length { return len } 5 | if type(len) == ratio { return len * container-length} 6 | if type(len) == relative { return len.length + len.ratio * container-length} 7 | } 8 | -------------------------------------------------------------------------------- /docs/guide/references.bib: -------------------------------------------------------------------------------- 1 | 2 | @book{nielsen_2022_quantum, 3 | author = {Nielsen, Michael A and Chuang, Isaac L}, 4 | edition = {2}, 5 | publisher = {Cambridge Cambridge University Press}, 6 | title = {Quantum computation and quantum information}, 7 | year = {2022} 8 | } -------------------------------------------------------------------------------- /examples/teleportation.typ: -------------------------------------------------------------------------------- 1 | #import "../src/quill.typ": * 2 | 3 | #quantum-circuit( 4 | lstick($|psi〉$), ctrl(1), gate($H$), 1, ctrl(2), meter(), [\ ], 5 | lstick($|beta_00〉$, n: 2), targ(), 1, ctrl(1), 1, meter(), [\ ], 6 | 3, gate($X$), gate($Z$), midstick($|psi〉$), setwire(0) 7 | ) -------------------------------------------------------------------------------- /tests/tequila/ranges-with-two-qubit-gates/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ" 3 | #import quill: tequila as tq 4 | 5 | #quill.quantum-circuit( 6 | ..tq.build( 7 | tq.cx((0, 1), (1, 2)), 8 | tq.cx(0, (1,2)), 9 | tq.cx((1, 2), 0), 10 | ) 11 | ) 12 | -------------------------------------------------------------------------------- /tests/gates/mqgate/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit( 5 | 1, mqgate(n: 3, fill: none)[E], 1, [\ ], [\ ] 6 | ) 7 | 8 | #pagebreak() 9 | 10 | #quantum-circuit( 11 | [\ ], 12 | 1, mqgate(n: 4, fill: none, pass-through: (2,))[E], 1, [\ ], [\ ], [\ ] 13 | ) -------------------------------------------------------------------------------- /examples/fault-tolerant-pi8.typ: -------------------------------------------------------------------------------- 1 | #import "../src/quill.typ": * 2 | 3 | #let group = gategroup.with(stroke: (dash: "dotted", thickness: .5pt)) 4 | 5 | #quantum-circuit( 6 | group(1, 4, padding: (left: 1.5em)), lstick($|0〉$), nwire(""), $H$, $T$, 7 | ctrl(1), $S X$, rstick($T|ψ〉$), [\ ], 8 | lstick($|ψ〉$), nwire(""), 2, targ(), meter(target: -1), 9 | ) -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Tests for Quill 2 | 3 | Here, tests for the library are collected. There are 4 | - some unit tests testing the Typst code for things like correct gate and instruction creation as well as 5 | - output image comparisons for regression tests. 6 | 7 | The tests are instrumented using [tytanic](https://github.com/tingerrr/tytanic). 8 | 9 | -------------------------------------------------------------------------------- /tests/tequila/graph-state/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ" as quill: tequila as tq 3 | 4 | 5 | #quill.quantum-circuit( 6 | ..tq.graph-state((1,2), (0,2), (0,3), (0,1), (2,3)) 7 | ) 8 | 9 | #pagebreak() 10 | 11 | #quill.quantum-circuit( 12 | ..tq.graph-state((1,2), (0,2), (0,3), (0,1), (2,3), n: 6), 13 | ) -------------------------------------------------------------------------------- /tests/tequila/styling/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ" 3 | #import quill: tequila as tq 4 | 5 | #quill.quantum-circuit( 6 | ..tq.build( 7 | tq.h(0), 8 | tq.cx(0, 1, open: true, wire-label: sym.dots.v), 9 | tq.ccx(0, 2, 1, open: true), 10 | tq.multi-controlled-gate((0, 1), 2, quill.targ, open: true), 11 | ) 12 | ) 13 | 14 | #pagebreak() -------------------------------------------------------------------------------- /tests/layout/gutter/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #box(fill: red, quantum-circuit( 5 | column-spacing: 0pt, 6 | row-spacing: 0pt, 7 | 10pt /*should be ignored*/, 8 | gate($H$), 5pt, 10pt, 5pt, gate($H$), 10pt, gate($H$), [\ ], 10pt, 9 | gate($H$), gate($H$), gate($H$), [\ ], 10pt, 20pt, 10 | gate($H$), gate($H$),20pt, gate($H$), 11 | )) -------------------------------------------------------------------------------- /tests/wire/targ-control-phase/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit( 5 | scale: 150%, 6 | 1, targ(), ctrl(open: true), phase($α$, open: true), 1, [\ ], 7 | setwire(2), 8 | 1, targ(fill: auto), ctrl(open: true), phase($α$, open: true), 1, [\ ], 9 | setwire(3), 10 | 1, targ(fill: auto), ctrl(open: true), phase($α$, open: true), 1, 11 | ) -------------------------------------------------------------------------------- /examples/composition.typ: -------------------------------------------------------------------------------- 1 | #import "../src/quill.typ": * 2 | 3 | #import tequila as tq 4 | 5 | #quantum-circuit( 6 | ..tq.graph-state((0, 1), (1,2)), 7 | ..tq.build(y: 3, 8 | tq.p($pi$, 0), 9 | tq.cx(0, (1, 2)), 10 | ), 11 | ..tq.graph-state(x: 6, y: 2, invert: true, (0, 1), (0, 2)), 12 | gategroup(x: 1, 3, 3), 13 | gategroup(x: 1, y: 3, 3, 3), 14 | gategroup(x: 6, y: 2, 3, 3), 15 | slice(x: 5) 16 | ) -------------------------------------------------------------------------------- /tests/gates/inputs-and-outputs/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit( 5 | 1, mqgate($U$, n: 3, width: 5em, 6 | inputs: ( 7 | (qubit: 0, n: 2, label: $x$), 8 | (qubit: 2, label: $y$) 9 | ), 10 | outputs: ( 11 | (qubit: 0, n: 2, label: $x$), 12 | (qubit: 2, label: $y plus.o f(x)$) 13 | ), 14 | ), 1, [\ ], 3, [\ ], 3 15 | ) -------------------------------------------------------------------------------- /tests/layout/relative-for-row-and-col-spacing/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit( 5 | column-spacing: 1em, 6 | row-spacing: 1em, 7 | gate($H$), gate($H$), [\ ], 8 | gate($H$), 1 9 | ) 10 | 11 | #pagebreak() 12 | 13 | #quantum-circuit( 14 | min-row-height: 1em, 15 | min-column-width: 1em, 16 | gate($H$), gate($H$), [\ ], 17 | gate($H$), 1 18 | ) -------------------------------------------------------------------------------- /docs/images/bell.typ: -------------------------------------------------------------------------------- 1 | #import "template.typ": * 2 | #show: doc-image 3 | 4 | 5 | // #let clr = if "dark" not in sys.inputs { white } else { black } 6 | // #set page(fill: white) if clr == black 7 | // #set page(fill: none) 8 | // #set text(fill: clr) 9 | 10 | 11 | #import "/src/quill.typ" : * 12 | #quantum-circuit( 13 | wire: .7pt, fill: none, 14 | lstick($|0〉$), gate($H$), ctrl(1), rstick($(|00〉+|11〉)/√2$, n: 2), [\ ], 15 | lstick($|0〉$), 1, targ(), 1 16 | ) -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "quill" 3 | version = "0.7.2" 4 | entrypoint = "src/quill.typ" 5 | authors = ["Mc-Zen "] 6 | license = "MIT" 7 | description = "Effortlessly create quantum circuit diagrams." 8 | compiler = "0.13.0" 9 | 10 | repository = "https://github.com/Mc-Zen/quill" 11 | keywords = ["quantum circuit diagram", "quantikz", "qcircuit"] 12 | categories = ["visualization"] 13 | disciplines = ["physics", "computer-science"] 14 | -------------------------------------------------------------------------------- /tests/wire/wires/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | 5 | 6 | #quantum-circuit( 7 | wires: (1, 2, 3), 8 | 4, [\ ], 9 | 4, [\ ], 10 | 4, [\ ], 11 | ) 12 | 13 | #pagebreak() 14 | 15 | #quantum-circuit( 16 | wires: (2, 1, 1, 3), 17 | 2, setwire(1), 1, [\ ], 18 | setwire(3) 19 | ) 20 | 21 | #pagebreak() 22 | 23 | #quantum-circuit( 24 | wires: 4, 25 | 2, [\ ], 26 | 2, [\ ], 27 | ) 28 | 29 | -------------------------------------------------------------------------------- /tests/gates/custom-colors/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | 5 | #quantum-circuit( 6 | gate($X$, fill: gray), phase($text(a, fill: #red)$, fill: red), phase($text(a, fill: #red)$, fill: .7pt + red, open: true), gate($F_m$, radius: 100%), gate($F#h(.2em)$, radius: (right: 100%), fill: green), targ(fill: auto), targ(fill: blue), ctrl(fill: blue), ctrl(fill: .7pt + blue, open: true), ctrl(fill: blue), ctrl(fill: .7pt + blue, open: true), 1 7 | ) -------------------------------------------------------------------------------- /tests/decorations/midstick/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit( 5 | 1, $H$, midstick($ρ$), $X$ 6 | ) 7 | 8 | #pagebreak() 9 | 10 | #quantum-circuit( 11 | 1, $H$, midstick($ρ$, fill: blue, label: ("label")), $X$ 12 | ) 13 | 14 | #pagebreak() 15 | #quantum-circuit( 16 | baseline: .6fr, 17 | 1, ctrl(1), targ(), ctrl(1), midstick("=", n: 2), swap(1), 1, [\ ], 18 | 1, targ(), ctrl(-1), targ(), 1, swap(), 1, 19 | ) -------------------------------------------------------------------------------- /tests/gates/custom-gate-size/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | 5 | #quantum-circuit( 6 | targ(), targ(size: 6pt), targ(size: 3pt), ctrl(), ctrl(size: 4pt), ctrl(open: true), ctrl(size: 4pt, open: true), ctrl(), ctrl(size: 4pt) 7 | ) 8 | 9 | #pagebreak() 10 | 11 | #quantum-circuit(scale: 70%, 12 | targ(), targ(size: 6pt), targ(size: 3pt), ctrl(), ctrl(size: 4pt), ctrl(open: true), ctrl(size: 4pt, open: true), ctrl(), ctrl(size: 4pt) 13 | ) -------------------------------------------------------------------------------- /examples/fault-tolerant-toffoli1.typ: -------------------------------------------------------------------------------- 1 | #import "../src/quill.typ": * 2 | 3 | #quantum-circuit( 4 | fill-wires: false, 5 | lstick($|0〉$), $H$, ctrl(3), 5, $X$, ctrl(2), rstick($|x〉$), [\ ], 6 | lstick($|0〉$), $H$, 1, ctrl(3), 3, $X$, 1, ctrl(), rstick($|y〉$), [\ ], 7 | lstick($|0〉$), 3, targ(), 1, $Z$, 2, targ(), rstick($|z plus.o x y〉$), [\ ], 8 | lstick($|x〉$), 1, targ(), 5, meter(target: -3), [\ ], 9 | lstick($|y〉$), 2, targ(), 3, meter(target: -3), [\ ], 10 | lstick($|z〉$), 3, ctrl(-3), $H$, meter(target: -3) 11 | ) -------------------------------------------------------------------------------- /tests/gates/custom-gate/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | 5 | #let draw-par-gate(gate, draw-params) = { 6 | let stroke = draw-params.wire 7 | let fill = if gate.fill != auto { gate.fill } else {draw-params.background } 8 | box( 9 | gate.content, 10 | fill: fill, stroke: (left: stroke, right: stroke), 11 | inset: draw-params.padding 12 | ) 13 | } 14 | 15 | #box(quantum-circuit( 16 | 1, gate("Quill", draw-function: draw-par-gate), 1, 17 | ), fill: gray) -------------------------------------------------------------------------------- /docs/images/generate-images.sh: -------------------------------------------------------------------------------- 1 | typst c docs/images/logo.typ docs/images/output/logo.svg --root . 2 | typst c docs/guide/gallery.typ docs/images/output/gallery.svg --root . 3 | typst c docs/images/bell.typ docs/images/output/bell.svg --root . 4 | typst c docs/images/composition.typ docs/images/output/composition.svg --root . 5 | typst c docs/images/phase-estimation.typ docs/images/output/phase-estimation.svg --root . 6 | typst c docs/images/qft.typ docs/images/output/qft.svg --root . 7 | typst c docs/images/teleportation.typ docs/images/output/teleportation.svg --root . -------------------------------------------------------------------------------- /examples/shor-nine-qubit-code.typ: -------------------------------------------------------------------------------- 1 | #import "../src/quill.typ": * 2 | 3 | #let ancillas = (setwire(0), 5, lstick($|0〉$), setwire(1), targ(), 2, [\ ], 4 | setwire(0), 5, lstick($|0〉$), setwire(1), 1, targ(), 1) 5 | 6 | #quantum-circuit( 7 | scale: 80%, 8 | lstick($|ψ〉$), 1, 10pt, ctrl(3), ctrl(6), $H$, 1, 15pt, 9 | ctrl(1), ctrl(2), 1, [\ ], 10 | ..ancillas, [\ ], 11 | lstick($|0〉$), 1, targ(), 1, $H$, 1, ctrl(1), ctrl(2), 12 | 1, [\ ], 13 | ..ancillas, [\ ], 14 | lstick($|0〉$), 2, targ(), $H$, 1, ctrl(1), ctrl(2), 15 | 1, [\ ], 16 | ..ancillas 17 | ) -------------------------------------------------------------------------------- /tests/decorations/gategroup/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit( 5 | gategroup(1, 2, label: ((pos: top, content: "A"), (pos: left, content: "B")),), $K$, 1, 5pt, swap(), [\ ], 6 | 2, swap(-1), 7 | gategroup(2, 1, x: 2, y: 0, label: "swap", stroke: .5pt + blue, radius: 4pt, fill: blue) 8 | ) 9 | 10 | #pagebreak() 11 | 12 | 13 | #quantum-circuit( 14 | gategroup(1, auto, right: 0), $X$, $Y$, $p$, [\ ], 15 | gategroup(auto, auto, bottom: 0, right: 1), $X$, $Y$, $p$, [\ ], 16 | [\ ], 17 | gategroup(x: 2, y: 1, auto, 1, bottom: 1) 18 | ) -------------------------------------------------------------------------------- /src/quill.typ: -------------------------------------------------------------------------------- 1 | #import "utility.typ" 2 | #import "decorations.typ": lstick, rstick, midstick, nwire, annotate, slice, setwire, gategroup 3 | #import "gates.typ": gate, mqgate, ctrl, swap, targ, meter, phantom, permute, phase, draw-functions 4 | #import draw-functions: meter-symbol 5 | #import "quantum-circuit.typ": quantum-circuit 6 | #import "tequila.typ" 7 | 8 | 9 | #let help(..args) = { 10 | import "@preview/tidy:0.4.1" 11 | 12 | let namespace = ( 13 | ".": ( 14 | read.with("/src/quantum-circuit.typ"), 15 | read.with("/src/gates.typ"), 16 | read.with("/src/decorations.typ"), 17 | ), 18 | "gates": read.with("/src/gates.typ"), 19 | ) 20 | tidy.generate-help(namespace: namespace, package-name: "quill")(..args) 21 | } -------------------------------------------------------------------------------- /tests/wire/mode/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit( 5 | gate($H$), slice(), setwire(2), gate($X$), setwire(0), meter(), [\ ], 6 | setwire(0), gate($X$), setwire(1), 1, setwire(2), gate($X$),1, [\ ], 7 | 1, setwire(1, stroke: (paint: blue, dash: "dash-dotted", thickness: .7pt, )), 1, setwire(2),1, [\ ], 8 | 1, [\ ], 9 | 2, setwire(2), 1, setwire(3), 1, setwire(4), 1, setwire(5), 1 10 | ) 11 | 12 | #pagebreak() 13 | 14 | 15 | #quantum-circuit( 16 | 2, setwire(1, stroke: blue), 1, setwire(1, stroke: 1pt), 1, setwire(1, stroke: red), setwire(1, stroke: stroke(dash: "dotted")), 1, setwire(2), 1, setwire(2, wire-distance: 2pt), 1, setwire(2, stroke: stroke(dash: "solid")), 1 17 | ) -------------------------------------------------------------------------------- /tests/tequila/placement/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ" 3 | #import quill: tequila as tq 4 | 5 | #quill.quantum-circuit( 6 | 1, $X$, $Y$, quill.gate("R", x: 1, y: 1), 7 | ..tq.build( 8 | x: 2, y: 1, 9 | tq.h(0), 10 | tq.cx(0, 1), 11 | ) 12 | ) 13 | 14 | #pagebreak() 15 | 16 | 17 | #quill.quantum-circuit( 18 | quill.lstick($|0〉$, n: 3), 19 | ..tq.graph-state( 20 | y: 1, 21 | (0, 1) 22 | ), 23 | ..tq.build( 24 | x: 2, y: 3, n: 3, 25 | tq.h(0), 26 | tq.cx(0, 1), 27 | ), 28 | ..tq.build( 29 | y: 4, 30 | tq.y(0), tq.swap(0, 1), 31 | ) 32 | ) 33 | 34 | #pagebreak() 35 | 36 | 37 | #quill.quantum-circuit( 38 | ..tq.build( 39 | x: 0, y: 0, append-wire: false, 40 | tq.h(0), 41 | tq.cx(0, 1), 42 | ) 43 | ) 44 | -------------------------------------------------------------------------------- /examples/phase-estimation.typ: -------------------------------------------------------------------------------- 1 | #import "../src/quill.typ": * 2 | 3 | #quantum-circuit( 4 | setwire(0), lstick(align(center)[First register\ $t$ qubits], n: 4, pad: 10.5pt), 5 | lstick($|0〉$), setwire(1), $H$, 4, midstick($ dots $), ctrl(4), rstick($|0〉$), [\ ], 10pt, 6 | setwire(0), phantom(width: 13pt), lstick($|0〉$), setwire(1), $H$, 2, ctrl(3), 1, 7 | midstick($ dots $), 1, rstick($|0〉$), [\ ], 8 | setwire(0), 1, lstick($|0〉$), setwire(1), $H$, 1, ctrl(2), 2, 9 | midstick($ dots $), 1, rstick($|0〉$), [\ ], 10 | setwire(0), 1, lstick($|0〉$), setwire(1), $H$, ctrl(1), 3, midstick($ dots $), 1, 11 | rstick($|0〉$), [\ ], 12 | setwire(0), lstick([Second register], n: 1, brace: "{", pad: 10.5pt), lstick($|u〉$), 13 | setwire(4, wire-distance: 1.3pt), 1, $ U^2^0 $, $ U^2^1 $, $ U^2^2 $, 14 | 1, midstick($ dots $), $ U^2^(t-1) $, rstick($|u〉$) 15 | ) -------------------------------------------------------------------------------- /tests/layout/placed-items/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit( 5 | 1, $H$, [\ ], 6 | 1, $H$, [\ ], 7 | 1, $H$, [\ ], 8 | ..range(3).map(i => mqgate($+$, x: i + 2, y: i, n: 2)), 9 | ..range(3).map(i => meter(x: 6, y: i)), 10 | ..range(3).map(i => ctrl(x: 7, y: i)), 11 | ..range(3).map(i => lstick($|0〉$, x: 0, y: i)), 12 | gategroup(4, 3, x: 2, y: 0), 13 | slice(y: 1, x: 6, n: 2), 14 | ) 15 | 16 | #pagebreak() 17 | 18 | #quantum-circuit( 19 | gate($H$, y: 1), gate($H$, y: 0), 1, $B$, gate($X$, x: 4), [\ ], gate($X$, x:3) 20 | ) 21 | 22 | #pagebreak() 23 | 24 | #quantum-circuit( 25 | gate($H$, x: 2), gate($H$, x: 1), $B$, 2, $N$ 26 | ) 27 | 28 | #pagebreak() 29 | 30 | #quantum-circuit( 31 | $X$, 20pt, $Y$, slice(y: 0, n: 1), 20pt, $Y$, 20pt, $ß$, 20pt, gategroup(x: 0, y: 0, 1, 4), mqgate("a", n: 1) 32 | ) -------------------------------------------------------------------------------- /examples/fault-tolerant-toffoli2.typ: -------------------------------------------------------------------------------- 1 | #import "../src/quill.typ": * 2 | 3 | #let group = gategroup.with(stroke: (dash: "dotted", thickness: .5pt)) 4 | 5 | #quantum-circuit( 6 | fill-wires: false, 7 | group(3, 3, padding: (left: 1.5em)), lstick($|0〉$), $H$, ctrl(2), ctrl(3), 3, 8 | group(2, 1),ctrl(1), 1, group(3, 1), ctrl(2), $X$, 1, rstick($|x〉$), [\ ], 9 | lstick($|0〉$), $H$, ctrl(), 1, ctrl(3), 2, $Z$, $X$, 2, group(2, 1), 10 | ctrl(1), rstick($|y〉$), [\ ], 11 | lstick($|0〉$), 1, targ(), 2, targ(), 1, mqgate($Z$, target: -1, wire-count: 2), 1, 12 | targ(fill: auto), 1, targ(fill: auto), rstick($|z plus.o x y〉$), [\ ], 13 | lstick($|x〉$), 2, targ(), 6, meter(target: -3), setwire(2), ctrl(-1, wire-count: 2), [\ ], 14 | lstick($|y〉$), 3, targ(), 3, meter(target: -3), setwire(2), ctrl(-2, wire-count: 2), [\ ], 15 | lstick($|z〉$), 4, ctrl(-3), $H$, meter(target: -3) 16 | ) -------------------------------------------------------------------------------- /.github/workflows/run_tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: 3 | push: 4 | branches: [ main ] 5 | paths: 6 | - src/** 7 | - tests/** 8 | - examples/** 9 | - .github/** 10 | pull_request: 11 | branches: [ main ] 12 | 13 | 14 | jobs: 15 | tests: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Install tytanic (binary) 22 | uses: taiki-e/install-action@v2 23 | with: 24 | tool: tytanic@0.3.0 25 | 26 | - name: Run test suite 27 | run: tt run --no-fail-fast 28 | 29 | - name: Archive artifacts 30 | uses: actions/upload-artifact@v4 31 | if: always() 32 | with: 33 | name: artifacts 34 | path: | 35 | tests/**/diff/*.png 36 | tests/**/out/*.png 37 | tests/**/ref/*.png 38 | retention-days: 5 -------------------------------------------------------------------------------- /examples/qft.typ: -------------------------------------------------------------------------------- 1 | #import "../src/quill.typ": * 2 | 3 | #quantum-circuit( 4 | scale: 85%, 5 | row-spacing: 5pt, 6 | column-spacing: 8pt, 7 | lstick($|j_1〉$), $H$, $R_2$, midstick($ dots $), 8 | $R_(n-1)$, $R_n$, 8, permute(4, 3, 2, 1, 0, wire-count: (1, 1, 0, 1, 1)), 9 | rstick($1/sqrt(2)(|0〉+e^(2pi i 0.j_n)|1〉)$),[\ ], 10 | lstick($|j_2〉$), 1, ctrl(-1), midstick($ dots $), 2, $H$, midstick($ dots $), 11 | $R_(n-2)$, $R_(n-1)$, midstick($ dots $), 4, 12 | rstick($1/sqrt(2)(|0〉+e^(2pi i 0.j_(n-1)j_n)|1〉)$), [\ ], 13 | 14 | setwire(0), midstick($dots.v$), 1, midstick($dots.v$), [\ ], 15 | 16 | lstick($|j_(n-1)〉$), 3, ctrl(-3), 3, ctrl(-2), 1, midstick($ dots $), $H$, 17 | $R_2$, 2, rstick($1/sqrt(2)(|0〉+e^(2pi i 0.j_2 dots j_n)|1〉)$), [\ ], 18 | lstick($|j_n〉$), 4, ctrl(-4), 3, ctrl(-3), midstick($ dots $), 1, ctrl(-1), $H$, 1, 19 | rstick($1/sqrt(2)(|0〉+e^(2pi i 0.j_1 dots j_n)|1〉)$) 20 | ) 21 | -------------------------------------------------------------------------------- /examples/fault-tolerant-measurement.typ: -------------------------------------------------------------------------------- 1 | #import "../src/quill.typ": * 2 | 3 | #let group = gategroup.with(stroke: (dash: "dotted", thickness: .5pt)) 4 | 5 | #quantum-circuit( 6 | row-spacing: 6pt, 7 | fill-wires: false, 8 | lstick($|0〉$), 10pt, group(3, 2, label: (content: "Prepare")), $H$, ctrl(2), 3pt, 9 | group(4, 2, label: (content: "Verify")), 3, 10 | group(7, 3, label: (content: [Controlled-$M$])), 11 | ctrl(4), 2, 10pt, group(3, 2, label: (content: "Decode")), ctrl(2), $H$, meter(), [\ ], 12 | lstick($|0〉$), 1, targ(), 1, ctrl(2), 2, ctrl(4), 1, targ(), 2, [\ ], 13 | lstick($|0〉$), 1, targ(), ctrl(1), 4, ctrl(4), targ(), 2, [\ ], 14 | setwire(0), 2, lstick($|0〉$), setwire(1), targ(), targ(), 1, [\ ], 10pt, 15 | setwire(0), 4, lstick(align(center)[Encoded\ Data], n: 3), setwire(1), 1, 16 | $M'$, 3, [\ ], 17 | setwire(0), 5, setwire(1), 2, $M'$, 2, [\ ], 18 | setwire(0), 5, setwire(1), 3, $M'$, 1, 19 | ) -------------------------------------------------------------------------------- /tests/wire/control-wires/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit(circuit-padding: 0pt, 5 | mqgate($a$, target: 1, wire-count: 1, wire-label: (content: "a", dx: 0pt)), 6 | ctrl(1, wire-count: 2, wire-label: (content: "a", dx: 0pt)), 7 | swap(1, wire-count: 3, wire-label: (content: "a", dx: 0pt)), 8 | mqgate($a$, target: 1, wire-count: 4, wire-label: (content: "a", dx: 0pt)), 9 | mqgate($a$, target: 1, wire-count: 5, wire-label: (content: "abcde", dx: 0pt)),[\ ], 10 | mqgate($a$, target: 1, wire-count: 1), 11 | ctrl(1, wire-count: 2), 12 | swap(1, wire-count: 3), 13 | mqgate($a$, target: 1, wire-count: 4), 14 | mqgate($a$, target: 1, wire-count: 5),[\ ], 15 | 5 16 | ) 17 | 18 | #pagebreak() 19 | 20 | 21 | #quantum-circuit( 22 | wire: 1pt, 23 | fill-wires: false, 24 | mqgate(target: 1, wire-stroke: red)[A], 1, swap(1, wire-stroke: 2pt), targ(1, wire-stroke: blue + .5pt), [\ ], 25 | 1, ctrl(-1, wire-stroke: (dash: "dashed")), 2, meter(target: -1, wire-stroke: silver), 26 | ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2025 Mc-Zen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/arrow.typ: -------------------------------------------------------------------------------- 1 | 2 | #let draw-arrow(start, end, length: 5pt, width: 2.5pt) = context layout(size => { 3 | let convert-ratio(x, y) = { 4 | if type(x) == ratio { x *=size.width } 5 | if type(y) == ratio { y *= size.height } 6 | (x, y) 7 | } 8 | let start = convert-ratio(..start) 9 | let end = convert-ratio(..end) 10 | let stroke = line.stroke 11 | let arrow-color = stroke.paint 12 | if arrow-color == auto { arrow-color = black } 13 | place(line(start: start, end: end)) 14 | let dir = (end.at(0) - start.at(0), end.at(1) - start.at(1)) 15 | dir = dir.map(x => float(repr(x).slice(0,-2))) 16 | // let angle = calc.atan2(dir.at(0), dir.at(1)) 17 | 18 | let len = calc.norm(..dir) 19 | dir = dir.map(x => x / len) 20 | let normal = (-dir.at(1), dir.at(0)) 21 | 22 | let arrow-start = end 23 | let arrow-end = (end.at(0) + length*dir.at(0), end.at(1) + length*dir.at(1)) 24 | let w = width/2 25 | let v1 = (arrow-start.at(0) - w*normal.at(0), arrow-start.at(1) - w*normal.at(1)) 26 | let v2 = (arrow-start.at(0) + w*normal.at(0), arrow-start.at(1) + w*normal.at(1)) 27 | polygon(arrow-end, v1, v2, fill: arrow-color) 28 | }) 29 | 30 | #let test-arrow() = { 31 | draw-arrow((0%, 0%), (50%, 10%)) 32 | } 33 | 34 | 35 | #test-arrow() -------------------------------------------------------------------------------- /tests/gates/permute/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | 5 | #quantum-circuit( 6 | 2, permute(1,0), permute(1,0), 1, permute(2,0,1), 2, [\ ], 7 | 2, 4, permute(1,0), 1, [\ ], 8 | 2, gate($H$), 5, 9 | ) 10 | 11 | #pagebreak() 12 | 13 | // Test separation parameter 14 | #quantum-circuit( 15 | 1, 16 | permute(1,0, separation: none), 17 | permute(1,0, separation: 2pt), 18 | permute(1,0, separation: red), 19 | 1, [\ ], 20 | 5 21 | ) 22 | 23 | #pagebreak() 24 | 25 | // Test bend 26 | #quantum-circuit( 27 | 1, permute(1,0, bend: 0%), 1, [\ ], 28 | 3, 29 | ) 30 | 31 | 32 | #pagebreak() 33 | 34 | // Test wire count and stroke 35 | #quantum-circuit( 36 | setwire(2), 1, permute(1, 0, wire-count: (2, 1)), setwire(1), 1, [\ ], 37 | 2, setwire(2), 38 | ) 39 | #quantum-circuit( 40 | 1, permute(1, 0, wire-count: (1, 2), stroke: (auto, blue)),setwire(2, stroke: blue), [\ ], 41 | setwire(2, stroke: blue), 2, setwire(1, stroke: black), 1, 42 | ) 43 | 44 | // #pagebreak() 45 | 46 | // #quantum-circuit( 47 | // setwire(2), 2, permute(1,3,4,0,2), permute(1,0), 1, permute(2,0,1), 2, [\ ], 48 | // setwire(2), 2, 4, 1, [\ ], 49 | // setwire(2), 2, 1, 5, [\ ], 50 | // setwire(2), 2, 1, 5, [\ ],setwire(2), 51 | // ) 52 | -------------------------------------------------------------------------------- /tests/gates/meter/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | #quantum-circuit( 5 | 1, meter(n: 3), [\ ], 2, [\ ], 2 6 | ) 7 | 8 | #pagebreak() 9 | 10 | #quantum-circuit( 11 | scale: 120%, 12 | 1, meter(label: $y$), 1, meter(n: 1, label: $lr(|plus.minus〉)$), meter(label: $phi/2$, n: 1, wire-count: 1), meter(target: 1, label: $X$), meter(n: 2, label: $X$), [\ ], 13 | 1, meter(radius: 3pt, fill: gray), 3,ctrl(), 1 14 | ) 15 | 16 | #pagebreak() 17 | 18 | #quantum-circuit( 19 | gate-padding: 2pt, 20 | wire: .2pt + red, 21 | color: red, 22 | 1, meter(label: $y$), 1, meter(n: 1, label: $lr(|plus.minus〉)$), meter(label: $phi/2$, n: 1, wire-count: 1), meter(target: 1, label: "a"), meter(n: 2, label: "a"), [\ ], 23 | 1, gate($H$), 3, ctrl(), 2 24 | ) 25 | 26 | #pagebreak() 27 | 28 | 29 | #quantum-circuit( 30 | 1, meter(fill: yellow), meter(fill: yellow)[$P_1$ #meter-symbol], meter(n: 2, fill: yellow)[#meter-symbol \ $P$], meter(n: 2), [\ ], 31 | 1, meter[#place(super(baseline: -0.5em)[X]) #meter-symbol] 32 | ) 33 | 34 | #pagebreak() 35 | 36 | // meter with multiple labels 37 | 38 | #quantum-circuit( 39 | meter(label: (content: [A], pos: bottom)), 40 | meter( 41 | label: ( 42 | (content: [A], pos: bottom), 43 | (content: [B]), 44 | ) 45 | ), 46 | ) 47 | -------------------------------------------------------------------------------- /tests/decorations/lstick-and-rstick/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | 5 | #quantum-circuit( 6 | lstick("a"), gate($H$), rstick("b"), [\ ], 7 | 1, gate($H$), rstick($mat(a,b;c,d,)$, n: 3), [\ ], 8 | lstick("asd", n: 2), gate($H$), 1, [\ ], 9 | 1, gate($T$), 1 10 | ) 11 | 12 | #pagebreak() 13 | 14 | #quantum-circuit( 15 | lstick("single with brace", brace: "{"), gate($H$), rstick("b", brace: "]"), [\ ], 16 | 1, gate($H$), rstick($mat(a,b;c,d)$, n: 3, brace: none), [\ ], 17 | lstick("nobrace", n: 2, brace: none), gate($H$), 1, [\ ], 18 | 1, gate($T$), 1 19 | ) 20 | 21 | #pagebreak() 22 | 23 | #quantum-circuit( 24 | 1, gate($H$), rstick($mat(a,b;c,d)$, n: 3, brace: "|"), [\ ], 25 | lstick("[-brace", n: 2, brace: "["), gate($H$), 1, [\ ], 26 | 1, gate($T$), 1 27 | ) 28 | 29 | #pagebreak() 30 | 31 | #quantum-circuit( 32 | lstick("very long lstick"), 1 33 | ) 34 | 35 | #pagebreak() 36 | 37 | #quantum-circuit( 38 | 1, rstick("very long rstick") 39 | ) 40 | 41 | #pagebreak() 42 | 43 | // lstick with equation numbering 44 | #set math.equation(numbering: "1") 45 | #quantum-circuit( 46 | lstick($|0〉$, brace: "{"), 1 47 | ) 48 | 49 | #pagebreak() 50 | 51 | #quantum-circuit( 52 | lstick($|0〉$, brace: "{", pad: 10pt), 1, rstick($|0〉$, brace: "}", pad: 10pt) 53 | ) 54 | -------------------------------------------------------------------------------- /docs/images/logo.typ: -------------------------------------------------------------------------------- 1 | 2 | #import "/src/quill.typ": * 3 | 4 | #set page(width: auto, height: auto, margin: 1pt) 5 | 6 | 7 | 8 | 9 | 10 | // This is the one 11 | #rect( 12 | stroke: none, 13 | radius: 3pt, 14 | inset: (x: 15pt, y: 4pt), 15 | quantum-circuit( 16 | row-spacing: 7pt, 17 | column-spacing: 18pt, 18 | wire: .6pt, 19 | scale: 130%, 20 | lstick($|psi〉$), gategroup(2,4, fill:blue.lighten(80%), stroke: (thickness: .7pt, dash: "dashed")), 21 | gate([Quill], radius: 2pt), ctrl(1), 1, 1, meter(target: 1), [\ ], 22 | setwire(2), 1, phantom(content: "X"), ctrl(), 23 | 1, setwire(1, stroke: (thickness: .9pt, dash: "loosely-dotted")), 15pt, 1, 24 | setwire(2, stroke: (dash: "solid", thickness: .6pt)), ctrl(), 1, 25 | 26 | annotate((3.9, 4), (0.1, 1.4), ((x0, x1), (y0, y1)) => { 27 | let x1 = x0 + 21.5pt 28 | place( 29 | curve( 30 | stroke: .7pt, 31 | curve.move((x0, y1)), 32 | curve.cubic(none, (x1 - 20pt, y0 + 15pt), (x1, y0)) 33 | ) 34 | ) 35 | let xp = x0 + .14*(x1 - x0) 36 | let yp = y0 + .7*(y1 - y0) 37 | place( 38 | curve( 39 | curve.move((xp, yp)), 40 | curve.cubic(none, (x1 - 20pt, y0 + 15pt), (x1, y0)), 41 | // curve.cubic(none, (x1 - 14pt, y0 + 19pt), (xp, yp)), 42 | // curve.close(mode: "straight") 43 | ) 44 | ) 45 | place( 46 | curve( 47 | curve.move((xp, yp)), 48 | curve.cubic(none, (x1 - 17pt, y0 + 15pt), (x1, y0)) 49 | ) 50 | ) 51 | place( 52 | curve( 53 | curve.move((xp, yp)), 54 | curve.cubic(none, (x1 - 14pt, y0 + 19pt), (x1, y0)) 55 | ) 56 | ) 57 | 58 | }) 59 | ) 60 | ) 61 | -------------------------------------------------------------------------------- /tests/labels/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ": * 3 | 4 | 5 | #let labels-everywhere = ( 6 | (content: "lt", pos: left + top), 7 | (content: "t", pos: top), 8 | (content: "rt", pos: right + top), 9 | (content: "l", pos: left), 10 | (content: "c", pos: center), 11 | (content: "r", pos: right), 12 | (content: "lb", pos: left + bottom), 13 | (content: "b", pos: bottom), 14 | (content: "rb", pos: right + bottom), 15 | ) 16 | #quantum-circuit( 17 | gate(hide[H], label: labels-everywhere), 18 | ) 19 | 20 | #pagebreak() 21 | 22 | #quantum-circuit( 23 | 1, mqgate(hide[Gate], n: 2, label: labels-everywhere), 1, [\ ], 24 | 3, 25 | ) 26 | 27 | #pagebreak() 28 | 29 | #quantum-circuit( 30 | gategroup(1, 2, label: labels-everywhere), gate($X$), gate($Y$), 31 | ) 32 | 33 | #pagebreak() 34 | 35 | #quantum-circuit( 36 | 1, ctrl(label: (content: "al", pos: right + top)), 1 37 | ) 38 | 39 | #pagebreak() 40 | 41 | #quantum-circuit( 42 | 1, slice(label: labels-everywhere), 1, [\ ], 43 | 2, [\ ], 44 | 2, [\ ], 45 | 2, [\ ], 46 | 2, [\ ], 47 | 2, 48 | ) 49 | 50 | #pagebreak() 51 | 52 | #quantum-circuit( 53 | gate("H", label: ((content: "H", pos: left, dx: 0pt, dy: 0pt))), 54 | ) 55 | 56 | #pagebreak() 57 | 58 | #quantum-circuit( 59 | gate("H", label: ((content: "H", pos: bottom, dx: 0pt, dy: 0pt))) 60 | ) 61 | 62 | #pagebreak() 63 | 64 | #quantum-circuit( 65 | 2, slice(label: (content: "E", pos: top)), 66 | gategroup(1,2,padding: 1em, radius: 2em, label: (content: "E", pos: bottom)), gate($H$, label: (content: "E", pos: top, dx: 50% + 2em, dy: 50%)), 2, 67 | ) 68 | 69 | #pagebreak() 70 | 71 | #quantum-circuit( 72 | 1, $H$, 1, $H$,[H],$I$, $S$, "H", [\ ], 73 | lstick($|0〉$, label: "System 1"), gate($H$, label: "a"), 1, midstick("mid", label: (content: "r", pos: bottom)), 1, rstick($F$, label: "a"), setwire(0) 74 | ) -------------------------------------------------------------------------------- /src/utility.typ: -------------------------------------------------------------------------------- 1 | 2 | #let if-none(a, b) = { if a != none { a } else { b } } 3 | #let if-auto(a, b) = { if a != auto { a } else { b } } 4 | #let is-gate(item) = { type(item) == dictionary and "gate-type" in item } 5 | #let is-circuit-drawable(item) = { is-gate(item) or type(item) in (str, content) } 6 | #let is-circuit-meta-instruction(item) = { type(item) == dictionary and "qc-instr" in item } 7 | 8 | 9 | 10 | // Get content from a gate or plain content item 11 | #let get-content(item, draw-params) = { 12 | if is-gate(item) { 13 | if item.draw-function != none { 14 | return (item.draw-function)(item, draw-params) 15 | } 16 | } else { return item } 17 | } 18 | 19 | // Get size hint for a gate or plain content item 20 | #let get-size-hint(item, draw-params) = { 21 | if is-gate(item) { 22 | return (item.size-hint)(item, draw-params) 23 | } 24 | measure(item) 25 | } 26 | 27 | 28 | // Creates a sized brace with given length. 29 | // `brace` can be auto, defaulting to "{" if alignment is right 30 | // and "}" if alignment is left. Other possible values are 31 | // "[", "]", "|", "{", and "}". 32 | #let create-brace(brace, alignment, length) = { 33 | let lookup = ( 34 | "{": ${$, 35 | "}": $}$, 36 | "|": $|$, 37 | "[": $[$, 38 | "]": $]$, 39 | ) 40 | if brace == auto { 41 | brace = if alignment == right {${$} else {$}$} 42 | } else if brace != none { 43 | assert(brace in lookup, message: "Unsupported brace " + repr(brace)) 44 | brace = lookup.at(brace) 45 | } 46 | return $ lr(#brace, size: length) $ 47 | } 48 | 49 | /// Updates the first stroke with the second, i.e., returns the second stroke but all 50 | /// fields that are auto are inherited from the first stroke. 51 | /// If the second stroke is none, returns none. 52 | #let update-stroke(stroke1, stroke2) = { 53 | let if-not-auto(a, b) = if b == auto {a} else {b} 54 | if stroke2 == none { return none } 55 | if stroke2 == auto { return stroke1 } 56 | if stroke1 == none { stroke1 = stroke() } 57 | let s1 = stroke(stroke1) 58 | let s2 = stroke(stroke2) 59 | let paint = if-not-auto(s1.paint, s2.paint) 60 | let thickness = if-not-auto(s1.thickness, s2.thickness) 61 | let cap = if-not-auto(s1.cap, s2.cap) 62 | let join = if-not-auto(s1.join, s2.join) 63 | let dash = if-not-auto(s1.dash, s2.dash) 64 | let miter-limit = if-not-auto(s1.miter-limit, s2.miter-limit) 65 | return stroke(paint: paint, thickness: thickness, cap: cap, join: join, dash: dash, miter-limit: miter-limit) 66 | } 67 | -------------------------------------------------------------------------------- /src/process-args.typ: -------------------------------------------------------------------------------- 1 | // This file contains helper functions to process and normalize special 2 | // arguments to functions, especially where multiple formats and types 3 | // are allowed. 4 | 5 | #import "layout.typ" 6 | 7 | 8 | #let process-padding-arg(padding) = { 9 | let type = type(padding) 10 | if type == length { 11 | return (left: padding, top: padding, right: padding, bottom: padding) 12 | } 13 | if type == dictionary { 14 | let rest = padding.at("rest", default: 0pt) 15 | let x = padding.at("x", default: rest) 16 | let y = padding.at("y", default: rest) 17 | return ( 18 | left: padding.at("left", default: x), 19 | right: padding.at("right", default: x), 20 | top: padding.at("top", default: y), 21 | bottom: padding.at("bottom", default: y), 22 | ) 23 | } 24 | assert(false, message: "Unsupported type \"" + str(type) + "\" as argument for padding") 25 | } 26 | 27 | 28 | /// Process the label argument to `gate`. Allowed input formats are none, array of dictionaries 29 | /// or a single dictionary/string/content (for just one label). 30 | /// 31 | /// Each dictionary needs to contain the key content and may optionally have values 32 | /// for the keys `pos` (specifying a 1d or 2d alignment) and `dx` as well as `dy` 33 | #let process-label-arg( 34 | labels, 35 | default-pos: right, 36 | default-dx: .4em, 37 | default-dy: .4em 38 | ) = { 39 | if labels == none { return () } 40 | let type = type(labels) 41 | if type == dictionary { labels = (labels,) } 42 | else if type in (symbol, content, str) { labels = ((content: labels),) } 43 | else if type == dictionary { labels = ((content: labels),) } 44 | let processed-labels = () 45 | for label in labels { 46 | let alignment = layout.make-2d-alignment(label.at("pos", default: default-pos)) 47 | let (x, y) = layout.make-2d-alignment-factor(alignment) 48 | processed-labels.push(( 49 | content: label.content, 50 | pos: alignment, 51 | dx: label.at("dx", default: default-dx * x), 52 | dy: label.at("dy", default: default-dy * y) 53 | )) 54 | } 55 | processed-labels 56 | } 57 | 58 | 59 | /// Assert that there are no additional named arguments in an argument sink. 60 | #let assert-no-named( 61 | /// Argument sink. 62 | args, 63 | /// Function name. This can be used to improve the error message. 64 | fn: "" 65 | ) = { 66 | if args.named().len() == 0 { return } 67 | 68 | assert(false, 69 | message: "Unexpected named argument \"" + args.named().keys().first() + "\"" + if fn == "" {""} else { 70 | " in function " + fn + "()" 71 | } 72 | ) 73 | } -------------------------------------------------------------------------------- /tests/tequila/basic/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 0pt) 2 | #import "/src/quill.typ" 3 | #import quill: tequila as tq 4 | 5 | #quill.quantum-circuit( 6 | ..tq.build( 7 | tq.h(0), 8 | tq.cx(0, 1), 9 | tq.cx(0, 2), 10 | ) 11 | ) 12 | 13 | #pagebreak() 14 | 15 | 16 | #quill.quantum-circuit( 17 | ..tq.build( 18 | n: 3, 19 | tq.h((0,1)), 20 | tq.gate(1, $Pi$, fill: orange), 21 | tq.mqgate(0, "E", n: 2, fill: yellow) 22 | ) 23 | ) 24 | 25 | #pagebreak() 26 | 27 | 28 | #quill.quantum-circuit(..tq.build( 29 | tq.x(0), tq.y(0), tq.z(0), 30 | tq.h(0), tq.s(0), tq.sdg(0), tq.sx(0), tq.sxdg(0), 31 | tq.t(0), tq.tdg(0), tq.p($theta$, 0), 32 | )) 33 | 34 | #pagebreak() 35 | 36 | 37 | #quill.quantum-circuit(..tq.build( 38 | tq.rx($theta$, 0), tq.ry($theta$, 0), tq.rz($theta$, 0), tq.u($pi$, $0$, $lambda$, 0) 39 | )) 40 | 41 | #pagebreak() 42 | 43 | #quill.quantum-circuit(..tq.build( 44 | tq.h(range(2)), 45 | tq.t(3), 46 | tq.h(0), 47 | tq.h(5), 48 | // tq.barrier(), 49 | tq.cx(2,1), 50 | tq.cx(1,2), 51 | tq.cz(1,2), 52 | tq.swap(1,2), 53 | )) 54 | 55 | #pagebreak() 56 | 57 | #quill.quantum-circuit(..tq.build( 58 | tq.h(range(5)), 59 | tq.cz(1,2), 60 | tq.cz(3,4), 61 | tq.x(3), 62 | tq.cz(0, range(1,5)) 63 | )) 64 | 65 | 66 | 67 | #pagebreak() 68 | 69 | #quill.quantum-circuit(..tq.build( 70 | tq.cz(0, 2), 71 | tq.h((0, 1, 2)) 72 | )) 73 | 74 | 75 | 76 | #pagebreak() 77 | 78 | #quill.quantum-circuit(..tq.build( 79 | append-wire: false, 80 | tq.s((0, 1, 2)), 81 | tq.meter(range(1,3)), 82 | )) 83 | 84 | 85 | 86 | #pagebreak() 87 | 88 | #quill.quantum-circuit(..tq.build( 89 | tq.ccx((0, 1, 2, 1, 0, 2), (1, 0, 1, 2, 2, 0), (2, 2, 0, 0, 1, 1)), 90 | tq.ccx(2, 4, 1), 91 | tq.ccz(2, 3, 1), 92 | tq.y(1), 93 | tq.cca(1, 4, 2, $X$), 94 | tq.h(range(5)), 95 | tq.cccx(0, 1, 3, 2), 96 | tq.multi-controlled-gate((0,1,4), 2, quill.mqgate.with(n:2, $K$)), 97 | tq.multi-controlled-gate((1,), 0, quill.gate.with($Y$)) 98 | )) 99 | 100 | 101 | #pagebreak() 102 | 103 | #quill.quantum-circuit(..tq.build( 104 | tq.measure(0), 105 | tq.measure(2, 1), 106 | tq.measure(1, 2), 107 | )) 108 | 109 | 110 | #pagebreak() 111 | 112 | #quill.quantum-circuit(..tq.build( 113 | n: 5, 114 | tq.x(0), 115 | tq.barrier(start: 0, end: 2), 116 | tq.h(range(5)) 117 | )) 118 | 119 | 120 | // #pagebreak() 121 | 122 | // #quill.quantum-circuit( 123 | // import tq: *, 124 | // ..tq.build( 125 | // n: 6, 126 | // import tq: *, 127 | // h(0), 128 | // h(1), 129 | // cx(1, 0), 130 | // cx(1, 3), 131 | // measure(1, 4), 132 | // barrier(start: 0, end: 1), 133 | // ), 134 | // [\ ], 135 | // [\ ], 136 | // [\ ], 137 | // [\ ], 138 | // quill.setwire(2) 139 | // ) -------------------------------------------------------------------------------- /docs/guide/template.typ: -------------------------------------------------------------------------------- 1 | #import "../../src/quill.typ" 2 | #import quill: * 3 | 4 | 5 | // The project function defines how your document looks. 6 | // It takes your content and some metadata and formats it. 7 | // Go ahead and customize it to your liking! 8 | #let project( 9 | title: "", 10 | abstract: [], 11 | authors: (), 12 | url: none, 13 | date: none, 14 | version: none, 15 | body, 16 | ) = { 17 | // Set the document's basic properties. 18 | set document(author: authors, title: title) 19 | set page(numbering: "1", number-align: left) 20 | set heading(numbering: "I.a") 21 | show heading.where(level: 1): it => block(smallcaps(it), below: 1em) 22 | show link: underline.with(offset: 1.2pt) 23 | 24 | v(4em) 25 | 26 | 27 | // Title row. 28 | align(center)[ 29 | #block(text(weight: 700, 1.75em, title)) 30 | #v(4em, weak: true) 31 | v#version #h(1.2cm) #date 32 | #block(link(url)) 33 | #v(1.5em, weak: true) 34 | ] 35 | 36 | // Author information. 37 | pad( 38 | top: 0.5em, 39 | x: 2em, 40 | grid( 41 | columns: (1fr,) * calc.min(3, authors.len()), 42 | gutter: 1em, 43 | ..authors.map(author => align(center, strong(author))), 44 | ), 45 | ) 46 | v(4em) 47 | 48 | // Abstract. 49 | pad( 50 | x: 2em, 51 | top: 1em, 52 | bottom: 1.1em, 53 | align(center)[ 54 | // #heading( 55 | // outlined: false, 56 | // numbering: none, 57 | // text(0.85em, smallcaps[Abstract]), 58 | // ) 59 | #abstract 60 | ], 61 | ) 62 | 63 | // Main body. 64 | set par(justify: true) 65 | set raw(lang: "typc") 66 | show raw: set text(size: .9em) 67 | show raw.where(block: true) : set par(justify: false) 68 | 69 | 70 | show link: set text(fill: purple.darken(30%)) 71 | 72 | body 73 | } 74 | 75 | 76 | 77 | 78 | #let makefigure(code, content, vertical: false) = { 79 | align(center, 80 | box(fill: gray.lighten(90%), inset: .8em, { 81 | table( 82 | align: center + horizon, 83 | columns: if vertical { 1 } else { 2 }, 84 | gutter: 1em, 85 | stroke: none, 86 | box(code), block(content) 87 | ) 88 | }) 89 | ) 90 | } 91 | #let stdscale = scale 92 | 93 | #let example(code, vertical: false, scope: (:), text-size: 1em, scale: 100%) = { 94 | figure( 95 | pad(y: 1em, 96 | box(fill: gray.lighten(90%), inset: .8em, { 97 | table( 98 | align: center + horizon, 99 | columns: if vertical { 1 } else { 2 }, 100 | gutter: 1em, 101 | stroke: none, 102 | { 103 | set text(size: text-size) 104 | box(code) 105 | }, 106 | block(stdscale(scale, reflow: true, eval("#import quill: *\n" + code.text, mode: "markup", scope: (quill: quill) + scope))) 107 | ) 108 | }) 109 | ) 110 | ) 111 | } 112 | 113 | 114 | #let insert-example(filename, fontsize: 1em) = { 115 | let content = read(filename) 116 | content = content.slice(content.position("*")+1).trim() 117 | makefigure(text(size: fontsize, raw(lang: "typ", block: true, content)), []) 118 | figure(include(filename)) 119 | } 120 | 121 | #let ref-fn(name) = link(label("quill:" + name), raw(name, lang: "")) 122 | -------------------------------------------------------------------------------- /src/verifications.typ: -------------------------------------------------------------------------------- 1 | #let plural-s(n) = if n == 1 { "" } else { "s" } 2 | 3 | #let verify-controlled-gate(gate, x, y, circuit-rows, circuit-cols) = { 4 | let multi = gate.multi 5 | if y + multi.target >= circuit-rows or y + multi.target < 0 { 6 | assert(false, message: 7 | "A controlled gate starting at qubit " + str(y) + 8 | " with relative target " + str(multi.target) + 9 | " exceeds the circuit which has only " + str(circuit-rows) + 10 | " qubit" + plural-s(circuit-rows) 11 | ) 12 | } 13 | } 14 | 15 | 16 | #let verify-mqgate(gate, x, y, circuit-rows, circuit-cols) = { 17 | let nq = gate.multi.num-qubits 18 | if y + nq - 1 >= circuit-rows { 19 | assert(false, message: 20 | "A " + str(nq) + "-qubit gate starting at qubit " + 21 | str(y) + " exceeds the circuit which has only " + 22 | str(circuit-rows) + " qubits" 23 | ) 24 | } 25 | } 26 | 27 | #let verify-slice(slice, x, y, circuit-rows, circuit-cols) = { 28 | if slice.wires < 0 { 29 | assert(false, message: "`slice`: The number of wires needs to be > 0 (is " + str(slice.wires) + ")") 30 | } 31 | if y + slice.wires > circuit-rows { 32 | assert(false, message: 33 | "A `slice` starting at qubit " + str(y) + 34 | " spanning " + str(slice.wires) + 35 | " qubit" + plural-s(slice.wires) + 36 | " exceeds the circuit which has only " + 37 | str(circuit-rows) + " qubit" + plural-s(circuit-rows) 38 | ) 39 | } 40 | } 41 | 42 | 43 | #let verify-gategroup(gategroup, x, y, circuit-rows, circuit-cols) = { 44 | if gategroup.wires <= 0 { 45 | assert(false, message: "`gategroup`: The number of wires needs to be > 0 (is " + str(gategroup.wires) + ")") 46 | } 47 | if gategroup.steps <= 0 { 48 | assert(false, message: "`gategroup`: The number of steps needs to be > 0 (is " + str(gategroup.steps) + ")") 49 | } 50 | if y + gategroup.wires > circuit-rows { 51 | assert(false, message: 52 | "A `gategroup` at qubit " + str(y) + 53 | " spanning " + str(gategroup.wires) + 54 | " qubit" + plural-s(gategroup.wires) + 55 | " exceeds the circuit which has only " + 56 | str(circuit-rows) + " qubit" + plural-s(circuit-rows) 57 | ) 58 | } 59 | if x + gategroup.steps > circuit-cols { 60 | assert(false, message: 61 | "A `gategroup` at column " + str(x) + 62 | " spanning " + str(gategroup.steps) + 63 | " column" + plural-s(gategroup.steps) + 64 | " exceeds the circuit which has only " + 65 | str(circuit-cols) + " column" + plural-s(circuit-cols) 66 | ) 67 | } 68 | } 69 | 70 | #let verify-annotation-content(annotation-content) = { 71 | let content-type = type(annotation-content) 72 | assert(content-type in (symbol, content, str, dictionary), message: "`annotate`: Unsupported callback return type `" + str(content-type) + "` (can be `dictionary` or `content`") 73 | 74 | if content-type == dictionary { 75 | assert("content" in annotation-content, message: "`annotate`: Missing field `content` in annotation. If the callback returns a dictionary, it must contain the key `content` and may specify coordinates with `dx` and `dy`.") 76 | if "z" in annotation-content { 77 | let z = annotation-content.z 78 | assert(z in ("below", "above"), message: "`annotate`: The parameter `z` can take the values `\"above\"` and `\"below\"`") 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /docs/guide/gallery.typ: -------------------------------------------------------------------------------- 1 | #import "../../src/quill.typ": * 2 | 3 | #set page(width: 18cm, height: auto, margin: 0mm) 4 | 5 | 6 | #let gallery = { 7 | set raw(lang: "typc") 8 | table( 9 | align: center + horizon, 10 | columns: (1fr, 1fr, 1.3fr, 1.1fr, 1fr, 1.48fr), 11 | column-gutter: (0pt, 0pt, 2.5pt, 0pt, 0pt), 12 | 13 | [Normal gate], quantum-circuit(1, gate($H$), 1), raw("gate($H$), $H$"), 14 | [Round gate], quantum-circuit(1, gate($X$, radius: 100%), 1), raw("gate($X$, \nradius: 100%)"), 15 | [D-shaped gate], quantum-circuit(1, gate($Y$, radius: (right: 100%)), 1), raw("gate($Y$, radius: \n(right: 100%))"), 16 | [Meter], quantum-circuit(1, meter(), 1), raw("meter()"), 17 | [Meter with \ label], quantum-circuit(1, meter(label: $lr(|±〉)$), 1), raw("meter(label: \n$lr(|±〉)$)"), 18 | [Phase gate], quantum-circuit(1, phase($α$), 1), raw("phase($α$)"), 19 | [Control], quantum-circuit(1, ctrl(), 1), raw("ctrl()"), 20 | [Open control], quantum-circuit(1, ctrl(open: true), 1), raw("ctrl(open: true)"), 21 | [Target], quantum-circuit(1, targ(), 1), raw("targ()"), 22 | [Swap target], quantum-circuit(1, swap(), 1), raw("swap()"), 23 | [Permutation \ gate], quantum-circuit(1, permute(2,0,1), 1, [\ ], 3, [\ ], 3), raw("permute(2,0,1)"), 24 | [Multi-qubit \ gate], quantum-circuit(1, mqgate($U$, n: 3), 1, [\ ], 3, [\ ], 3), raw("mqgate($U$, n: 3)"), 25 | [lstick], quantum-circuit(lstick($|psi〉$), 2), raw("lstick($|psi〉$)"), 26 | [rstick], quantum-circuit(2, rstick($|psi〉$)), raw("rstick($|psi〉$)"), 27 | [Multi-qubit \ lstick], quantum-circuit(row-spacing: 10pt, lstick($|psi〉$, n: 2), 2, [\ ], 3), raw("lstick($|psi〉$, \nn: 2)"), 28 | [Multi-qubit \ rstick], quantum-circuit(row-spacing: 10pt,2, rstick($|psi〉$, n: 2, brace: "]"),[\ ], 3), raw("rstick($|psi〉$, \nn: 2, brace: \"]\")"), 29 | [midstick], quantum-circuit(1, midstick("yeah"),1), raw("midstick(\"yeah\")"), 30 | [Wire bundle], quantum-circuit(1, nwire(5), 1), raw("nwire(5)"), 31 | [Controlled \ #smallcaps("z")-gate], quantum-circuit(1, ctrl(1), 1, [\ ], 1, ctrl(), 1), [#raw("ctrl(1)") \ + \ #raw("ctrl()")], 32 | [Controlled \ #smallcaps("x")-gate], quantum-circuit(1, ctrl(1), 1, [\ ], 1, targ(), 1), [#raw("ctrl(1)") \ + \ #raw("targ()")], 33 | [Swap \ gate], quantum-circuit(1, swap(1), 1, [\ ], 1, swap(), 1), [#raw("swap(1)") \ + \ #raw("swap()")], 34 | [Controlled \ Hadamard], quantum-circuit(1, mqgate($H$, target: 1), 1, [\ ], 1, ctrl(), 1), [#raw("mqgate($H$,target:1)") \ + \ #raw("ctrl()")], 35 | [Plain\ vertical\ wire], quantum-circuit(1, ctrl(1, show-dot: false), 1, [\ ], 3), raw("ctrl(1, show-dot: false)"), 36 | [Meter to \ classical], quantum-circuit(1, meter(target: 1), 1, [\ ], setwire(2), 1, ctrl(), 1), [#raw("meter(target: 1)") \ + \ #raw("ctrl()")], 37 | [Classical wire], quantum-circuit(setwire(2), 3), raw("setwire(2)"), [Styled wire], quantum-circuit(setwire(1, stroke: green), 3), raw("setwire(1, stroke: green)"), 38 | [Labels], 39 | quantum-circuit(scale: 100%, 40 | 1, gate($Q$, label: ( 41 | (content: "b",pos:top), 42 | (content:"b",pos:bottom), 43 | ( content: "a", 44 | pos: left + top ), 45 | ( content: "c", 46 | pos: right + top, 47 | dy: 0pt, dx: 50% ), 48 | )), 1 49 | ), 50 | [#set text(size: .6em);```typc 51 | gate($Q$, label: ( 52 | (content: "b",pos:top), 53 | (content:"b",pos:bottom), 54 | ( content: "a", 55 | pos: left + top ), 56 | ( content: "c", 57 | pos: right + top, 58 | dy: 0pt, dx: 50% ), 59 | )) 60 | ``` 61 | ], 62 | [Gate inputs \ and outputs], 63 | quantum-circuit(scale: 80%, 64 | 1, mqgate($U$, n: 3, width: 5em, 65 | inputs: ( 66 | (qubit: 0, n: 2, label: $x$), 67 | (qubit: 2, label: $y$) 68 | ), 69 | outputs: ( 70 | (qubit: 0, n: 2, label: $x$), 71 | (qubit: 2, label: $y ⊕ f(x)$) 72 | ), 73 | ), 1, [\ ], 3, [\ ], 3 74 | ), 75 | [#set text(size: .6em);```typc 76 | mqgate($U$, n: 3, width: 5em, 77 | inputs: ( 78 | (qubit:0, n:2, label:$x$), 79 | (qubit:2, label: $y$) 80 | ), 81 | outputs: ( 82 | (qubit:0, n:2, label:$x$), 83 | (qubit:2, label:$y⊕f(x)$) 84 | ) 85 | )```] 86 | ) 87 | } 88 | 89 | 90 | #rect( 91 | stroke: none, 92 | radius: 3pt, 93 | inset: (x: 6pt, y: 6pt), 94 | fill: white, 95 | gallery 96 | ) -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | The main algorithm used in `quantum-circuit` for circuit layout is quite intricate and is described here broadly for a better overview. The algorithm specifically takes care of the following things: 4 | - Not drawing wires through gates: this way we can also use transparent gates. 5 | - Computing the bounding box of the circuit: it also treats labels that can be attached to almost anything. 6 | - Computing the position of automatically placed items. 7 | - Custom spacing between rows and columns (so-called gutters). 8 | - Correctly adjusting the wire distance according to the gate heights: especially for the case of multi-qubit gates. 9 | 10 | 11 | ## Notes 12 | ### Differentiation between single-qubit and multi-qubit gates 13 | `quill` differentiates between ordinary single-qubit gates (such as a Hadmard gate) and multi-qubit-gates. The latter are generalized gates that can 14 | a) span across multiple qubits, 15 | b) have a control wire towards some target qubit, 16 | c) have both. 17 | 18 | This differentiation is used because multi-qubit gates require much more care and processing. 19 | 20 | 21 | ### Anatomy of a cell 22 | 23 | ``` 24 | cell gutter 25 | ┌─────────┬──┐ ┐ 26 | │ ┌───┐ │ │ │ 27 | wire ─┼──┤ H ├──┼──┼─ │ cell height 28 | │ └───┘ │ │ │ ┐ 29 | └─────────┴──┘ ┘ ┘ 0.5*row-spacing 30 | └─────────┘ 31 | cell width 32 | 33 | └──┘ 34 | 0.5*column-spacing 35 | ``` 36 | A quantum circuit is made up of a matrix of cells. Gates are by default placed in the middle of a cell (exception for example `lstick`) and wires _always_ run through vertically centered through the cell. 37 | 38 | The padding of the cell is determined by the value of `column-spacing` and `row-spacing`. These lengths can be specified in `quantum-circuit()` and are added to the size of the gate to compute a temporary cell size. The largest cell in a row and column determines the final cell width and height. If `equal-row-heights` is true, then all rows are resized to match the largest row. To the right of each cell (or rather column) is an optional gutter that has zero width by default. Additional row gutters can increase the spacing between rows. 39 | 40 | As an example, this code 41 | ```typ 42 | #quantum-circuit( 43 | 1, $H$, 10pt, ctrl(1), 1, [\ ], 15pt 44 | 2, 5pt, $X$, 1 45 | ) 46 | ``` 47 | produces a circuit according the following schematic: 48 | ``` 49 | col 0 col 1 50 | ┌─────────┬──┬─────────┬┐ 51 | │ ┌───┐ │ │ ││ 52 | wire 0 ─┼──┤ H ├──┼──┼────o────┼┼─ 53 | │ └───┘ │ │ │ ││ 54 | ├─────────┼──┼────┼────┼┤ 55 | ├─────────┼──┼────┼────┼┤ ← 15pt row gutter 56 | │ │ │ ┌─┴─┐ ││ 57 | wire 1 ─┼─────────┼──┼──┤ X ├──┼┼─ 58 | │ │ │ └───┘ ││ 59 | └─────────┴──┴─────────┴┘ 60 | ↑ ↑ 61 | 10pt gutter 0pt gutter 62 | ``` 63 | Note, that the `5pt` gutter is overridden by the `10pt` gutter in the same column. 64 | 65 | 66 | ## Description of the `quantum-circuit()` layout algorithm 67 | 68 | The algorithm is roughly divided into two parts. First, we iterate over all children, determine their position and compute the layout. In the second step, the actual circuit is created by composing the different item groups: 69 | - decorations 70 | - horizontal and vertical wires 71 | - single-qubit-gates and multi-qubit gates. 72 | 73 | ### Preprocessing 74 | First, "auto"-gates are processed, i.e., we replace `str` and `content` items (like `$H$`) with gates. 75 | 76 | ### Build Matrix 77 | All gates are arranged in a grid — the _matrix_. By default, gates are placed automatically, advancing the column index but gates can also be explicitly placed in a specific cell. In the first pass through all children, we: 78 | - Determine the matrix position of automatically placed items. 79 | - Add an entry to the matrix for each gate that contains the `size` of the gate, whether the gate is in `box` mode and some gutter value for optional spacing after the corresponding column. Empty cells simply have a size of 0. The matrix is automatically resized to accommodate for new gates. Each cell can only host one item. 80 | - Store all column gutters (specified by `length` children after a gate). 81 | - Store row gutters separately (specified by `length` children after a `[\ ]`). 82 | - Store all `setwire()` instructions in an array to be processed later. 83 | - Put all normal (non-controlled or multi-qubit) gates in an array `single-qubit-gates`. 84 | - Put multi-qubit-gates (including controlled gates) in an array `multi-qubit-gates`. 85 | - Store all decorations (such as `slice`, `gategroup`, or `annotate`) together with their cell position in an array. 86 | 87 | The matrix requires some post-processing to equalize the row lengths. The column gutters are computed as the maximum gutters per column across all rows. 88 | 89 | ### Process multi-qubit gates 90 | 91 | For all multi-qubit gates that have a `target`, a (vertical) control wire instruction is stored containing column, starting and target wire, as well as the wire style. 92 | 93 | If a gate spans across multiple qubits, the size-hints `width` is unconditionally set to the width of the gate for all cells that the gate contains. This is important for wire placement. Additionally, if the `size-all-wires` parameter requires it, the cell height is set to the same value as well. 94 | 95 | ### Finish layout computation 96 | 97 | Now the necessary height of each row and the width of each column can be computed using the size hints stored in the matrix. In both cases the maximum value per column/row is used. For ease of access the center coordinates of each column and row is computed from the row heights, column widths and row/column gutters. 98 | 99 | ### Build circuit 100 | 101 | In this step, the circuit is crafted from the individual components. First, the decorations (`slice`, `gategroup`, `annotate`) are drawn on two layers (one below the circuit which is applied immediately and one above the circuit which is applied later on). Afterwards, the horizontal and verticals wires are drawn. Here, we need to take care not to drawn _through_ a gate and use the size hints from the matrix cells. Then, the gates are drawn and finally the second the decoration layer is applied. 102 | 103 | Most items in the circuit feature the attachment of labels at each side. In order to accommodate for their size and to appropriately pad the circuit, the bounds of the circuit need to be updated for each item with labels. These contain gates, decorations, and vertical wires. 104 | 105 | ### Apply scaling and bounds extension 106 | 107 | Finally, the entire circuit is scaled according to the `scale` argument and padded with the bounds that were computed in the building step. -------------------------------------------------------------------------------- /src/layout.typ: -------------------------------------------------------------------------------- 1 | #import "length-helpers.typ": * 2 | 3 | /// Update bounds to contain the given rectangle 4 | /// - bounds (array): Current bound coordinates x0, y0, x1, y1 5 | /// - rect (array): Bounds rectangle x0, y0, x1, y1 6 | #let update-bounds(bounds, rect) = ( 7 | calc.min(bounds.at(0), rect.at(0).to-absolute()), 8 | calc.min(bounds.at(1), rect.at(1).to-absolute()), 9 | calc.max(bounds.at(2), rect.at(2).to-absolute()), 10 | calc.max(bounds.at(3), rect.at(3).to-absolute()), 11 | ) 12 | 13 | #let offset-bounds(bounds, offset) = ( 14 | bounds.at(0) + offset.at(0), 15 | bounds.at(1) + offset.at(1), 16 | bounds.at(2) + offset.at(0), 17 | bounds.at(3) + offset.at(1), 18 | ) 19 | 20 | #let make-bounds(x0: 0pt, y0: 0pt, width: 0pt, height: 0pt, x1: none, y1: none) = ( 21 | x0.to-absolute(), 22 | y0.to-absolute(), 23 | (if x1 != none { x1 } else { x0 + width }).to-absolute(), 24 | (if y1 != none { y1 } else { y0 + height }).to-absolute(), 25 | ) 26 | 27 | 28 | /// Take an alignment or 2d alignment and return a 2d alignment with the possibly 29 | /// unspecified axis set to a default value. 30 | #let make-2d-alignment(alignment, default-vertical: horizon, default-horizontal: center) = { 31 | let axis = alignment.axis() 32 | if axis == none { return alignment } 33 | if alignment.axis() == "horizontal" { return alignment + default-vertical } 34 | if alignment.axis() == "vertical" { return alignment + default-horizontal } 35 | } 36 | 37 | 38 | #let make-2d-alignment-factor(alignment) = { 39 | let alignment = make-2d-alignment(alignment) 40 | let x = 0 41 | let y = 0 42 | if alignment.x == left { x = -1 } 43 | else if alignment.x == right { x = 1 } 44 | if alignment.y == top { y = -1 } 45 | else if alignment.y == bottom { y = 1 } 46 | return (x, y) 47 | } 48 | 49 | 50 | 51 | 52 | #let default-size-hint(item, draw-params) = { 53 | let content = (item.draw-function)(item, draw-params) 54 | let hint = measure(content) 55 | hint.offset = auto 56 | return hint 57 | } 58 | 59 | 60 | 61 | 62 | 63 | #let lrstick-size-hint(item, draw-params) = { 64 | let content = (item.draw-function)(item, draw-params) 65 | let hint = measure(content) 66 | let dx = 0pt 67 | if item.data.align == right { dx = hint.width } 68 | hint.offset = (x: dx, y: auto) 69 | return hint 70 | } 71 | 72 | 73 | 74 | 75 | 76 | /// Place some content along with optional labels while computing bounds. 77 | /// 78 | /// Returns a pair of the placed content and a bounds array. 79 | /// 80 | /// - content (content): The content to place. 81 | /// - dx (length): Horizontal displacement. 82 | /// - dy (length): Vertical displacement. 83 | /// - size (auto, dictionary): For computing bounds, the size of the placed content 84 | /// is needed. If `auto` is passed, this function computes the size itself 85 | /// but if it is already known it can be passed through this parameter. 86 | /// - labels (array): An array of labels which in turn are dictionaries that must 87 | /// specify values for the keys `content` (content), `pos` (strictly 2d 88 | /// alignment), `dx` and `dy` (both length, ratio or relative length). 89 | /// - draw-params (dictionary): Drawing parameters. 90 | /// -> pair 91 | #let place-with-labels( 92 | content, 93 | dx: 0pt, 94 | dy: 0pt, 95 | size: auto, 96 | labels: (), 97 | draw-params: none, 98 | ) = { 99 | if size == auto { size = measure(content) } 100 | let bounds = make-bounds( 101 | x0: dx, y0: dy, width: size.width, height: size.height 102 | ) 103 | if labels.len() == 0 { 104 | return (place(content, dx: dx, dy: dy), bounds) 105 | } 106 | 107 | let placed-labels = place(dx: dx, dy: dy, 108 | box({ 109 | for label in labels { 110 | let label-size = measure(label.content) 111 | let ldx = get-length(label.dx, size.width) 112 | let ldy = get-length(label.dy, size.height) 113 | 114 | if label.pos.x == left { ldx -= label-size.width } 115 | else if label.pos.x == center { ldx += 0.5 * (size.width - label-size.width) } 116 | else if label.pos.x == right { ldx += size.width } 117 | if label.pos.y == top { ldy -= label-size.height } 118 | else if label.pos.y == horizon { ldy += 0.5 * (size.height - label-size.height) } 119 | else if label.pos.y == bottom { ldy += size.height } 120 | 121 | 122 | let placed-label = place(label.content, dx: ldx, dy: ldy) 123 | let label-bounds = make-bounds( 124 | x0: ldx + dx, y0: ldy + dy, 125 | width: label-size.width, height: label-size.height 126 | ) 127 | bounds = update-bounds(bounds, label-bounds) 128 | placed-label 129 | } 130 | }) 131 | ) 132 | return (place(content, dx: dx, dy: dy) + placed-labels, bounds) 133 | } 134 | 135 | 136 | 137 | 138 | // From a list of row heights or col widths, compute the respective 139 | // cell center coordinates, e.g., (3pt, 3pt, 4pt) -> (1.5pt, 4.5pt, 8pt) 140 | #let compute-center-coords(cell-lengths, gutter) = { 141 | let center-coords = () 142 | let tmpx = 0pt 143 | gutter.insert(0, 0pt) 144 | // assert.eq(cell-lengths.len(), gutter.len()) 145 | for (cell-length, gutter) in cell-lengths.zip(gutter) { 146 | center-coords.push(tmpx + cell-length / 2 + gutter) 147 | tmpx += cell-length + gutter 148 | } 149 | return center-coords 150 | } 151 | 152 | 153 | // Given a list of n center coordinates and n cell sizes along one axis (x or y), retrieve the coordinates for a single cell or a list of cells given by index. 154 | // If a cell index is out of bounds, the outer last coordinate is returned 155 | // center-coords: List of center coordinates for each index 156 | // cell-sizes: List of cell sizes for each index 157 | // cells: Indices of cell for which to retrieve coordinates 158 | // These may also be floats. In this case, the integer part determines the cell index and the fractional part a percentage of the cell width. e.g., passing 2.5 would return the center coordinate of the cell 159 | #let get-cell-coords(center-coords, cell-sizes, cells) = { 160 | let last = center-coords.at(-1) + cell-sizes.at(-1) / 2 161 | let get(x) = { 162 | let integral = calc.floor(x) 163 | let fractional = x - integral 164 | let cell-width = cell-sizes.at(integral, default: 0pt) 165 | return center-coords.at(integral, default: last) + cell-width * (fractional - 0.5) 166 | } 167 | if type(cells) in (int, float) { get(cells) } 168 | else if type(cells) == array { cells.map(x => get(x)) } 169 | else { panic("Unsupported coordinate type") } 170 | } 171 | 172 | #let get-cell-coords1(center-coords, cell-sizes, cells) = { 173 | let last = center-coords.at(-1) + cell-sizes.at(-1) / 2 174 | let get(x) = { 175 | let integral = calc.floor(x) 176 | let fractional = x - integral 177 | let cell-width = cell-sizes.at(integral, default: 0pt) 178 | let c1 = center-coords.at(integral, default: last) 179 | let c2 = center-coords.at(integral + 1, default: last) 180 | return c1 + (c2 - c1) * (fractional) 181 | } 182 | if type(cells) in (int, float) { get(cells) } 183 | else if type(cells) == array { cells.map(x => get(x)) } 184 | else { panic("Unsupported coordinate type") } 185 | } 186 | 187 | #let std-scale = scale 188 | -------------------------------------------------------------------------------- /src/decorations.typ: -------------------------------------------------------------------------------- 1 | #import "gates.typ": * 2 | 3 | 4 | // align: "left" (for rstick) or "right" (for lstick) 5 | // brace: auto, none, "{", "}", "|", "[", ... 6 | #let lrstick(content, n, align, brace, label, pad: 0pt, x: auto, y: auto) = gate( 7 | content, 8 | x: x, 9 | y: y, 10 | draw-function: draw-functions.draw-lrstick, 11 | size-hint: layout.lrstick-size-hint, 12 | box: false, 13 | floating: true, 14 | multi: if n == 1 { none } else { 15 | ( 16 | target: none, 17 | num-qubits: n, 18 | wire-count: 0, 19 | label: label, 20 | size-all-wires: if n > 1 { none } else { false }, 21 | pass-through: () 22 | )}, 23 | data: ( 24 | brace: brace, 25 | align: align, 26 | pad: pad 27 | ), 28 | label: label 29 | ) 30 | 31 | 32 | 33 | /// Basic command for labelling a wire at the start. 34 | /// 35 | /// ```example 36 | /// #quantum-circuit( 37 | /// lstick($|0〉$), 1 38 | /// ) 39 | /// ``` 40 | /// It can also span multiple wires 41 | /// ```example 42 | /// #quantum-circuit( 43 | /// lstick($rho$, n: 2), 1, [\ ], 44 | /// 1 45 | /// ) 46 | /// ``` 47 | #let lstick( 48 | 49 | /// Label to display, e.g., `$|0〉$`. 50 | /// -> content 51 | body, 52 | 53 | /// How many wires the `lstick` should span. 54 | /// -> int 55 | n: 1, 56 | 57 | /// If `brace` is `auto`, then a default `{` brace is shown only if `n > 1`. 58 | /// A brace is always shown when explicitly given, e.g., `"}"`, `"["` or 59 | /// `"|"`. No brace is shown for `brace: none`. 60 | /// -> auto | none | str 61 | brace: auto, 62 | 63 | /// Adds a padding between the label and the connected wire to the right. 64 | /// -> length 65 | pad: 0pt, 66 | 67 | /// One or more labels to add to the gate. See @gate.label. 68 | /// -> none | array | str | content | dictionary 69 | label: none, 70 | 71 | x: auto, 72 | 73 | y: auto 74 | 75 | ) = lrstick(body, n, right, brace, label, pad: pad, x: x, y: y) 76 | 77 | 78 | 79 | /// Basic command for labelling a wire at the end. 80 | /// 81 | /// ```example 82 | /// #quantum-circuit( 83 | /// 1, rstick($|0〉$) 84 | /// ) 85 | /// ``` 86 | /// It can also span multiple wires 87 | /// ```example 88 | /// #quantum-circuit( 89 | /// 1, rstick($rho$, n: 2), [\ ], 90 | /// ) 91 | /// ``` 92 | #let rstick( 93 | 94 | /// Label to display, e.g., `$|0〉$`. 95 | /// -> content 96 | body, 97 | 98 | /// How many wires the `rstick` should span. 99 | /// -> int 100 | n: 1, 101 | 102 | /// If `brace` is `auto`, then a default `}` brace is shown only if `n > 1`. 103 | /// A brace is always shown when explicitly given, e.g., `"}"`, `"["` or 104 | /// `"|"`. No brace is shown for `brace: none`. 105 | /// -> auto | none | str 106 | brace: auto, 107 | 108 | /// Adds a padding between the label and the connected wire to the left. 109 | /// -> length 110 | pad: 0pt, 111 | 112 | /// One or more labels to add to the gate. See @gate. 113 | /// -> none | array | str | content | dictionary 114 | label: none, 115 | 116 | x: auto, 117 | 118 | y: auto 119 | 120 | ) = lrstick(body, n, left, brace, label, pad: pad, x: x, y: y) 121 | 122 | 123 | 124 | /// Create a midstick, i.e., a mid-circuit text. 125 | /// 126 | /// ```example 127 | /// #quantum-circuit( 128 | /// 1, midstick($cal(E)$), 1 129 | /// ) 130 | /// ``` 131 | /// It can also span multiple wires 132 | /// ```example 133 | /// #quantum-circuit( 134 | /// 1, midstick($cal(E)^2$, n: 2), 135 | /// 1, [\ ], 136 | /// ) 137 | /// ``` 138 | #let midstick( 139 | 140 | /// Label to display, e.g., `$rho$`. 141 | /// -> content 142 | body, 143 | 144 | /// How many wires the `midstick` should span. 145 | /// -> content 146 | n: 1, 147 | 148 | /// One or more labels to add to the gate. See @gate. 149 | /// -> none | array | str | content | dictionary 150 | label: none, 151 | 152 | /// How to fill the midstick. 153 | /// -> none color | gradient | tiling 154 | fill: none, 155 | 156 | x: auto, 157 | 158 | y: auto 159 | 160 | ) = { 161 | if n == 1 { 162 | gate(body, draw-function: draw-functions.draw-unboxed-gate, label: label, fill: fill, x: x, y: y) 163 | } else { 164 | mqgate(body, n: n, draw-function: draw-functions.draw-boxed-multigate, label: label, fill: fill, x: x, y: y, stroke: none) 165 | } 166 | } 167 | 168 | 169 | 170 | /// Creates a symbol similar to `\qwbundle` on `quantikz`. Annotates a wire to 171 | /// be a bundle of quantum or classical wires. 172 | /// 173 | /// ```example 174 | /// #quantum-circuit( 175 | /// 1, nwire($5$), 1 176 | /// ) 177 | /// ``` 178 | #let nwire( 179 | 180 | /// The label to put on top of the bundle. 181 | /// -> int | content 182 | body, 183 | 184 | x: auto, 185 | 186 | y: auto 187 | 188 | ) = gate([#body], draw-function: draw-functions.draw-nwire, box: false, x: x, y: y) 189 | 190 | 191 | 192 | /// Set current wire mode (0: none, 1 wire: quantum, 2 wires: classical, more 193 | /// are possible) and optionally the stroke style. 194 | /// 195 | /// The wire style is reset for each row. 196 | #let setwire( 197 | 198 | /// Number of wires to display. 199 | /// -> int 200 | count, 201 | 202 | /// When given, the stroke is applied to the wire. Otherwise the current stroke is kept. 203 | /// -> auto | none | stroke 204 | stroke: auto, 205 | 206 | /// Distance between wires. 207 | /// -> length 208 | wire-distance: auto, 209 | 210 | /// The starting column of the wire change command. 211 | /// -> auto | int 212 | x: auto, 213 | 214 | /// The starting wire of the wire change command. 215 | /// -> auto | int 216 | y: auto 217 | 218 | ) = { 219 | let result = ( 220 | x: x, 221 | y: y, 222 | qc-instr: "setwire", 223 | ) 224 | if count != auto { result.count = count } 225 | if stroke != auto { result.stroke = stroke } 226 | if wire-distance != auto { result.distance = wire-distance } 227 | result 228 | } 229 | 230 | 231 | /// Highlight a group of circuit elements by drawing a rectangular box around 232 | /// them. 233 | #let gategroup( 234 | 235 | /// Number of wires to include. Is ignored if @gategroup.bottom is given. 236 | /// -> int 237 | wires, 238 | 239 | /// Number of columns to include. Is ignored if @gategroup.right is given. 240 | /// -> int 241 | steps, 242 | 243 | /// The starting column of the gategroup. 244 | /// -> auto | int 245 | x: auto, 246 | 247 | /// The starting wire of the gategroup. 248 | /// -> auto | int 249 | y: auto, 250 | 251 | /// The column where the gategroup should end. 252 | /// -> auto | int 253 | right: auto, 254 | 255 | /// The row where the gategroup should end. 256 | /// -> auto | int 257 | bottom: auto, 258 | 259 | /// The gategroup can be placed `"below"` or `"above"` the circuit. 260 | /// -> "below" | "above" 261 | z: "below", 262 | 263 | /// Padding of rectangle. May be one length for all sides or a dictionary 264 | /// with the keys `left`, `right`, `top`, `bottom` and `default`. Not all 265 | /// keys need to be specified. The value for `default` is used for the 266 | /// omitted sides or `0pt` if no `default` is given. 267 | /// -> length | dictionary 268 | padding: 0pt, 269 | 270 | /// Stroke for rectangle. 271 | /// -> stroke 272 | stroke: .7pt, 273 | 274 | /// Fill color for rectangle. 275 | /// -> color 276 | fill: none, 277 | 278 | /// Corner radius for rectangle. 279 | /// -> length, dictionary 280 | radius: 0pt, 281 | 282 | /// One or more labels to add to the group. See @gate. 283 | /// -> none | array | str | content | dictionary 284 | label: none 285 | 286 | ) = ( 287 | qc-instr: "gategroup", 288 | wires: wires, 289 | steps: steps, 290 | x: x, 291 | y: y, 292 | z: z, 293 | right: right, 294 | bottom: bottom, 295 | padding: process-args.process-padding-arg(padding), 296 | style: (fill: fill, stroke: stroke, radius: radius), 297 | labels: process-args.process-label-arg(label, default-pos: top) 298 | ) 299 | 300 | 301 | 302 | /// Slice the circuit vertically, showing a separation line between columns. 303 | #let slice( 304 | 305 | /// Number of wires to slice. 306 | /// -> int 307 | n: 0, 308 | 309 | /// The starting column of the slice. 310 | /// -> auto | int 311 | x: auto, 312 | 313 | /// The starting wire of the slice. 314 | /// -> auto | int 315 | y: auto, 316 | 317 | /// The slice can be placed `"below"` or `"above"` the circuit. 318 | /// -> "below" | "above" 319 | z: "below", 320 | 321 | /// Line style for the slice. 322 | /// -> stroke 323 | stroke: (paint: red, thickness: .7pt, dash: "dashed"), 324 | 325 | /// One or more labels to add to the slice. See @gate. 326 | /// -> none | array | str | content | dictionary 327 | label: none 328 | 329 | ) = ( 330 | qc-instr: "slice", 331 | wires: n, 332 | x: x, 333 | y: y, 334 | z: z, 335 | style: (stroke: stroke), 336 | labels: process-args.process-label-arg(label, default-pos: top) 337 | ) 338 | 339 | 340 | 341 | /// Lower-level interface to the cell coordinates to create an arbitrary 342 | /// annotatation by passing a custom function. 343 | /// 344 | /// This function is passed the coordinates of the specified cell rows 345 | /// and columns. 346 | #let annotate( 347 | 348 | /// Column indices for which to obtain coordinates. 349 | /// -> int | array 350 | columns, 351 | 352 | /// Row indices for which to obtain coordinates. 353 | /// -> int | array 354 | rows, 355 | 356 | /// Function to call with the obtained coordinates. The signature should 357 | /// be with signature `(col-coords, row-coords) => {}`. This function is 358 | /// expected to display the content to draw in absolute coordinates within 359 | /// the circuit. 360 | /// -> function 361 | callback, 362 | 363 | /// The slice can be placed `"below"` or `"above"` the circuit. 364 | /// -> "below" | "above" 365 | z: "below" 366 | 367 | ) = ( 368 | qc-instr: "annotate", 369 | rows: rows, 370 | x: none, 371 | y: none, 372 | z: z, 373 | columns: columns, 374 | callback: callback 375 | ) 376 | -------------------------------------------------------------------------------- /src/draw-functions.typ: -------------------------------------------------------------------------------- 1 | // INTERNAL GATE DRAW FUNCTIONS 2 | 3 | 4 | #import "utility.typ" 5 | #import "arrow.typ" 6 | #import "layout.typ" 7 | 8 | 9 | 10 | 11 | 12 | 13 | // Default gate draw function. Draws a box with global padding 14 | // and the gates content. Stroke and default fill are only applied if 15 | // gate.box is true 16 | #let draw-boxed-gate(gate, draw-params) = align(center, box( 17 | inset: draw-params.padding, 18 | width: gate.width, 19 | radius: gate.radius, 20 | stroke: if gate.box { utility.if-auto(gate.stroke, draw-params.wire) }, 21 | fill: utility.if-auto(gate.fill, if gate.box {draw-params.background}), 22 | gate.content, 23 | )) 24 | 25 | // Same but without displaying a box 26 | #let draw-unboxed-gate(gate, draw-params) = box( 27 | inset: draw-params.padding, 28 | fill: utility.if-auto(gate.fill, draw-params.background), 29 | gate.content 30 | ) 31 | 32 | // Draw a gate spanning multiple wires 33 | #let draw-boxed-multigate(gate, draw-params) = { 34 | let dy = draw-params.multi.wire-distance 35 | let extent = gate.multi.extent 36 | if extent == auto { extent = draw-params.x-gate-size.height / 2 } 37 | 38 | let style-params = ( 39 | width: gate.width, 40 | stroke: utility.if-auto(gate.stroke, draw-params.wire), 41 | radius: gate.radius, 42 | fill: utility.if-auto(gate.fill, draw-params.background), 43 | inset: draw-params.padding, 44 | ) 45 | align(center + horizon, box( 46 | ..style-params, 47 | height: dy + 2 * extent, 48 | gate.content 49 | )) 50 | 51 | 52 | let draw-inouts(inouts, alignment) = { 53 | 54 | if inouts != none and dy != 0pt { 55 | let width = measure(line(length: gate.width)).width 56 | let y0 = -(dy + extent) - draw-params.center-y-coords.at(0) 57 | let get-wire-y(qubit) = { draw-params.center-y-coords.at(qubit) + y0 } 58 | 59 | set text(size: .8em) 60 | context { 61 | for inout in inouts { 62 | let size = measure(inout.label) 63 | let y = get-wire-y(inout.qubit) 64 | let label-x = draw-params.padding 65 | if "n" in inout and inout.n > 1 { 66 | let y2 = get-wire-y(inout.qubit + inout.n - 1) 67 | let brace = utility.create-brace(auto, alignment, y2 - y + draw-params.padding) 68 | let brace-x = 0pt 69 | let size = measure(brace) 70 | if alignment == right { brace-x += width - size.width } 71 | 72 | place(brace, dy: y - 0.5 * draw-params.padding, dx: brace-x) 73 | label-x = size.width 74 | y += 0.5 * (y2 - y) 75 | } 76 | place(dy: y - size.height / 2, align( 77 | alignment, 78 | box(inout.label, width: width, inset: (x: label-x)) 79 | )) 80 | } 81 | } 82 | 83 | } 84 | 85 | } 86 | draw-inouts(gate.multi.inputs, left) 87 | draw-inouts(gate.multi.outputs, right) 88 | } 89 | 90 | #let draw-targ(item, draw-params) = { 91 | let size = item.data.size 92 | box({ 93 | circle( 94 | radius: size, 95 | stroke: draw-params.wire, 96 | fill: utility.if-auto(item.fill, draw-params.background) 97 | ) 98 | place(line(start: (size, 0pt), length: 2*size, angle: -90deg, stroke: draw-params.wire)) 99 | place(line(start: (0pt, -size), length: 2*size, stroke: draw-params.wire)) 100 | }) 101 | } 102 | 103 | #let draw-ctrl(gate, draw-params) = { 104 | let color = utility.if-auto(gate.fill, draw-params.color) 105 | if "show-dot" in gate.data and not gate.data.show-dot { return none } 106 | if gate.data.open { 107 | let stroke = utility.if-auto(gate.fill, draw-params.wire) 108 | box(circle(stroke: stroke, fill: draw-params.background, radius: gate.data.size)) 109 | } else { 110 | box(circle(fill: color, radius: gate.data.size)) 111 | } 112 | } 113 | 114 | #let draw-swap(gate, draw-params) = { 115 | box({ 116 | let d = gate.data.size 117 | let stroke = draw-params.wire 118 | box(width: d, height: d, { 119 | place(line(start: (-0pt, -0pt), end: (d, d), stroke: stroke)) 120 | place(line(start: (d, 0pt), end: (0pt, d), stroke: stroke)) 121 | }) 122 | }) 123 | } 124 | 125 | 126 | #let meter-symbol = box(rect( 127 | stroke: none, fill: none, 128 | inset: 0pt, outset: 0pt, 129 | { 130 | place(curve( 131 | curve.move((0%, 110%)), 132 | curve.quad((13%, 40%), (50%, 40%)), 133 | curve.quad(auto, (100%, 110%)), 134 | )) 135 | set align(left) 136 | arrow.draw-arrow((50%, 120%), (90%, 30%), length: 3.8pt, width: 2.8pt) 137 | } 138 | )) 139 | 140 | 141 | 142 | #let draw-meter(gate, draw-params) = { 143 | let height = draw-params.x-gate-size.height 144 | let width = 1.5 * height 145 | height -= 2 * draw-params.padding 146 | width -= 2 * draw-params.padding 147 | 148 | gate.content = block({ 149 | set align(top) 150 | set curve(stroke: draw-params.wire) 151 | set line(stroke: draw-params.wire) 152 | set rect(width: width, height: height) 153 | 154 | utility.if-auto(gate.content, meter-symbol) 155 | }) 156 | 157 | if gate.multi != none and gate.multi.num-qubits > 1 { 158 | draw-boxed-multigate(gate, draw-params) 159 | } else { 160 | draw-boxed-gate(gate, draw-params) 161 | } 162 | } 163 | 164 | 165 | #let draw-nwire(gate, draw-params) = { 166 | set text(size: .7em) 167 | let size = measure(gate.content) 168 | let extent = 2.5pt + size.height 169 | box(height: 2 * extent, { // box is solely for height hint 170 | place(dx: 1pt, dy: 0pt, gate.content) 171 | place(dy: extent, line(start: (0pt,-4pt), end: (-5pt,4pt), stroke: draw-params.wire)) 172 | }) 173 | } 174 | 175 | 176 | 177 | #let draw-permutation-gate(gate, draw-params) = { 178 | let dy = draw-params.multi.wire-distance 179 | let width = gate.width 180 | if dy == 0pt { return box(width: width, height: 4pt) } 181 | 182 | let separation = gate.data.separation 183 | if separation == auto { separation = draw-params.background } 184 | if type(separation) == color { separation += 3pt } 185 | if type(separation) == length { separation += draw-params.background } 186 | 187 | let qubits = gate.data.qubits 188 | let wire-counts = gate.data.wire-count 189 | let stroke = gate.data.stroke 190 | 191 | box( 192 | height: dy + 4pt, 193 | inset: (y: 2pt), 194 | fill: draw-params.background, 195 | width: width, { 196 | let y0 = draw-params.center-y-coords.at(gate.qubit) 197 | let bend = gate.data.bend * width / 2 198 | for (from, wire-count) in wire-counts.enumerate() { 199 | let to = qubits.at(from) 200 | let y-from = draw-params.center-y-coords.at(from + gate.qubit) - y0 201 | let y-to = draw-params.center-y-coords.at(to + gate.qubit) - y0 202 | if separation != none { 203 | place(curve( 204 | curve.move((0pt, y-from)), 205 | curve.cubic((bend, y-from), (width - bend, y-to), (width, y-to)), 206 | stroke: separation 207 | )) 208 | } 209 | let correction = calc.atan((y-to - y-from) / width) / 1.2rad * calc.sqrt(gate.data.bend / 100%) 210 | let pcurve(dy) = place(dy: dy, curve( 211 | curve.move((-.1pt, y-from)), 212 | curve.cubic((bend - dy*correction, y-from), (width + .1pt - bend - dy*correction, y-to), (width + .1pt, y-to)), 213 | stroke: utility.update-stroke(draw-params.wire, stroke.at(from)) 214 | )) 215 | range(wire-count) 216 | .map(i => (2*i - (wire-count - 1)) * 1pt) 217 | .map(pcurve).join() 218 | 219 | } 220 | } 221 | ) 222 | } 223 | 224 | // Draw an lstick (align: "right") or rstick (align: "left") 225 | #let draw-lrstick(gate, draw-params) = { 226 | 227 | assert(gate.data.align in (left, right), message: "`lstick`/`rstick`: Only left and right are allowed for parameter align") 228 | 229 | let content = box(inset: draw-params.padding, gate.content) 230 | let size = measure(content) 231 | let brace = none 232 | 233 | if gate.data.brace != none { 234 | let brace-height 235 | if gate.multi == none { 236 | brace-height = 1em + 2 * draw-params.padding 237 | } else { 238 | brace-height = draw-params.multi.wire-distance + .5em 239 | } 240 | let brace-symbol = gate.data.brace 241 | if brace-symbol == auto and gate.multi == none { 242 | brace-symbol = none 243 | } 244 | brace = utility.create-brace(brace-symbol, gate.data.align, brace-height) 245 | } 246 | 247 | let brace-size = measure(brace) 248 | let width = size.width + brace-size.width + gate.data.pad 249 | let height = size.height 250 | let brace-offset-y 251 | let content-offset-y = 0pt 252 | 253 | if gate.multi == none { 254 | brace-offset-y = size.height / 2 - brace-size.height / 2 255 | } else { 256 | let dy = draw-params.multi.wire-distance 257 | // at first (layout) stage: 258 | if dy == 0pt { return box(width: 2 * width, height: 0pt, content) } 259 | height = dy 260 | content-offset-y = -size.height / 2 + height / 2 261 | brace-offset-y = -.25em 262 | } 263 | 264 | let brace-pos-x = if gate.data.align == right { size.width } else { gate.data.pad } 265 | let content-pos-x = if gate.data.align == right { 0pt } else { width - size.width } 266 | 267 | box( 268 | width: width, 269 | height: height, 270 | { 271 | place(brace, dy: brace-offset-y, dx: brace-pos-x) 272 | place(content, dy: content-offset-y, dx: content-pos-x) 273 | } 274 | ) 275 | } 276 | 277 | 278 | 279 | 280 | #let draw-gategroup(x1, x2, y1, y2, item, draw-params) = { 281 | let p = item.padding 282 | let (x1, x2, y1, y2) = (x1 - p.left, x2 + p.right, y1 - p.top, y2 + p.bottom) 283 | let size = (width: x2 - x1, height: y2 - y1) 284 | layout.place-with-labels( 285 | dx: x1, dy: y1, 286 | labels: item.labels, 287 | size: size, 288 | draw-params: draw-params, rect( 289 | width: size.width, height: size.height, 290 | stroke: item.style.stroke, 291 | fill: item.style.fill, 292 | radius: item.style.radius 293 | ) 294 | ) 295 | } 296 | 297 | #let draw-slice(x, y1, y2, item, draw-params) = layout.place-with-labels( 298 | dx: x, dy: y1, 299 | size: (width: 0pt, height: y2 - y1), 300 | labels: item.labels, 301 | draw-params: draw-params, 302 | line(angle: 90deg, length: y2 - y1, stroke: item.style.stroke) 303 | ) 304 | 305 | 306 | 307 | #let draw-horizontal-wire(x1, x2, y, stroke, wire-count, wire-distance: 1pt) = { 308 | if x1 == x2 { return } 309 | 310 | let wire = line(start: (x1, y), end: (x2, y), stroke: stroke) 311 | range(wire-count) 312 | .map(i => (2*i - (wire-count - 1)) * wire-distance) 313 | .map(dy => place(wire, dy: dy)) 314 | .join() 315 | } 316 | 317 | #let draw-vertical-wire( 318 | y1, 319 | y2, 320 | x, 321 | stroke, 322 | wire-count: 1, 323 | wire-distance: 1pt, 324 | ) = { 325 | let height = y2 - y1 326 | 327 | let wire = line(start: (0pt, 0pt), end: (0pt, height), stroke: stroke) 328 | let wires = range(wire-count) 329 | .map(i => 2 * i * wire-distance) 330 | .map(dx => place(wire, dx: dx)) 331 | 332 | place( 333 | dx: x - (wire-count - 1) * wire-distance, 334 | dy: y1, 335 | wires.join() 336 | ) 337 | } 338 | 339 | #let draw-vertical-wire-with-labels( 340 | y1, 341 | y2, 342 | x, 343 | stroke, 344 | wire-count: 1, 345 | wire-distance: 1pt, 346 | wire-labels: (), 347 | draw-params: none, 348 | ) = { 349 | let height = y2 - y1 350 | 351 | let wire = line(start: (0pt, 0pt), end: (0pt, height), stroke: stroke) 352 | let wires = range(wire-count) 353 | .map(i => 2 * i * wire-distance) 354 | .map(dx => place(wire, dx: dx)) 355 | 356 | layout.place-with-labels( 357 | dx: x - (wire-count - 1) * wire-distance, 358 | dy: y1, 359 | labels: wire-labels, 360 | draw-params: draw-params, 361 | size: (width: 2 * (wire-count - 1) * wire-distance, height: height), 362 | wires.join() 363 | ) 364 | } 365 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Quill 3 |
4 | 5 |
6 | 7 | [![Typst Package](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FMc-Zen%2Fquill%2Fv0.7.2%2Ftypst.toml&query=%24.package.version&prefix=v&logo=typst&label=package&color=239DAD)](https://typst.app/universe/package/quill) 8 | [![Test Status](https://github.com/Mc-Zen/quill/actions/workflows/run_tests.yml/badge.svg)](https://github.com/Mc-Zen/quill/actions/workflows/run_tests.yml) 9 | [![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/Mc-Zen/quill/blob/main/LICENSE) 10 | [![User Manual](https://img.shields.io/badge/manual-.pdf-purple)][guide] 11 | 12 |
13 | 14 | 15 | **Quill** is a package for creating quantum circuit diagrams in [Typst](https://typst.app/). 16 | It features two distinct creation models: 17 | - The manual and powerful [grid model](#basic-usage-the-grid-model). 18 | - The automatic, instruction-driven model [Tequila](#tequila) which is also useful for _composing_ sub-circuits. These circuits are then embedded into the grid model. 19 | 20 | Outline: 21 | 22 | 23 | - [Usage](#basic-usage-the-grid-model): _A quick introduction_ 24 | - [Cheat sheet](#cheat-sheet): _A gallery showcasing various circuit elements_ 25 | - [Tequila](#tequila): _Building circuits similar to QASM or Qiskit_ 26 | - [Examples](#examples) 27 | - [Changelog](#changelog) 28 | 29 | 30 | ## Basic usage (the grid model) 31 | 32 | The function `quantum-circuit()` takes any number of positional gates and works similar to the built-in Typst functions `table()` and `grid()`. 33 | - A variety of different gate and instruction commands are available for adding elements. 34 | - Integers can be used to produce any number of empty cells (filled with the current wire style). 35 | - A new wire is started by adding a `[\ ]` item. 36 | 37 | 38 | ```typ 39 | #{ 40 | import "@preview/quill:0.7.2": * 41 | 42 | quantum-circuit( 43 | lstick($|0〉$), $H$, ctrl(1), rstick($(|00〉+|11〉)/√2$, n: 2), [\ ], 44 | lstick($|0〉$), 1, targ(), 1 45 | ) 46 | } 47 | ``` 48 |
49 | 50 | ![Bell state preparation circuit](https://github.com/user-attachments/assets/f80f6041-379e-440c-bcda-95348f066f17) 51 | 52 |
53 | 54 | Plain quantum gates — such as a Hadamard gate — can be written with the shorthand notation `$H$` instead of the more lengthy `gate($H$)`. The latter offers additional styling options. 55 | 56 | Refer to the [user guide][guide] for a full documentation of this package. You can also look up the documentation of any function by calling the help module, e.g., `#help("gate")` just where you are currently typing (powered by [tidy][tidy]). 57 | 58 | 59 | ## Cheat Sheet 60 | 61 | This gallery quickly showcases a large selection of possible gates and decorations that can be added to any quantum circuit. 62 | 63 |
64 | 65 | ![Gallery](https://github.com/user-attachments/assets/c7852e83-6652-4503-a52e-3a658c123a37) 66 | 67 |
68 | 69 | 70 | 71 | 72 | 73 | ## Tequila 74 | 75 | 76 | _Tequila_ is a submodule that adds a completely different way of building circuits. 77 | 78 | ```typ 79 | #import "@preview/quill:0.7.2" as quill: tequila as tq 80 | 81 | #quill.quantum-circuit( 82 | ..tq.build( 83 | tq.h(0), 84 | tq.cx(0, 1), 85 | tq.cx(0, 2), 86 | ), 87 | quill.gategroup(x: 2, y: 0, 3, 2) 88 | ) 89 | ``` 90 | 91 | This is similar to how _QASM_ and _Qiskit_ work: gates are successively applied to the circuit which is then laid out automatically by packing gates as tightly as possible. We start by calling the `tq.build()` function and filling it with quantum operations. This returns a collection of gates which we expand into the circuit with the `..` syntax. 92 | Now, we still have the option to add annotations, groups, slices, or even more gates via manual placement. 93 | 94 | The syntax works analogously to Qiskit. Available gates are `x`, `y`, `z`, `h`, `s`, `sdg`, `sx`, `sxdg`, `t`, `tdg`, `p`, `rx`, `ry`, `rz`, `u`, `cx`, `cz`, and `swap`. With `barrier`, an invisible barrier can be inserted to prevent gates on different qubits to be packed tightly. Finally, with `tq.gate` and `tq.mqgate`, a generic gate can be created. These two accept the same styling arguments as the normal `gate` (or `mqgate`). 95 | 96 | Also like Qiskit, all qubit arguments support ranges, e.g., `tq.h(range(5))` adds a Hadamard gate on the first five qubits and `tq.cx((0, 1), (1, 2))` adds two CX gates: one from qubit 0 to 1 and one from qubit 1 to 2. 97 | 98 | With Tequila, it is easy to build templates for quantum circuits and to compose circuits of various building blocks. For this purpose, `tq.build()` and the built-in templates all feature optional `x` and `y` arguments to allow placing a sub-circuit at an arbitrary position of the circuit. 99 | As an example, Tequila provides a `tq.graph-state()` template for quickly drawing graph state preparation circuits. 100 | 101 | The following example demonstrates how to compose multiple sub-circuits. 102 | 103 | 104 | ```typ 105 | #import tequila as tq 106 | 107 | #quantum-circuit( 108 | ..tq.graph-state((0, 1), (1, 2)), 109 | ..tq.build(y: 3, 110 | tq.p($pi$, 0), 111 | tq.cx(0, (1, 2)), 112 | ), 113 | ..tq.graph-state(x: 6, y: 2, invert: true, (0, 1), (0, 2)), 114 | gategroup(x: 1, 3, 3), 115 | gategroup(x: 1, y: 3, 3, 3), 116 | gategroup(x: 6, y: 2, 3, 3), 117 | slice(x: 5) 118 | ) 119 | ``` 120 |
121 | Gallery 122 |
123 | 124 | 125 | ## Examples 126 | 127 | Some show-off examples, loosely replicating figures from [Quantum Computation and Quantum Information by M. Nielsen and I. Chuang](https://www.cambridge.org/highereducation/books/quantum-computation-and-quantum-information/01E10196D0A682A6AEFFEA52D53BE9AE#overview). The code for these examples can be found in the [example folder][examples] or in the [user guide][guide]. 128 | 129 |
130 | Quantum teleportation circuit 131 |
132 |
133 | Quantum circuit for phase estimation 134 |
135 |
136 | Quantum fourier transformation circuit 137 |
138 | 139 | ## Contribution 140 | 141 | If you spot an issue or have a suggestion, you are invited to [post it](https://github.com/Mc-Zen/quill/issues) or to contribute to this package. In [architecture.md][architecture], you can also find a description of the algorithm that forms the base of `quantum-circuit()`. 142 | 143 | ## Tests 144 | This package uses [tytanic](https://github.com/tingerrr/tytanic) for running [tests](tests/). 145 | 146 | 147 | ## Changelog 148 | 149 | 150 | ### v0.7.2 151 | - Added a paremter `wire-stroke` to all controlled gates such as `mqgate`, `ctrl`, `targ`, `swap`, and `meter` that gives control over the stroke of the control wire(s). 152 | - Added an optional label to `tequila.measure`. 153 | - Fixed multiple and manually positioned labels with `meter`. 154 | - Allow specifying the right/bottom end of a `gategroup` with two new parameter `gategroup.right` and `gategroup.bottom`. 155 | 156 | ### v0.7.1 157 | - Added the parameter `wires` to `quantum-circuit` that allows defining the number of (qu)bits explicitly. This parameter also accepts an array of wire counts, e.g., `wires: (1,) * qubits + (2,) * clbits` which is useful to avoid having to many `setwire` commands. 158 | - Added a `pass-through` parameter to `mqgate` that enables wires to pass through a multi-qubit gate instead of attaching themselves as in- and outputs. 159 | - Added the parameters `wire-count` and `stroke` to permute, allowing classical or even mixed permutation gates. 160 | - Added more flexibility to meters by allowing the content to be specified. 161 | - Fixed circuits with right-to-left text direction 162 | 163 | ### v0.7.0 164 | - Improvements to Tequila: 165 | - Exposed a `tequila.ca` gate for arbitrary single-qubit controlled gates. 166 | - Added `tequila.measure` to replace `tequila.meter`. The new gate can also receive an index of a wire to send the result to via a classical wire. 167 | - Addd optional `start` and `end` parameters to `tequila.barrier` that allow local barriers. 168 | - Fixed problems with `multi-controlled-gate`. 169 | - `targ` can now take a "target" qubit as in `targ(2)` to produce a vertical wire, just like `ctrl` and `swap`. 170 | - Both `ctrl` and `swap` can now be used without target argument like `ctrl()`. This can replace usage like `ctrl(0)` for gates without a control wire. 171 | - Added position parameters `x` and `y` to the `phantom` gate. 172 | 173 | ### v0.6.1 174 | - Fixes braces in circuits which were broken in new Typst version 0.13. 175 | - Replaces usage of the now deprecated `path` element with the new `curve` element. 176 | - ⚠️ Requires Typst 0.13. 177 | 178 | 179 | ### v0.6.0 180 | - Improved support for Tequila: controlled-gates can now take additional styling parameters. 181 | - ⚠️ Removed `targX`: use `swap(0)` instead. 182 | - Fixed `stroke` for the plain `gate` command. 183 | - Big documentation update. 184 | 185 | ### v0.5.0 186 | - Added support for multi-controlled gates with Tequila. 187 | - Switched to using `context` instead of the now deprecated `style()` for measurement. 188 | Note: Starting with this version, Typst 0.11.0 or higher is required. 189 | 190 | ### v0.4.0 191 | - Alternative model for creating and composing circuits: [Tequila](#tequila). 192 | 193 | ### v0.3.0 194 | - New features 195 | - Enable manual placement of gates, `gate($X$, x: 3, y: 1)`, similar to built-in `table()` in addition to automatic placement. This works for most elements, not only gates. 196 | - Add parameter `pad` to `lstick()` and `rstick()`. 197 | - Add parameter `fill-wires` to `quantum-circuit()`. All wires are filled unto the end (determined by the longest wire) by default (breaking change ⚠️). This behavior can be reverted by setting `fill-wires: false`. 198 | - `gategroup()` `slice()` and `annotate()` can now be placed above or below the circuit with `z: "above"` and `z: "below"`. 199 | - `help()` command for quickly displaying the documentation of a given function, e.g., `help("gate")`. Powered by [tidy][tidy]. 200 | - Improvements: 201 | - Complete rework of circuit layout implementation 202 | - allows transparent gates since wires are not drawn through gates anymore. The default fill is now `auto` and using `none` sets the background to transparent. 203 | - `midstick` is now transparent by default. 204 | - `setwire()` can now be used to override only partial wire settings, such as wire color `setwire(1, stroke: blue)`, width `setwire(1, stroke: 1pt)` or wire distance, all separately. Before, some settings were reset. 205 | - Fixes: 206 | - Fixed `lstick`/`rstick` when equation numbering is turned on. 207 | - Removed: 208 | - The already deprecated `scale-factor` (use `scale` instead) 209 | 210 | ### v0.2.1 211 | - Improvements: 212 | - Add `fill` parameter to `midstick()`. 213 | - Add `bend` parameter to `permute()`. 214 | - Add `separation` parameter to `permute()`. 215 | - Fixes: 216 | - With Typst 0.11.0, `scale()` now takes into account outer alignment. This broke the positioning of centered/right-aligned circuits, e.g., ones put into a `figure()`. 217 | - Change wires to be drawn all through `ctrl()`, making it consistent to `swap()` and `targ()`. 218 | 219 | 220 | ### v0.2.0 221 | - New features: 222 | - Add arbitrary labels to any `gate` (also derived gates such as `meter`, `ctrl`, ...), `gategroup` or `slice` that can be anchored to any of the nine 2d alignments. 223 | - Add optional gate inputs and outputs for multi-qubit gates (see gallery). 224 | - Implicit gates (breaking change ⚠️): a content item automatically becomes a gate, so you can just type `$H$` instead of `gate($H$)` (of course, the `gate()` function is still important in order to use the many available options). 225 | - Other breaking changes ⚠️: 226 | - `slice()` has no `dx` and `dy` parameters anymore. Instead, labels are handled through `label` exactly as in `gate()`. Also the `wires` parameter is replaced with `n` for consistency with other multi-qubit gates. 227 | - Swap order of row and column parameters in `annotate()` to make it consistent with built-in Typst functions. 228 | - Improvements: 229 | - Improve layout (allow row/column spacing and min lengths to be specified in em-lengths). 230 | - Automatic bounds computation, even for labels. 231 | - Improve meter (allow multi-qubit gate meters and respect global (per-circuit) gate padding). 232 | - Fixes: 233 | - `lstick`/`rstick` braces broke with Typst 0.7.0. 234 | - `lstick`/`rstick` bounds. 235 | - Documentation 236 | - Add section on creating custom gates. 237 | - Add section on using labels. 238 | - Explain usage of `slice()` and `gategroup()`. 239 | 240 | ### v0.1.0 241 | 242 | Initial Release 243 | 244 | 245 | [guide]: https://github.com/Mc-Zen/quill/releases/download/v0.7.2/quill-guide.pdf 246 | [examples]: https://github.com/Mc-Zen/quill/tree/main/examples 247 | [tidy]: https://github.com/Mc-Zen/tidy 248 | [architecture]: https://github.com/Mc-Zen/quill/blob/main/docs/architecture.md 249 | 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /src/tequila-impl.typ: -------------------------------------------------------------------------------- 1 | #import "gates.typ" 2 | #import "utility.typ": if-auto 3 | 4 | /// Info about one single quantum gate. 5 | #let gate-info( 6 | 7 | /// Qubit or first qubit in the case of a multi-qubit gate. 8 | /// -> int 9 | qubit, 10 | 11 | /// The gate function. 12 | /// -> function 13 | constructor, 14 | 15 | /// Number of qubits. 16 | /// -> int 17 | n: 1, 18 | 19 | /// Additional gates to draw along with the main one given as 20 | /// `(qubit, gate)` tuples. This is used to draw targets or multiple 21 | /// controls. 22 | /// -> array 23 | supplements: (), 24 | 25 | ) = ( 26 | ( 27 | qubit: qubit, 28 | n: n, 29 | supplements: supplements, 30 | constructor: constructor, 31 | ), 32 | ) 33 | 34 | 35 | 36 | #let construct-single-qubit-gate( 37 | 38 | /// One or more qubits. Named arguments are disallowed. 39 | /// -> int | array 40 | qubit, 41 | 42 | /// Gate function. 43 | /// -> function 44 | gate 45 | 46 | ) = { 47 | 48 | if type(qubit) == arguments { 49 | if qubit.named().len() != 0 { 50 | assert(false, message: "Unexpected argument `" + qubit.named().keys().first() + "`") 51 | } 52 | qubit = qubit.pos() 53 | } 54 | if type(qubit) == int { 55 | qubit = (qubit,) 56 | } 57 | 58 | if qubit.len() == 1 { qubit = qubit.first() } 59 | if type(qubit) == int { return gate-info(qubit, gate) } 60 | qubit.map(qubit => gate-info(qubit, gate)) 61 | } 62 | 63 | 64 | /// Generates a two-qubit gate with two qubits connected by a wire. 65 | #let construct-two-qubit-gate( 66 | 67 | /// Control qubit(s). 68 | /// -> int | array 69 | qubit1, 70 | 71 | /// Target qubit(s). 72 | /// -> int | array 73 | qubit2, 74 | 75 | /// Gate to put at the control qubit. This gate needs to take a 76 | /// single positional argument: the relative target number. 77 | /// -> function 78 | gate1, 79 | 80 | /// Gate to put at the target qubit. 81 | /// -> function 82 | gate2 83 | 84 | ) = { 85 | if type(qubit1) == int and type(qubit2) == int { 86 | assert.ne(qubit2, qubit1, message: "Target and control qubit cannot be the same") 87 | return gate-info( 88 | qubit1, 89 | gate1.with(qubit2 - qubit1), 90 | n: qubit2 - qubit1 + 1, 91 | supplements: ((qubit2, gate2),) 92 | ) 93 | } 94 | if type(qubit1) == int { qubit1 = (qubit1,) } 95 | if type(qubit2) == int { qubit2 = (qubit2,) } 96 | 97 | range(calc.max(qubit1.len(), qubit2.len())).map(i => { 98 | let c = qubit1.at(i, default: qubit1.last()) 99 | let t = qubit2.at(i, default: qubit2.last()) 100 | assert.ne(t, c, message: "Target and control qubit cannot be the same") 101 | gate-info( 102 | c, 103 | gate1.with(t - c), 104 | n: t - c + 1, 105 | supplements: ((t, gate2),) 106 | ) 107 | }) 108 | } 109 | 110 | 111 | 112 | /// Creates a gate with multiple controls. 113 | #let construct-multi-controlled-gate( 114 | 115 | /// Control qubits. 116 | /// -> array 117 | controls, 118 | 119 | /// Target qubit. 120 | /// -> int 121 | qubit, 122 | 123 | /// Gate to put at the target. 124 | /// -> function 125 | gate, 126 | 127 | /// Additional arguments to apply to the `ctrl` gate. 128 | /// -> any 129 | ..args 130 | 131 | ) = { 132 | let ctrl = gates.ctrl.with(..args) 133 | 134 | controls = controls.map(c => if type(c) == int { (c,) } else { c }) 135 | if type(qubit) == int { qubit = (qubit,) } 136 | 137 | range(calc.max(qubit.len(), ..controls.map(array.len))).map(i => { 138 | let target = qubit.at(i, default: qubit.last()) 139 | let cs = controls.map(c => c.at(i, default: c.last())) 140 | 141 | assert((cs + (target,)).dedup().len() == cs.len() + 1, message: "Target and control qubits need to be all different (were " + str(target) + " and " + repr(cs) + ")") 142 | 143 | 144 | 145 | 146 | let (first, ..rest) = (cs + (target,)).sorted() 147 | let n = rest.last() - first 148 | 149 | if first == target { 150 | let (..rest, last) = rest 151 | gate-info( 152 | target, gate, 153 | n: n + 1, 154 | supplements: rest.map(q => (q, ctrl.with(0))) + ((last, ctrl.with(-n)),) 155 | ) 156 | } else { 157 | 158 | gate-info( 159 | first, ctrl.with(n), 160 | n: n + 1, 161 | supplements: rest.map(q => 162 | (q, if q == target { gate } else { ctrl.with(0) }) 163 | ) 164 | ) 165 | } 166 | 167 | }) 168 | } 169 | 170 | 171 | #let gate(qubit, ..args) = gate-info(qubit, gates.gate.with(..args)) 172 | 173 | #let mqgate(qubit, n: 1, ..args) = { 174 | gate-info(qubit, n: n, gates.mqgate.with(..args, n: n)) 175 | } 176 | 177 | #let barrier(start: 0, end: auto) = gate-info( 178 | start, 179 | n: if end == auto { auto } else { end - start }, 180 | barrier 181 | ) 182 | 183 | #let x(qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($X$, ..args)) 184 | #let y(qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($Y$, ..args)) 185 | #let z(qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($Z$, ..args)) 186 | 187 | #let h(qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($H$, ..args)) 188 | #let s(qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($S$, ..args)) 189 | #let sdg(qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($S^dagger$, ..args)) 190 | #let sx(qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($sqrt(X)$, ..args)) 191 | #let sxdg(qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($sqrt(X)^dagger$, ..args)) 192 | #let t(qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($T$, ..args)) 193 | #let tdg(qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($T^dagger$, ..args)) 194 | #let p(theta, qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($P (#theta)$, ..args)) 195 | 196 | #let rx(theta, qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($R_x (#theta)$, ..args)) 197 | #let ry(theta, qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($R_y (#theta)$, ..args)) 198 | #let rz(theta, qubit, ..args) = construct-single-qubit-gate(qubit, gates.gate.with($R_z (#theta)$, ..args)) 199 | 200 | #let u(theta, phi, lambda, qubit, ..args) = construct-single-qubit-gate( 201 | qubit, gates.gate.with($U (#theta, #phi, #lambda)$, ..args) 202 | ) 203 | 204 | #let meter(qubit, ..args) = construct-single-qubit-gate(qubit, gates.meter.with(..args)) 205 | 206 | #let measure(label: none, ..args) = { 207 | let qubits = args.pos() 208 | assert(qubits.len() > 0, message: "Missing argument `qubit`") 209 | 210 | if type(label) == content { 211 | label = (content: label, pos: bottom) 212 | } 213 | 214 | if qubits.len() == 1 { 215 | construct-single-qubit-gate(qubits.first(), gates.meter.with(..args.named())) 216 | } else { 217 | assert(qubits.len() == 2, message: "Expected a qubit and a classical, got more than three positional arguments") 218 | construct-two-qubit-gate( 219 | qubits.last(), 220 | qubits.first(), 221 | gates.ctrl.with(open: true, wire-count: 2, label: label), 222 | gates.meter.with(..args.named()) 223 | ) 224 | } 225 | } 226 | 227 | 228 | #let cx(control, target, ..args) = construct-two-qubit-gate( 229 | control, target, gates.ctrl.with(..args), gates.targ 230 | ) 231 | #let cz(control, target, ..args) = construct-two-qubit-gate( 232 | control, target, gates.ctrl.with(..args), gates.ctrl.with(0) 233 | ) 234 | #let swap(control, target, ..args) = construct-two-qubit-gate( 235 | control, target, gates.swap.with(..args), gates.swap.with(0) 236 | ) 237 | #let ccx(control1, control2, target, ..args) = construct-multi-controlled-gate( 238 | (control1, control2), target, gates.targ, ..args 239 | ) 240 | #let ccz(control1, control2, target, ..args) = construct-multi-controlled-gate( 241 | (control1, control2), target, gates.ctrl.with(0), ..args 242 | ) 243 | #let cca(control1, control2, target, content, ..args) = construct-multi-controlled-gate( 244 | (control1, control2), target, gates.gate.with(content), ..args 245 | ) 246 | #let cccx(control1, control2, control3, target, ..args) = construct-multi-controlled-gate( 247 | (control1, control2, control3), target, gates.targ, ..args 248 | ) 249 | 250 | 251 | #let ca(control, target, ..args) = construct-two-qubit-gate( 252 | control, target, gates.ctrl, gates.gate.with(..args) 253 | ) 254 | 255 | #let multi-controlled-gate(controls, target, gate, ..args) = construct-multi-controlled-gate( 256 | controls, target, gate, ..args 257 | ) 258 | 259 | 260 | /// Constructs a circuit from operation instructions. 261 | /// 262 | /// ```example 263 | /// #import tequila as tq 264 | /// 265 | /// #quantum-circuit( 266 | /// ..tq.build( 267 | /// tq.h(0), 268 | /// tq.cx(0, 1), 269 | /// tq.sdg(1) 270 | /// ) 271 | /// ) 272 | /// ``` 273 | #let build( 274 | 275 | /// Number of qubits. Can be inferred automatically. 276 | /// -> auto | int 277 | n: auto, 278 | 279 | /// Determines at which column the subcircuit will be put in the circuit. 280 | /// -> int 281 | x: 1, 282 | 283 | /// Determines at which row the subcircuit will be put in the circuit. 284 | /// -> int 285 | y: 0, 286 | 287 | /// If set to `true`, the a last column of outgoing wires will be added. 288 | /// -> bool 289 | append-wire: true, 290 | 291 | /// Sequence of instructions. 292 | /// -> any 293 | ..children 294 | 295 | ) = { 296 | let operations = children.pos().flatten().filter(x => x != none) 297 | 298 | let num-qubits = n 299 | if num-qubits == auto { 300 | num-qubits = calc.max(..operations.map(x => x.qubit + calc.max(0, if-auto(x.n, 1) - 1))) + 1 301 | } 302 | 303 | let tracks = ((),) * num-qubits 304 | 305 | // now we doin some Tetris 306 | for op in operations { 307 | let start = op.qubit 308 | let end = start + if-auto(op.n, 2) - 1 309 | 310 | assert(start >= 0 and start < num-qubits, message: "The qubit `" + str(start) + "` is out of range. Leave `n` at `auto` if you want to automatically resize the circuit. ") 311 | assert(end >= 0 and end < num-qubits, message: "The qubit `" + str(end) + "` is out of range. Leave `n` at `auto` if you want to automatically resize the circuit. " + repr((start, end, num-qubits, op))) 312 | 313 | // Special case: barriers 314 | let (q1, q2) = (start, end).sorted() 315 | if op.constructor == barrier { 316 | if op.n == auto { end = num-qubits - 1} 317 | (q1, q2) = (start, end) 318 | } 319 | 320 | 321 | // Find how "high" the tracks in interval [q1, q2] are stacked so far. 322 | let max-track-len = calc.max(..tracks.slice(q1, q2 + 1).map(array.len)) 323 | let h = (start,) + op.supplements.map(supplement => supplement.first()) 324 | 325 | // Even up the "height" of the tracks in interval [q1, q2]. 326 | for q in range(q1, q2 + 1) { 327 | let dif = max-track-len - tracks.at(q).len() 328 | if op.constructor != barrier and q not in h { 329 | dif += 1 330 | } 331 | tracks.at(q) += (1,) * dif 332 | } 333 | 334 | // Place gate and supplementary gates. 335 | if op.constructor != barrier { 336 | tracks.at(start).push((op.constructor)(x: x + tracks.at(start).len(), y: y + start)) 337 | for (qubit, supplement) in op.supplements { 338 | tracks.at(qubit).push((supplement)(x: x + tracks.at(end).len(), y: y + qubit)) 339 | } 340 | } 341 | } 342 | 343 | // Fill up all tracks 344 | let max-track-len = calc.max(..tracks.map(array.len)) + 1 345 | for q in range(tracks.len()) { 346 | tracks.at(q) += (1,) * (max-track-len - tracks.at(q).len()) 347 | } 348 | 349 | let num-cols = x + calc.max(..tracks.map(array.len)) - 2 350 | if append-wire { num-cols += 1 } 351 | 352 | // A special placeholder guarantees that 353 | // - the last wire is shown even if there are no gates on it 354 | // - all wires go to the last column correctly. 355 | let placeholder = gates.gate( 356 | none, 357 | x: num-cols, y: y + num-qubits - 1, 358 | data: "placeholder", box: false, floating: true, 359 | size-hint: (it, i) => (width: 0pt, height: 0pt) 360 | ) 361 | 362 | (placeholder,) + tracks.join().filter(x => x != 1) 363 | } 364 | 365 | 366 | 367 | /// Constructs a graph state preparation circuit. 368 | /// 369 | /// ```example 370 | /// #import tequila as tq 371 | /// 372 | /// #quantum-circuit( 373 | /// ..tq.graph-state( 374 | /// (1, 2), (2,0) 375 | /// ) 376 | /// ) 377 | /// ``` 378 | #let graph-state( 379 | 380 | /// Number of qubits. Can be inferred automatically. 381 | /// -> auto | int 382 | n: auto, 383 | 384 | /// Determines at which column the subcircuit will be put in the circuit. 385 | /// -> int 386 | x: 1, 387 | 388 | /// Determines at which row the subcircuit will be put in the circuit. 389 | /// -> int 390 | y: 0, 391 | 392 | /// If set to `true`, the circuit will be inverted, i.e., a circuit for 393 | /// "uncomputing" the corresponding graph state. 394 | /// -> bool 395 | invert: false, 396 | 397 | /// -> array 398 | ..edges 399 | 400 | ) = { 401 | edges = edges.pos() 402 | let max-qubit = 0 403 | 404 | for edge in edges { 405 | assert(type(edge) == array, message: "Edges need to be pairs of vertices") 406 | assert(edge.len() == 2, message: "Every edge needs to have exactly two vertices") 407 | max-qubit = calc.max(max-qubit, ..edge) 408 | } 409 | 410 | let num-qubits = max-qubit + 1 411 | if n != auto { 412 | num-qubits = n 413 | assert(n > max-qubit, message: "") 414 | } 415 | 416 | let gates = ( 417 | h(range(num-qubits)), 418 | barrier(), 419 | edges.map(edge => cz(..edge)) 420 | ) 421 | 422 | if invert { 423 | gates = gates.rev() 424 | } 425 | 426 | build( 427 | x: x, y: y, 428 | ..gates 429 | ) 430 | } 431 | 432 | 433 | /// Template for the quantum fourier transform (QFT). 434 | /// ```example 435 | /// #import tequila as tq 436 | /// 437 | /// #quantum-circuit( 438 | /// ..tq.qft(2) 439 | /// ) 440 | /// ``` 441 | #let qft( 442 | 443 | /// Number of qubits. 444 | /// -> auto | int 445 | n, 446 | 447 | /// Determines at which column the QFT routine will be placed in the circuit. 448 | /// -> int 449 | x: 1, 450 | 451 | /// Determines at which row the QFT routine will be placed in the circuit. 452 | /// -> int 453 | y: 0 454 | 455 | ) = { 456 | let gates = () 457 | 458 | for i in range(n - 1) { 459 | gates.push(h(i)) 460 | for j in range(2, n - i + 1) { 461 | gates.push(ca(i + j - 1, i, $R_#j$)) 462 | } 463 | gates.push(barrier()) 464 | } 465 | 466 | gates.push(h(n - 1)) 467 | build(n: n, x: x, y: y, ..gates) 468 | } 469 | -------------------------------------------------------------------------------- /src/gates.typ: -------------------------------------------------------------------------------- 1 | #import "layout.typ" 2 | #import "process-args.typ" 3 | #import "draw-functions.typ" 4 | 5 | 6 | 7 | 8 | /// Creates a quantum (or classical) gate. For special gates, many dedicated 9 | /// gate commands like @ctrl, @swap, and more exist. 10 | /// 11 | /// ```example 12 | /// #quantum-circuit( 13 | /// 1, gate($H$), 1 14 | /// ) 15 | /// ``` 16 | /// 17 | /// Note, that most of the parameters listed here are mostly used for derived gate 18 | /// functions and do not need to be touched in all but very few cases. 19 | #let gate( 20 | 21 | /// The body of the gate (may be none for special gates like @ctrl). 22 | /// -> content 23 | body, 24 | 25 | /// The column to put the gate in. 26 | /// -> auto | int 27 | x: auto, 28 | 29 | /// The row to put the gate in. 30 | /// -> auto | int 31 | y: auto, 32 | 33 | /// How to fill the gate box. 34 | /// -> auto | none | color | gradient | tiling 35 | fill: auto, 36 | 37 | /// How to stroke the gate box. 38 | /// -> auto | stroke 39 | stroke: auto, 40 | 41 | /// Border radius for the gate box. Allows the same values as the built-in 42 | /// `rect()` function. 43 | /// -> length | dictionary 44 | radius: 0pt, 45 | 46 | /// Specifies the width of the gate. 47 | /// -> auto | length 48 | width: auto, 49 | 50 | /// Whether this is a boxed gate (determines whether the outgoing wire 51 | /// will be drawn all through the gate (`box: false`) or not). 52 | /// -> bool 53 | box: true, 54 | 55 | /// Whether the content for this gate will be shown floating (i.e. no 56 | /// width is reserved for layout). 57 | /// -> bool 58 | floating: false, 59 | 60 | /// Information for multi-qubit and controlled gates (see @mqgate). 61 | /// -> none | dictionary 62 | multi: none, 63 | 64 | /// Size hint function. This function should return a dictionary containing 65 | /// the keys `width` and `height`. The result is used to determine the gates 66 | /// position and cell sizes of the grid. Signature: `(gate, draw-params) => {}`. 67 | /// -> function 68 | size-hint: layout.default-size-hint, 69 | 70 | /// Drawing function that produces the displayed content. Signature: 71 | /// `(gate, draw-params) => {}`. 72 | /// -> function 73 | draw-function: draw-functions.draw-boxed-gate, 74 | 75 | /// Optional additional gate data. This can for example be a dictionary storing 76 | /// extra information that may be used for instance in a custom `draw-function`. 77 | /// -> any 78 | data: none, 79 | 80 | /// One or more labels to add to the gate. Usually, a label consists of a 81 | /// dictionary with entries for the keys `content` (the label content), `pos` 82 | /// (2d alignment specifying the position of the label) and optionally `dx` 83 | /// and/or `dy` (lengths, ratios or relative lengths). If only a single label 84 | /// is to be added, a plain content or string value can be passed which is then 85 | /// placed at the default position. 86 | /// -> none | array | str | content | dictionary 87 | label: none 88 | 89 | ) = ( 90 | content: body, 91 | x: x, 92 | y: y, 93 | fill: fill, 94 | stroke: stroke, 95 | radius: radius, 96 | width: width, 97 | box: box, 98 | floating: floating, 99 | multi: multi, 100 | size-hint: size-hint, 101 | draw-function: draw-function, 102 | gate-type: "", 103 | data: data, 104 | labels: process-args.process-label-arg(label, default-pos: top) 105 | ) 106 | 107 | 108 | 109 | /// Base command for creating multi-qubit or controlled gates. See also @ctrl and @swap. 110 | /// ```example 111 | /// #quantum-circuit( 112 | /// 1, mqgate($E$, n: 2, target: 2), 1, 113 | /// [\ ], [\ ] 114 | /// ) 115 | /// ``` 116 | #let mqgate( 117 | 118 | /// The body of the gate. 119 | /// -> content 120 | body, 121 | 122 | /// The number of wires that the multi-qubit gate spans. 123 | /// -> int 124 | n: 1, 125 | 126 | /// Specifies a control wire to be drawn to a target wire relative to the wire 127 | /// that the is placed on. 128 | /// -> none | int 129 | target: none, 130 | 131 | /// Which wires to allow to pass through the gate. 132 | /// ```example 133 | /// #quantum-circuit( 134 | /// wires: 4, 135 | /// 1, mqgate( 136 | /// n: 4, 137 | /// fill: none, 138 | /// pass-through: (2,), 139 | /// $E$ 140 | /// ), 1 141 | /// ) 142 | /// ``` 143 | /// Note that it is necessary to set `fill` to none to prevent the gate from 144 | /// drawing over the wires. 145 | /// 146 | /// The indices are given relative to the first wire of the gate. The (relative) first and last wires cannot pass through the gate. 147 | pass-through: (), 148 | 149 | 150 | /// The column to put the gate in. 151 | /// -> auto | int 152 | x: auto, 153 | 154 | /// The row to put the gate in. If $n>1$, this sets the _first_ row of the 155 | /// multi-qubit gate. 156 | /// -> auto | int 157 | y: auto, 158 | 159 | /// How to fill the gate box 160 | /// -> auto | none | color | gradient | tiling 161 | fill: auto, 162 | 163 | /// How to stroke the gate box. 164 | /// -> auto | stroke 165 | stroke: auto, 166 | 167 | /// Border radius for the gate box. Allows the same values as the built-in 168 | /// `rect()` function. 169 | /// -> length | dictionary 170 | radius: 0pt, 171 | 172 | /// Specifies the width of the gate. 173 | /// -> auto | length 174 | width: auto, 175 | 176 | /// Whether this is a boxed gate (determines whether the outgoing wire will 177 | /// be drawn all through the gate (`box: false`) or not). 178 | /// -> bool 179 | box: true, 180 | 181 | /// The number of parallel control wires to draw. 182 | /// -> int 183 | wire-count: 1, 184 | 185 | /// How to stroke the control wire. 186 | /// -> auto | stroke 187 | wire-stroke: auto, 188 | 189 | /// You can put labels inside the gate to label the input wires with this 190 | /// argument. It accepts a list of labels, each of which has to be a 191 | /// dictionary with the keys `qubit` (denoting the qubit to label, starting 192 | /// at 0) and `content` (containing the label content). Optionally, providing 193 | /// a value for the key `n` allows for labelling multiple qubits spanning 194 | /// over `n` wires. These are then grouped by a brace. 195 | /// -> none | array 196 | inputs: none, 197 | 198 | /// Same as @mqgate.inputs but for gate outputs. 199 | /// -> none | array 200 | outputs: none, 201 | 202 | /// How much to extent the gate beyond the first and last wire, default is 203 | /// to make it align with an X gate (so [size of x gate] / 2). 204 | /// -> auto | length 205 | extent: auto, 206 | 207 | /// A single-qubit gate affects the height of the row it is being put on. 208 | /// For multi-qubit gate there are different possible behaviours: 209 | /// - `false`: The hight is affected on only the first and last wire. 210 | /// - `true`: The height of all wires is affected. 211 | /// - `none`: The height on no wire is affected. 212 | /// -> none | bool 213 | size-all-wires: false, 214 | 215 | /// See @gate.draw-function 216 | /// -> function 217 | draw-function: draw-functions.draw-boxed-multigate, 218 | 219 | /// One or more labels to add to the gate. See @gate.label. 220 | /// -> none | array | str | content | dictionary 221 | label: none, 222 | 223 | /// One or more labels to add to the control wire. Works analogous to 224 | /// `labels` but with default positioning to the right of the wire. 225 | /// -> none | array | str | content | dictionary 226 | wire-label: none, 227 | 228 | /// Optional additional gate data. This can for example be a dictionary 229 | /// storing extra information that may be used for instance in a custom 230 | /// `draw-function`. 231 | /// -> any 232 | data: none, 233 | 234 | ) = gate( 235 | body, 236 | x: x, 237 | y: y, 238 | fill: fill, 239 | stroke: stroke, 240 | box: box, 241 | width: width, 242 | radius: radius, 243 | draw-function: draw-function, 244 | multi: ( 245 | target: target, 246 | num-qubits: n, 247 | wire-count: wire-count, 248 | wire-stroke: wire-stroke, 249 | label: label, 250 | extent: extent, 251 | size-all-wires: size-all-wires, 252 | inputs: inputs, 253 | outputs: outputs, 254 | wire-label: process-args.process-label-arg(wire-label, default-pos: right), 255 | pass-through: pass-through 256 | ), 257 | label: label, 258 | data: data, 259 | ) 260 | 261 | 262 | 263 | // SPECIAL GATES 264 | 265 | /// Draws a meter representing a measurement. Meters can stand alone, 266 | /// ```example 267 | /// #quantum-circuit( 268 | /// 1, meter(), 1 269 | /// ) 270 | /// ``` 271 | /// connect to another wire and have a label, 272 | /// ```example 273 | /// #quantum-circuit( 274 | /// 1, meter(target: 1, label: $X$), 1, [\ ] 275 | /// ) 276 | /// ``` 277 | /// and can span multiple qubits. 278 | /// ```example 279 | /// #quantum-circuit( 280 | /// 1, meter(n: 2), 1, [\ ] 281 | /// ) 282 | /// ``` 283 | #let meter( 284 | 285 | /// Optional body to use for the meter. Here you may use the `meter-symbol` and 286 | /// combine it with additional measurement information. 287 | /// ```example 288 | /// #quantum-circuit( 289 | /// 1, meter[$P_1$ #meter-symbol], 1 290 | /// ) 291 | /// ``` 292 | /// Superscripts can be used to embed a small basis inset. 293 | /// 294 | /// ```example 295 | /// #let basis-meter(basis) = meter[ 296 | /// #place(super(baseline: -0.4em, basis)) 297 | /// #meter-symbol 298 | /// ] 299 | /// 300 | /// #quantum-circuit( 301 | /// 1, basis-meter[X], 1 302 | /// ) 303 | /// ``` 304 | /// 305 | /// -> any 306 | ..body, 307 | 308 | /// If given, draw a control wire to the given target qubit the specified 309 | /// number of wires up or down. 310 | /// -> none | int 311 | target: none, 312 | 313 | /// The number of qubits that the meter spans. 314 | /// -> int 315 | n: 1, 316 | 317 | x: auto, 318 | 319 | y: auto, 320 | 321 | /// Wire count for the optional control wire, see @meter.target. 322 | /// -> int 323 | wire-count: 2, 324 | 325 | /// How to stroke the optional control wire. 326 | /// -> auto | stroke 327 | wire-stroke: auto, 328 | 329 | /// One or more labels to add to the gate. See @gate.label. 330 | /// -> none | array | str | content | dictionary 331 | label: none, 332 | 333 | fill: auto, 334 | 335 | radius: 0pt 336 | 337 | ) = { 338 | process-args.assert-no-named(body, fn: "meter") 339 | body = body.pos() 340 | if body.len() == 1 { 341 | body = body.first() 342 | } else if body.len() == 0 { 343 | body = auto 344 | } else { 345 | assert(false, message: "Unexpected positional argument `" + repr(body.at(1)) + "` encountered at meter") 346 | } 347 | label = process-args.process-label-arg(label, default-dy: 0.5em, default-pos: top) 348 | if target == none and n == 1 { 349 | gate(body, x: x, y: y, fill: fill, radius: radius, draw-function: draw-functions.draw-meter, label: label) 350 | } else { 351 | mqgate(body, x: x, y: y, n: n, target: target, fill: fill, radius: radius, box: true, wire-count: wire-count, wire-stroke: wire-stroke, draw-function: draw-functions.draw-meter, label: label) 352 | } 353 | } 354 | 355 | 356 | 357 | /// Creates a visualized permutation gate which maps the qubits $q_k, q_(k+1), ... $ 358 | /// to the qubits $q_(p(k)), q_(p(k+1)), ...$ when placed on the qubit $k$. The 359 | /// permutation map is given by the `qubits` argument. Note, that qubit indices 360 | /// start with 0. 361 | /// 362 | /// 363 | /// 364 | /// ```example 365 | /// #quantum-circuit( 366 | /// 1, permute(1, 0), 1, [\ ] 367 | /// ) 368 | /// ``` 369 | /// The amount of wire bending can be configured: 370 | /// ```example 371 | /// #quantum-circuit( 372 | /// 1, permute(2,0,1, bend: 0%), 373 | /// 1, [\ ], [\ ] 374 | /// ) 375 | /// ``` 376 | /// Note also, that the wiring is not very sophisticated and will probably look best for 377 | /// relatively simple permutations. Furthermore, it only works with quantum wires. 378 | #let permute( 379 | 380 | /// Qubit permutation. 381 | /// -> int 382 | ..qubits, 383 | 384 | /// Width of the permutation gate. 385 | /// -> length 386 | width: 30pt, 387 | 388 | /// How much to bend the wires. With `0%`, the wires are straight. 389 | /// -> ratio 390 | bend: 100%, 391 | 392 | /// Overlapping wires are separated by drawing a thicker line below. With 393 | /// this option, this line can be customized in color or thickness. 394 | /// -> auto | none | length | color | stroke 395 | separation: auto, 396 | 397 | /// The number of wires to display on each permuted wire. This can be used to 398 | /// create a permutation of classical or even mixed wires. 399 | /// -> int | array 400 | wire-count: 1, 401 | 402 | /// How to stroke each permuted wire. Wires left at `auto` will inherit the general wire style. 403 | /// -> auto | stroke | array 404 | stroke: auto, 405 | 406 | x: auto, 407 | 408 | y: auto, 409 | 410 | ) = { 411 | if qubits.named().len() != 0 { 412 | assert(false, message: "Unexpected named argument `" + qubits.named().keys().first() + "` in function `permute()`") 413 | } 414 | qubits = qubits.pos() 415 | if type(wire-count) == array { 416 | assert( 417 | wire-count.len() == qubits.len(), 418 | message: "The number of wire-counts and permuted qubits must match" 419 | ) 420 | } else { 421 | wire-count = (wire-count,) * qubits.len() 422 | } 423 | if type(stroke) == array { 424 | assert( 425 | stroke.len() == qubits.len(), 426 | message: "The number of strokes and permuted qubits must match" 427 | ) 428 | } else { 429 | stroke = (stroke,) * qubits.len() 430 | } 431 | mqgate(none, x: x, y: y, n: qubits.len(), width: width, draw-function: draw-functions.draw-permutation-gate, data: (qubits: qubits, extent: 2pt, separation: separation, bend: bend, wire-count: wire-count, stroke: stroke)) 432 | } 433 | 434 | 435 | 436 | /// Creates an invisible (phantom) gate for reserving space. If `content` 437 | /// is provided, the `height` and `width` parameters are ignored and the gate 438 | /// will take the size it would have if `gate(content)` was called. 439 | /// 440 | /// Instead specifying width and/or height will create a gate with exactly the 441 | /// given size (without padding). 442 | #let phantom( 443 | 444 | /// Content to measure for the phantom gate size. 445 | /// -> content 446 | content: none, 447 | 448 | /// Width of the phantom gate (ignored if `content` is not `none`). 449 | /// -> length 450 | width: 0pt, 451 | 452 | /// Height of the phantom gate (ignored if `content` is not `none`). 453 | /// -> length 454 | height: 0pt, 455 | 456 | x: auto, 457 | 458 | y: auto, 459 | 460 | ) = { 461 | let thecontent = if content != none { box(hide(content)) } else { 462 | box(width: width, height: height) 463 | } 464 | gate(thecontent, x: x, y: y, box: false, fill: none) 465 | } 466 | 467 | 468 | 469 | /// Target element for controlled-$X$ operations. 470 | /// 471 | /// ```example 472 | /// #quantum-circuit( 473 | /// 1, targ(), 1, 474 | /// ) 475 | /// ``` 476 | #let targ( 477 | 478 | /// How many wires up or down the target wire lives. 479 | /// -> int 480 | ..n, 481 | 482 | /// How to fill the target circle. If set to `auto`, the target is 483 | /// filled with the circuits background color. 484 | /// -> none | auto | color | gradient | tiling 485 | fill: none, 486 | 487 | /// Radius of the target symbol. 488 | /// -> length 489 | size: 4.3pt, 490 | 491 | label: none, 492 | 493 | x: auto, 494 | 495 | y: auto, 496 | 497 | /// Wire count for the control wire. 498 | /// -> int 499 | wire-count: 1, 500 | 501 | /// How to stroke the optional control wire. 502 | /// -> auto | stroke 503 | wire-stroke: auto, 504 | 505 | /// One or more labels to add to the control wire. See @mqgate.wire-label. 506 | /// -> none | array | str | content | dictionary 507 | wire-label: none, 508 | 509 | ) = { 510 | process-args.assert-no-named(n, fn: "targ") 511 | n = n.pos() 512 | assert(n.len() <= 1, message: "Unexpected second positional argument for `targ`") 513 | 514 | mqgate( 515 | none, 516 | x: x, 517 | y: y, 518 | target: n.at(0, default: 0), 519 | box: false, 520 | draw-function: draw-functions.draw-targ, 521 | wire-count: wire-count, 522 | wire-stroke: wire-stroke, 523 | fill: if fill == true {auto} else if fill == false {none} else {fill}, 524 | data: (size: size), 525 | label: label, 526 | wire-label: wire-label 527 | ) 528 | } 529 | 530 | 531 | 532 | /// Creates a phase gate shown as a point on the wire together with a label. 533 | /// 534 | /// ```example 535 | /// #quantum-circuit( 536 | /// 1, phase($α$), 1, 537 | /// ) 538 | /// ``` 539 | #let phase( 540 | 541 | /// The angle value to display. 542 | /// -> content 543 | label, 544 | 545 | /// Whether to draw an open dot. 546 | /// -> bool 547 | open: false, 548 | 549 | /// How to fill or stroke the circle if `open: true`. 550 | /// -> none | color | stroke 551 | fill: auto, 552 | 553 | /// The radius of the circle. 554 | /// -> length 555 | size: 2.3pt, 556 | 557 | x: auto, 558 | 559 | y: auto 560 | 561 | ) = gate( 562 | none, 563 | x: x, 564 | y: y, 565 | box: false, 566 | draw-function: (gate, draw-params) => box( 567 | inset: (x: .6em), 568 | draw-functions.draw-ctrl(gate, draw-params) 569 | ), 570 | fill: fill, 571 | data: (open: open, size: size), 572 | label: process-args.process-label-arg(label, default-pos: top + right, default-dx: -.5em) 573 | ) 574 | 575 | 576 | 577 | /// Creates a #smallcaps("swap") operation with another qubit. 578 | /// 579 | /// 580 | /// ```example 581 | /// #quantum-circuit( 582 | /// 1, swap(1), 1, [\ ], 583 | /// 1, swap(), 1 584 | /// ) 585 | /// ``` 586 | #let swap( 587 | 588 | /// How many wires up or down the target wire lives. 589 | /// -> int 590 | ..n, 591 | 592 | wire-count: 1, 593 | 594 | /// The size of the target symbol. 595 | /// -> length. 596 | size: 7pt, 597 | 598 | label: none, 599 | 600 | /// One or more labels to add to the control wire. See @mqgate.wire-label. 601 | /// -> none | array | str | content | dictionary 602 | wire-label: none, 603 | 604 | /// How to stroke the control wire. 605 | /// -> auto | stroke 606 | wire-stroke: auto, 607 | 608 | x: auto, 609 | 610 | y: auto 611 | 612 | ) = { 613 | process-args.assert-no-named(n, fn: "swap") 614 | n = n.pos() 615 | assert(n.len() <= 1, message: "Unexpected second positional argument for `swap`") 616 | 617 | mqgate( 618 | none, 619 | x: x, 620 | y: y, 621 | target: n.at(0, default: 0), 622 | box: false, 623 | draw-function: draw-functions.draw-swap, 624 | wire-count: wire-count, 625 | wire-stroke: wire-stroke, 626 | data: (size: size), 627 | label: label, 628 | wire-label: wire-label 629 | ) 630 | } 631 | 632 | 633 | 634 | /// Creates a control with a vertical wire to another qubit. 635 | /// 636 | /// ```example 637 | /// #quantum-circuit( 638 | /// 1, ctrl(1), 1, [\ ], 639 | /// 1, ctrl(), 1 640 | /// ) 641 | /// ``` 642 | #let ctrl( 643 | 644 | /// How many wires up or down the target wire lives. 645 | /// -> int 646 | ..n, 647 | 648 | /// Wire count for the control wire. 649 | /// -> int 650 | wire-count: 1, 651 | 652 | /// How to stroke the control wire. 653 | /// -> auto | stroke 654 | wire-stroke: auto, 655 | 656 | /// Whether to draw an open dot. 657 | /// -> bool 658 | open: false, 659 | 660 | /// How to fill or stroke the circle if `open: true`. 661 | /// none | color 662 | fill: auto, 663 | 664 | /// The radius of the control circle. 665 | /// -> length 666 | size: 2.3pt, 667 | 668 | /// Whether to show the control dot at all. Set this to false to obtain a 669 | /// vertical wire with no dots at all. 670 | /// -> bool 671 | show-dot: true, 672 | 673 | /// One or more labels to add to the control wire. See @mqgate.wire-label. 674 | /// -> none | array | str | content | dictionary 675 | wire-label: none, 676 | 677 | label: none, 678 | 679 | x: auto, 680 | 681 | y: auto 682 | 683 | ) = { 684 | process-args.assert-no-named(n, fn: "ctrl") 685 | n = n.pos() 686 | assert(n.len() <= 1, message: "Unexpected second positional argument for `ctrl`") 687 | 688 | mqgate( 689 | none, 690 | x: x, 691 | y: y, 692 | target: n.at(0, default: 0), 693 | box: false, 694 | draw-function: draw-functions.draw-ctrl, 695 | wire-count: wire-count, 696 | fill: fill, 697 | data: (open: open, size: size, show-dot: show-dot), 698 | label: label, 699 | wire-label: wire-label, 700 | wire-stroke: wire-stroke 701 | ) 702 | } 703 | -------------------------------------------------------------------------------- /src/quantum-circuit.typ: -------------------------------------------------------------------------------- 1 | #import "utility.typ" as utility: if-auto 2 | #import "verifications.typ" 3 | #import "length-helpers.typ" 4 | #import "decorations.typ": * 5 | 6 | #let signum(x) = if x >= 0. { 1. } else { -1. } 7 | 8 | 9 | 10 | /// Create a quantum circuit diagram. Children may be 11 | /// - gates created by one of the many gate commands (@gate, @mqgate, @meter, ...), 12 | /// - `[\ ]` for creating a new wire/row, 13 | /// - commands like @setwire, @slice or @gategroup, 14 | /// - integers for creating cells filled with the current wire setting, 15 | /// - lengths for creating space between rows or columns, 16 | /// - plain content or strings to be placed on the wire, and 17 | /// - @lstick, @midstick or @rstick for placement next to the wire. 18 | #let quantum-circuit( 19 | 20 | /// Style for drawing the circuit wires. This can take anything 21 | /// that is valid for the stroke of the built-in `line()` function. 22 | /// -> stroke 23 | wire: .7pt + black, 24 | 25 | /// Spacing between rows. 26 | /// -> length 27 | row-spacing: 12pt, 28 | 29 | /// Spacing between columns. 30 | /// -> length 31 | column-spacing: 12pt, 32 | 33 | /// Minimum height of a row (e.g., when no gates are given). 34 | /// -> length 35 | min-row-height: 10pt, 36 | 37 | /// Minimum width of a column. 38 | /// -> length 39 | min-column-width: 0pt, 40 | 41 | /// General padding setting including the inset for gate boxes and 42 | /// the distance of @lstick and co. to the wire. 43 | /// -> length 44 | gate-padding: .4em, 45 | 46 | /// If true, then all rows will have the same height and the wires will 47 | /// have equal distances orienting on the highest row. 48 | /// -> bool 49 | equal-row-heights: false, 50 | 51 | /// Foreground color, default for strokes, text, controls etc. If you want 52 | /// to have dark-themed circuits, set this to white for instance and 53 | /// update `wire` and `fill` accordingly. 54 | /// -> color 55 | color: black, 56 | 57 | /// Default fill color for gates. 58 | /// -> color 59 | fill: white, 60 | 61 | /// Default font size for text in the circuit. 62 | /// -> length 63 | font-size: 10pt, 64 | 65 | /// Total scale factor applied to the entire circuit without changing proportions. 66 | /// -> ratio 67 | scale: 100%, 68 | 69 | /// Set the baseline for the circuit. If a content or a string is given, the 70 | /// baseline will be adjusted automatically to align with the center of it. 71 | /// One useful application is `"="` so the circuit aligns with the equals symbol. 72 | /// -> length | content | str 73 | baseline: 0pt, 74 | 75 | /// Padding for the circuit (e.g., to accommodate for annotations) in form of 76 | /// a dictionary with possible keys `left`, `right`, `top` and `bottom`. Not 77 | /// all of those need to be specified. 78 | /// 79 | /// This setting basically just changes the size of the bounding box for the 80 | /// circuit and can be used to increase it when labels or annotations extend 81 | /// beyond the actual circuit. 82 | /// -> length | dictionary 83 | circuit-padding: .4em, 84 | 85 | /// How many wires the circuit has or an array of wire counts, e.g., 86 | /// `(1, 1, 2)` for two quantum wires and one classical wire. 87 | /// -> auto | int | array 88 | wires: auto, 89 | 90 | /// Whether to automatically fill up all wires until the end. 91 | /// -> bool 92 | fill-wires: true, 93 | 94 | /// Items, gates and circuit commands (see description). 95 | /// -> any 96 | ..children 97 | 98 | ) = { 99 | if children.pos().len() == 0 { return } 100 | if children.named().len() > 0 { 101 | panic("Unexpected named argument '" + children.named().keys().at(0) + "' for quantum-circuit()") 102 | } 103 | if type(wire) == color { wire += .7pt } 104 | if type(wire) == length { wire += black } 105 | 106 | set text(wire.paint, size: font-size) 107 | set math.equation(numbering: none) 108 | 109 | context { 110 | 111 | // Parameter object to pass to draw-function containing current style info 112 | let draw-params = ( 113 | wire: wire, 114 | padding: measure(line(length: gate-padding)).width, 115 | background: fill, 116 | color: color, 117 | x-gate-size: none, 118 | multi: (wire-distance: 0pt) 119 | ) 120 | 121 | draw-params.x-gate-size = layout.default-size-hint(gate($X$), draw-params) 122 | 123 | let items = children.pos().map( x => { 124 | if type(x) in (content, str) and x != [\ ] { return gate(x) } 125 | return x 126 | }) 127 | 128 | /////////// First part: Layout (and spacing) /////////// 129 | 130 | let column-spacing = column-spacing.to-absolute() 131 | let row-spacing = row-spacing.to-absolute() 132 | let min-row-height = min-row-height.to-absolute() 133 | let min-column-width = min-column-width.to-absolute() 134 | 135 | // All these arrays are gonna be filled up in the loop over `items` 136 | let matrix = ((),) 137 | let row-gutter = (0pt,) 138 | let single-qubit-gates = () 139 | let multi-qubit-gates = () 140 | let meta-instructions = () 141 | 142 | let auto-cell = (empty: true, size: (width: 0pt, height: 0pt), gutter: 0pt) 143 | 144 | let default-wire-style = ( 145 | count: 1, 146 | distance: 1pt, 147 | stroke: wire 148 | ) 149 | let (row, col) = (0, 0) 150 | let prev-col = 0 151 | let wire-ended = false 152 | 153 | // Wire styles and wire pieces per row. 154 | let wire-instructions = ( 155 | (default-wire-style, ), 156 | ) 157 | 158 | if type(wires) == array { 159 | wire-instructions = wires.map(count => (default-wire-style + (count: count),)) 160 | } 161 | 162 | for item in items { 163 | if item == [\ ] { 164 | if fill-wires { 165 | wire-instructions.at(row).push((prev-col, -1)) 166 | } 167 | row += 1; col = 0; prev-col = 0 168 | if row >= wire-instructions.len() { 169 | wire-instructions.push((default-wire-style,)) 170 | } 171 | 172 | if row >= matrix.len() { 173 | matrix.push(()) 174 | row-gutter.push(0pt) 175 | } 176 | wire-ended = true 177 | } else if utility.is-circuit-meta-instruction(item) { 178 | if item.qc-instr == "setwire" { 179 | let new-style = (:) 180 | if "distance" in item { new-style.distance = item.distance } 181 | if "count" in item { new-style.count = item.count } 182 | if "stroke" in item { new-style.stroke = item.stroke } 183 | wire-instructions.at(row).push(new-style) 184 | } else { 185 | // Visual meta instructions are handled later 186 | let (x, y) = (if-auto(item.x, col), if-auto(item.y, row)) 187 | meta-instructions.push((x: x, y: y, item: item)) 188 | } 189 | } else if utility.is-circuit-drawable(item) { 190 | let gate = item 191 | let (x, y) = (gate.x, gate.y) 192 | if x == auto { 193 | x = col 194 | if y == auto { 195 | if col != prev-col { 196 | wire-instructions.at(row).push((prev-col, col)) 197 | } 198 | prev-col = col 199 | col += 1 200 | } 201 | } 202 | if y == auto { y = row } 203 | 204 | if y >= matrix.len() { matrix += ((),) * (y - matrix.len() + 1) } 205 | if x >= matrix.at(y).len() { 206 | matrix.at(y) += (auto-cell,) * (x - matrix.at(y).len() + 1) 207 | } 208 | 209 | assert(matrix.at(y).at(x).empty, message: "Attempted to place a second gate at column " + str(x) + ", row " + str(y)) 210 | 211 | let size-hint = utility.get-size-hint(item, draw-params) 212 | let gate-size = size-hint 213 | if item.floating { size-hint.width = 0pt } // floating items don't take width in the layout 214 | 215 | matrix.at(y).at(x) = ( 216 | size: size-hint, 217 | gutter: 0pt, 218 | box: item.box, 219 | empty: gate.data == "placeholder" 220 | ) 221 | let gate-info = ( 222 | gate: gate, 223 | size: gate-size, 224 | x: x, 225 | y: y 226 | ) 227 | if gate.multi != none { multi-qubit-gates.push(gate-info) } 228 | else { single-qubit-gates.push(gate-info) } 229 | wire-ended = false 230 | } else if type(item) == int { 231 | wire-instructions.at(row).push((prev-col, col + item - 1)) 232 | col += item 233 | prev-col = col - 1 234 | if col >= matrix.at(row).len() { 235 | matrix.at(row) += (auto-cell,) * (col - matrix.at(row).len()) 236 | } 237 | wire-ended = false 238 | } else if type(item) == length { 239 | if wire-ended { 240 | row-gutter.at(row - 1) = calc.max(row-gutter.at(row - 1), item) 241 | } else if col > 0 { 242 | matrix.at(row).at(col - 1).gutter = calc.max(matrix.at(row).at(col - 1).gutter, item) 243 | } 244 | } 245 | } 246 | 247 | 248 | if wires != auto { 249 | let num-wires = if type(wires) == array { wires.len() } else { wires } 250 | if matrix.len() < num-wires { 251 | let diff = num-wires - matrix.len() 252 | 253 | matrix += ((),) * diff 254 | row-gutter += (0pt,) * diff 255 | } else if type(wires) == int and matrix.len() > num-wires { 256 | assert(false, message: "You explicitly specified " + str(wires) + " wires but there are " + str(matrix.len())) 257 | } 258 | } 259 | 260 | // finish up matrix 261 | let num-rows = matrix.len() 262 | let num-cols = calc.max(0, ..matrix.map(array.len)) 263 | if num-rows == 0 or num-cols == 0 { return none } 264 | 265 | 266 | for i in range(num-rows) { 267 | matrix.at(i) += (auto-cell,) * (num-cols - matrix.at(i).len()) 268 | } 269 | row-gutter += (0pt,) * (matrix.len() - row-gutter.len()) 270 | 271 | if wire-instructions.len() != num-rows { 272 | let diff = num-rows - wire-instructions.len() 273 | wire-instructions += ((default-wire-style,),) * diff 274 | } 275 | 276 | if fill-wires { 277 | wire-instructions.at(row).push((prev-col, -1)) // fill current wire 278 | for row in range(row + 1, num-rows) { 279 | wire-instructions.at(row).push((0, -1)) 280 | } 281 | } 282 | 283 | let vertical-wires = () 284 | // Treat multi-qubit gates (and controlled gates) 285 | // - extract and store all necessary vertical control wires 286 | // - Apply same size-hints to all cells that a mqgate spans (without the control wire). 287 | for gate in multi-qubit-gates { 288 | let (x, y) = gate 289 | let size = matrix.at(y).at(x).size 290 | let multi = gate.gate.multi 291 | 292 | if multi.target != none and multi.target != 0 { 293 | verifications.verify-controlled-gate(gate.gate, x, y, num-rows, num-cols) 294 | 295 | let diff = if multi.target > 0 {multi.num-qubits - 1} else {0} 296 | vertical-wires.push(( 297 | x: x, 298 | y: y + diff, 299 | target: multi.target - diff, 300 | wire-style: (count: multi.wire-count, stroke: multi.wire-stroke), 301 | labels: multi.wire-label 302 | )) 303 | } 304 | let nq = multi.num-qubits 305 | if nq == 1 { continue } 306 | 307 | verifications.verify-mqgate(gate.gate, x, y, num-rows, num-cols) 308 | 309 | for qubit in range(y, y + nq) { 310 | if qubit - y in multi.pass-through { continue } 311 | matrix.at(qubit).at(x).size.width = size.width 312 | } 313 | let start = y 314 | if multi.size-all-wires != none { 315 | if not multi.size-all-wires { 316 | start = calc.max(0, y + nq - 1) 317 | } 318 | for qubit in range(start, y + nq) { 319 | matrix.at(qubit).at(x).size = size 320 | } 321 | } 322 | } 323 | 324 | 325 | let row-heights = matrix.map(row => 326 | calc.max(min-row-height, ..row.map(item => item.size.height)) + row-spacing 327 | ) 328 | if equal-row-heights { 329 | let max-row-height = calc.max(..row-heights) 330 | row-heights = (max-row-height,) * row-heights.len() 331 | } 332 | 333 | let col-widths = range(num-cols).map(j => 334 | calc.max(min-column-width, ..range(num-rows).map(i => { 335 | matrix.at(i).at(j).size.width 336 | })) + column-spacing 337 | ) 338 | 339 | let col-gutter = range(num-cols).map(j => 340 | calc.max(0pt, ..range(num-rows).map(i => { 341 | matrix.at(i).at(j).gutter 342 | })) 343 | ) 344 | 345 | let center-x-coords = layout.compute-center-coords(col-widths, col-gutter).map(x => x - 0.5 * column-spacing) 346 | let center-y-coords = layout.compute-center-coords(row-heights, row-gutter).map(x => x - 0.5 * row-spacing) 347 | draw-params.center-y-coords = center-y-coords 348 | 349 | let circuit-width = col-widths.sum() + col-gutter.slice(0, -1).sum(default: 0pt) - column-spacing 350 | let circuit-height = row-heights.sum() + row-gutter.sum() - row-spacing 351 | 352 | 353 | 354 | /////////// Second part: Generation /////////// 355 | 356 | let bounds = (0pt, 0pt, circuit-width, circuit-height) 357 | 358 | let circuit = block( 359 | width: circuit-width, height: circuit-height, { 360 | set align(top + left) // quantum-circuit could be called in a scope where these have been changed which would mess up everything 361 | set place(left) 362 | 363 | let layer-below-circuit 364 | let layer-above-circuit 365 | for (item, x, y) in meta-instructions { 366 | let (the-content, decoration-bounds) = (none, none) 367 | if item.qc-instr == "gategroup" { 368 | if item.right != auto { 369 | item.steps = num-cols - x - item.right 370 | } 371 | if item.bottom != auto { 372 | item.wires = num-rows - y - item.bottom 373 | } 374 | verifications.verify-gategroup(item, x, y, num-rows, num-cols) 375 | let (dy1, dy2) = layout.get-cell-coords(center-y-coords, row-heights, (y, y + item.wires - 1e-9)) 376 | let (dx1, dx2) = layout.get-cell-coords(center-x-coords, col-widths, (x, x + item.steps - 1e-9)) 377 | (the-content, decoration-bounds) = draw-functions.draw-gategroup(dx1, dx2, dy1, dy2, item, draw-params) 378 | } else if item.qc-instr == "slice" { 379 | verifications.verify-slice(item, x, y, num-rows, num-cols) 380 | let end = if item.wires == 0 { row-heights.len() } else { y + item.wires } 381 | let (dy1, dy2) = layout.get-cell-coords(center-y-coords, row-heights, (y, end)) 382 | let dx = layout.get-cell-coords(center-x-coords, col-widths, x) 383 | (the-content, decoration-bounds) = draw-functions.draw-slice(dx, dy1, dy2, item, draw-params) 384 | } else if item.qc-instr == "annotate" { 385 | let rows = layout.get-cell-coords(center-y-coords, row-heights, item.rows) 386 | let cols = layout.get-cell-coords(center-x-coords, col-widths, item.columns) 387 | let annotation = (item.callback)(cols, rows) 388 | verifications.verify-annotation-content(annotation) 389 | if type(annotation) == dictionary { 390 | (the-content, decoration-bounds) = layout.place-with-labels( 391 | annotation.content, 392 | dx: annotation.at("dx", default: 0pt), 393 | dy: annotation.at("dy", default: 0pt), 394 | draw-params: draw-params 395 | ) 396 | } else if type(annotation) in (symbol, content, str) { 397 | layer-below-circuit += place(annotation) 398 | } 399 | } 400 | if decoration-bounds != none { 401 | bounds = layout.update-bounds(bounds, decoration-bounds) 402 | } 403 | if item.at("z", default: "below") == "below" { layer-below-circuit += the-content } 404 | else { layer-above-circuit += the-content } 405 | } 406 | 407 | layer-below-circuit 408 | 409 | 410 | let get-gate-pos(x, y, size-hint) = { 411 | let dx = center-x-coords.at(x) 412 | let dy = center-y-coords.at(y) 413 | let (width, height) = size-hint 414 | let offset = size-hint.at("offset", default: auto) 415 | 416 | if offset == auto { return (dx - width / 2, dy - height / 2) } 417 | 418 | assert(type(offset) == dictionary, message: "Unexpected type `" + str(type(offset)) + "` for parameter `offset`") 419 | 420 | let offset-x = offset.at("x", default: auto) 421 | let offset-y = offset.at("y", default: auto) 422 | if offset-x == auto { dx -= width / 2} 423 | else if type(offset-x) == length { dx -= offset-x } 424 | if offset-y == auto { dy -= height / 2} 425 | else if type(offset-y) == length { dy -= offset-y } 426 | return (dx, dy) 427 | } 428 | 429 | 430 | let get-anchor-width(x, y) = { 431 | if x == num-cols { return 0pt } 432 | let el = matrix.at(y).at(x) 433 | if "box" in el and not el.box { return 0pt } 434 | return el.size.width 435 | } 436 | 437 | let get-anchor-height(x, y) = { 438 | let el = matrix.at(y).at(x) 439 | if "box" in el and not el.box { return 0pt } 440 | return el.size.height 441 | } 442 | 443 | 444 | for (x, y, target, wire-style, labels) in vertical-wires { 445 | let dx = center-x-coords.at(x) 446 | let (dy1, dy2) = (center-y-coords.at(y), center-y-coords.at(y + target)) 447 | dy1 += get-anchor-height(x, y) / 2 * signum(target) 448 | dy2 -= get-anchor-height(x, y + target) / 2 * signum(target) 449 | 450 | if labels.len() == 0 { 451 | draw-functions.draw-vertical-wire( 452 | dy1, dy2, dx, 453 | utility.update-stroke(wire, wire-style.stroke), 454 | wire-count: wire-style.count, 455 | ) 456 | } else { 457 | let (result, gate-bounds) = draw-functions.draw-vertical-wire-with-labels( 458 | dy1, dy2, dx, 459 | wire, wire-count: wire-style.count, 460 | wire-labels: labels, 461 | draw-params: draw-params 462 | ) 463 | result 464 | bounds = layout.update-bounds(bounds, gate-bounds) 465 | } 466 | } 467 | 468 | 469 | for (row, wire-in) in wire-instructions.enumerate() { 470 | let wire-style = wire-in.at(0) 471 | for wire-piece in wire-in { 472 | if type(wire-piece) == dictionary { 473 | if "stroke" in wire-piece { 474 | wire-piece.stroke = utility.update-stroke(wire-style.stroke, wire-piece.stroke) 475 | } 476 | wire-style += wire-piece 477 | } else { 478 | if wire-style.count == 0 { continue } 479 | let (start-x, end-x) = wire-piece 480 | if end-x == -1 { 481 | end-x = num-cols - 1 482 | } 483 | if start-x == end-x { continue } 484 | 485 | let draw-subwire(x1, x2) = { 486 | let dx1 = center-x-coords.at(x1) 487 | let dx2 = center-x-coords.at(x2, default: circuit-width) 488 | let dy = center-y-coords.at(row) 489 | dx1 += get-anchor-width(x1, row) / 2 490 | dx2 -= get-anchor-width(x2, row) / 2 491 | draw-functions.draw-horizontal-wire(dx1, dx2, dy, wire-style.stroke, wire-style.count, wire-distance: wire-style.distance) 492 | } 493 | // Draw wire pieces and take care not to draw through gates. 494 | for x in range(start-x + 1, end-x) { 495 | let anchor-width = get-anchor-width(x, row) 496 | if anchor-width == 0pt { continue } // no gate or `box: false` gate. 497 | draw-subwire(start-x, x) 498 | start-x = x 499 | } 500 | draw-subwire(start-x, end-x) 501 | } 502 | } 503 | } 504 | 505 | 506 | 507 | for gate-info in single-qubit-gates { 508 | let (gate, size, x, y) = gate-info 509 | let (dx, dy) = get-gate-pos(x, y, size) 510 | let content = utility.get-content(gate, draw-params) 511 | 512 | let (result, gate-bounds) = layout.place-with-labels( 513 | content, 514 | size: size, 515 | dx: dx, dy: dy, 516 | labels: gate.labels, draw-params: draw-params 517 | ) 518 | bounds = layout.update-bounds(bounds, gate-bounds) 519 | result 520 | } 521 | 522 | for gate-info in multi-qubit-gates { 523 | let (gate, size, x, y) = gate-info 524 | let draw-params = draw-params 525 | gate.qubit = y 526 | if gate.multi.num-qubits > 1 { 527 | let dy1 = center-y-coords.at(y + gate.multi.num-qubits - 1) 528 | let dy2 = center-y-coords.at(y) 529 | draw-params.multi.wire-distance = dy1 - dy2 530 | } 531 | 532 | // lsticks need their offset/width to be updated again (but don't update the height!) 533 | let content = utility.get-content(gate, draw-params) 534 | let new-size = utility.get-size-hint(gate, draw-params) 535 | size.offset = new-size.offset 536 | size.width = new-size.width 537 | 538 | let (dx, dy) = get-gate-pos(x, y, size) 539 | let (result, gate-bounds) = layout.place-with-labels( 540 | content, 541 | size: if gate.multi != none and gate.multi.num-qubits > 1 {auto} else {size}, 542 | dx: dx, dy: dy, 543 | labels: gate.labels, draw-params: draw-params 544 | ) 545 | bounds = layout.update-bounds(bounds, gate-bounds) 546 | result 547 | } 548 | 549 | layer-above-circuit 550 | 551 | // show matrix 552 | // for (i, row) in matrix.enumerate() { 553 | // for (j, entry) in row.enumerate() { 554 | // let (dx, dy) = (center-x-coords.at(j), center-y-coords.at(i)) 555 | // place( 556 | // dx: dx - entry.size.width / 2, dy: dy - entry.size.height / 2, 557 | // box(stroke: green, width: entry.size.width, height: entry.size.height) 558 | // ) 559 | // } 560 | // } 561 | 562 | }) // end circuit = block(..., { 563 | 564 | let scale = scale 565 | if circuit-padding != none { 566 | let circuit-padding = process-args.process-padding-arg(circuit-padding) 567 | bounds.at(0) -= circuit-padding.left 568 | bounds.at(1) -= circuit-padding.top 569 | bounds.at(2) += circuit-padding.right 570 | bounds.at(3) += circuit-padding.bottom 571 | } 572 | let final-height = scale * (bounds.at(3) - bounds.at(1)) 573 | let final-width = scale * (bounds.at(2) - bounds.at(0)) 574 | 575 | let thebaseline = baseline 576 | if type(thebaseline) in (content, str) { 577 | thebaseline = height/2 - measure(thebaseline).height/2 578 | } 579 | if type(thebaseline) == fraction { 580 | thebaseline = 100% - layout.get-cell-coords1(center-y-coords, row-heights, thebaseline / 1fr) + bounds.at(1) 581 | } 582 | box(baseline: thebaseline, 583 | width: final-width, 584 | height: final-height, 585 | // stroke: 1pt + gray, 586 | align(left + top, move(dy: -scale * bounds.at(1), dx: -scale * bounds.at(0), 587 | layout.std-scale( 588 | x: scale, 589 | y: scale, 590 | origin: left + top, 591 | circuit 592 | ))) 593 | ) 594 | 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /docs/guide/quill-guide.typ: -------------------------------------------------------------------------------- 1 | #import "template.typ": * 2 | #import "@preview/tidy:0.4.3" 3 | 4 | 5 | #let version = toml("/typst.toml").package.version 6 | #show link: set text(fill: rgb("#1e8f6f")) 7 | 8 | #show: project.with( 9 | title: "Quill", 10 | authors: ("Mc-Zen",), 11 | abstract: [Quill is a library for creating quantum circuit diagrams in #link("https://typst.app/", [Typst]). ], 12 | date: datetime.today().display("[month repr:long] [day], [year]"), 13 | version: version, 14 | url: "https://github.com/Mc-Zen/quill" 15 | ) 16 | 17 | #v(4em) 18 | 19 | #outline(depth: 2, indent: 2em) 20 | #pagebreak() 21 | 22 | = Introduction 23 | 24 | #pad(x: 1cm)[_@gate-gallery features a gallery of many gates and symbols and how to create them. In @demo, you can find a variety of example figures along with the code. @tequila introduces an alternative model for creating and composing circuits._] 25 | 26 | Would you like to create quantum circuits directly in Typst? Maybe a circuit for quantum teleportation? 27 | #figure[#include("../../examples/teleportation.typ")] 28 | 29 | Or one for phase estimation? The code for both examples can be found in @demo. 30 | #figure[#include("../../examples/phase-estimation.typ")] 31 | 32 | This library provides high-level functionality for generating these and more quantum circuit diagrams. 33 | 34 | For those who work with the LaTeX packages `qcircuit` and `quantikz`, the syntax will be familiar. The wonderful thing about Typst is that the changes can be viewed instantaneously which makes it ever so much easier to design a beautiful quantum circuit. The syntax also has been updated a little bit to fit with concepts of the Typst language and many things like styling content is much simpler than with `quantikz` since it is directly supported in Typst. 35 | 36 | 37 | = Basics 38 | 39 | A circuit can be created by calling the #ref-fn("quantum-circuit()") function with a number of circuit elements. 40 | 41 | 42 | // #example( 43 | // ```typ 44 | // #quantum-circuit( 45 | // lstick($|0〉$), gate($H$), phase($ϑ$), 46 | // gate($H$), rstick($cos ϑ/2 lr(|0〉)-sin ϑ/2 lr(|1〉)$) 47 | // ) 48 | // ```) 49 | 50 | 51 | #example( 52 | ```typ 53 | #quantum-circuit( 54 | 1, gate($H$), phase($theta.alt$), meter(), 1 55 | ) 56 | ``` 57 | ) 58 | 59 | A quantum gate is created with the #ref-fn("gate()") command. To make life easier, instead of calling `gate($H$)`, you can also just put in the gate's content `$H$`. Unlike `qcircuit` and `quantikz`, the math environment is not automatically entered for the content of the gate which allows for passing in any type of content (even images or tables). Use displaystyle math (for example `$ U_1 $` instead of `$U_1$` to enable appropriate scaling of the gate for more complex mathematical expressions like double subscripts etc. 60 | 61 | #pagebreak() 62 | 63 | Consecutive gates are automatically joined with wires. Plain integers can be used to indicate a number of cells with just wire and no gate (where you would use a lot of `&`'s and `\qw`'s in `quantikz`). 64 | 65 | #example( 66 | ```typ 67 | #quantum-circuit( 68 | 1, $H$, 4, meter() 69 | ) 70 | ``` 71 | ) 72 | 73 | 74 | 75 | A new wire can be created by breaking the current wire with `[\ ]`: 76 | 77 | #example( 78 | ```typ 79 | #quantum-circuit( 80 | 1, $H$, ctrl(1), 1, [\ ], 81 | 2, targ(), 1 82 | ) 83 | ``` 84 | ) 85 | 86 | We can create a #smallcaps("cx")-gate by calling #ref-fn("ctrl()") and passing the relative distance to the desired wire, e.g., `1` to the next wire, `2` to the second-next one or `-1` to the previous wire. Per default, the end of the vertical wire is just joined with the target wire without any decoration at all. Here, we make the gate a #smallcaps("cx")-gate by adding a #ref-fn("targ()") symbol on the second wire. In order to make a #smallcaps("cz")-gate with another control circle on the target wire, just use `ctrl()` as target. 87 | 88 | 89 | == Multi-Qubit Gates and Wire Labels 90 | Let's look at a quantum bit-flipping error correction circuit. Here we encounter our first multi-qubit gate as well as wire labels: 91 | 92 | #example(vertical: true, 93 | ```typ 94 | #quantum-circuit( 95 | lstick($|psi〉$), ctrl(1), ctrl(2), mqgate($E_"bit"$, n: 3), ctrl(1), ctrl(2), 96 | targ(), rstick($|psi〉$), [\ ], 97 | lstick($|0〉$), targ(), 2, targ(), 1, ctrl(-1), 1, [\ ], 98 | lstick($|0〉$), 1, targ(), 2, targ(), ctrl(-1), 1 99 | ) 100 | ``` 101 | ) 102 | 103 | Multi-qubit gates have a dedicated command #ref-fn("mqgate()") which allows to specify the number of qubits `n` as well as a variety of other options. Wires can be labelled at the beginning or the end with the #ref-fn("lstick()") and #ref-fn("rstick()") commands, respectively. Both create a label "sticking" out from the wire. 104 | 105 | #pagebreak() 106 | 107 | Just as multi-qubit gates, #ref-fn("lstick()") and #ref-fn("rstick()") can span multiple wires, again with the parameter `n`. Furthermore, the brace can be changed or turned off with `brace: none`. If the label is only applied to a single qubit, it will have no brace by default but in this case a brace can be added just the same way. By default it is set to `brace: auto`. 108 | 109 | #example(vertical: true, 110 | ```typ 111 | #quantum-circuit( 112 | lstick($|000〉$, n: 3), $H$, ctrl(1), ctrl(2), 1, 113 | rstick($|psi〉$, n: 3, brace: "]"), [\ ], 114 | 1, $H$, ctrl(), 3, [\ ], 115 | 1, $H$, 1, ctrl(), 2 116 | ) 117 | ``` 118 | ) 119 | 120 | 121 | == All about Wires 122 | In many circuits, we need classical wires. This library generalizes the concept of quantum, classical and bundled wires and provides the #ref-fn("setwire()") command that allows all sorts of changes to the current wire setting. You may call `setwire()` with the number of wires to display and optionally a `stroke` setting: 123 | 124 | #example(vertical: false, 125 | ```typ 126 | #quantum-circuit( 127 | 1, $A$, meter(n: 1), [\ ], 128 | setwire(2, stroke: blue), 2, ctrl(), 2, [\ ], 129 | 1, $X$, setwire(0), 1, lstick($|0〉$), setwire(1), $Y$, 130 | ) 131 | ``` 132 | ) 133 | 134 | The `setwire()` command produces no cells and can be called at any point on the wire. When a new wire is started, the default wire setting is restored automatically (see @circuit-styling on how to customize the default). Calling `setwire(0)` removes the wire altogether until `setwire()` is called with different arguments. More than two wires are possible and it lies in your hands to decide how many wires still look good. The distance between bundled wires can also be specified: 135 | 136 | #example(vertical: false, 137 | ```typ 138 | #quantum-circuit( 139 | setwire(4, wire-distance: 1.5pt), 1, $U$, meter() 140 | ) 141 | ``` 142 | ) 143 | 144 | 145 | #pagebreak() 146 | 147 | == Slices and Gate Groups 148 | 149 | In order to structure quantum circuits, you often want to mark sections to denote certain steps in the circuit. This can be easily achieved through the #ref-fn("slice()") and #ref-fn("gategroup()") commands. Both are inserted into the circuit where the slice or group should begin and allow an arbitrary number of labels through the `labels` argument (more on labels in @labels). The function `gategroup()` takes two positional integer arguments which specify the number of wires and steps the group should span. Slices reach down to the last wire by default but the number of sliced wires can also be set manually. 150 | 151 | 152 | #example( 153 | ```typ 154 | #quantum-circuit( 155 | 1, gate($H$), ctrl(1), 156 | slice(label: "1"), 1, 157 | gategroup(3, 3, label: (content: 158 | "Syndrome measurement", pos: bottom)), 159 | 1, ctrl(2), ctrl(), 1, 160 | slice(label: "3", n: 2, 161 | stroke: blue), 162 | 2, [\ ], 163 | 2, targ(), 1, ctrl(1), 1, ctrl(), 3, [\ ], 164 | 4, targ(), targ(), meter(target: -2) 165 | ) 166 | ``` 167 | ) 168 | 169 | == Labels 170 | Finally, we want to show how to place labels on gates and vertical wires. The function #ref-fn("gate()") and all the derived gate commands such as #ref-fn("meter()"), #ref-fn("ctrl()"), #ref-fn("lstick()") etc. feature a `label` argument for adding any number of labels on and around the element. In order to produce a simple label on the default position (for plain gates this is at the top of the gate, for vertical wires it is to the right and for the #ref-fn("phase()") gate it is to the top right), you can just pass content or a string: 171 | 172 | #example( 173 | ```typ 174 | #quantum-circuit( 175 | 1, gate($H$, label: "Hadamard"), 1 176 | ) 177 | ``` 178 | ) 179 | 180 | If you want to change the position of the label or specify the offset, you want to pass a dictionary with the key `content` and optional values for `pos` (alignment), `dx` and `dy` (length, ratio or relative length): 181 | 182 | #example( 183 | ```typ 184 | #quantum-circuit( 185 | 1, gate($H$, label: (content: "Hadamard", pos: bottom, dy: 0pt)), 1 186 | ) 187 | ``` 188 | ) 189 | 190 | #pagebreak() 191 | 192 | Multiple labels can be added by passing an array of labels specified through dictionaries. 193 | 194 | #example( 195 | ```typ 196 | #quantum-circuit( 197 | 1, gate(hide($H$), label: ( 198 | (content: "lt", pos: left + top), 199 | (content: "t", pos: top), 200 | (content: "rt", pos: right + top), 201 | (content: "l", pos: left), 202 | (content: "c", pos: center), 203 | (content: "r", pos: right), 204 | (content: "lb", pos: left + bottom), 205 | (content: "b", pos: bottom), 206 | (content: "rb", pos: right + bottom), 207 | )), 1 208 | ) 209 | ``` 210 | ) 211 | 212 | Labels for slices and gate groups work just the same. In order to place a label on a control wire, you can use the `wire-label` parameter provided for #ref-fn("mqgate()"), #ref-fn("ctrl()") and #ref-fn("swap()"). 213 | 214 | #example( 215 | ```typ 216 | #quantum-circuit( 217 | 1, ctrl(1, wire-label: $phi$), 2, 218 | swap(1, wire-label: ( 219 | content: rotate(-90deg, smallcaps("swap")), 220 | pos: left, dx: 0pt) 221 | ), 1, [\ ], 10pt, 222 | 1, ctrl(), 2, swap(), 1, 223 | ) 224 | ``` 225 | ) 226 | 227 | 228 | 229 | #pagebreak() 230 | = Gate Placement 231 | By default, all gates are placed automatically and sequentially. In this, `quantum-circuit()` behaves similar to the built-in `table()` and `grid()` functions. However, just like with `table.cell` and `grid.cell`, it is also possible to place any gate at a certain column `x` and row `y`. This makes it possible to simplify redundant code. 232 | 233 | Let's look at an example of preparing a certain graph state: 234 | 235 | #example( 236 | ```typ 237 | #quantum-circuit( 238 | ..range(4).map(i => lstick($|0〉$, y: i, x: 0)), 239 | ..range(4).map(i => gate($H$, y: i, x: 1)), 240 | 2, 241 | ctrl(2), 1, ctrl(1), 1, [\ ], 242 | 3, ctrl(2), ctrl(), [\ ], 243 | 2, ctrl(), [\ ], 244 | 3, ctrl() 245 | ) 246 | ``` 247 | ) 248 | Note, that it is not possible to add a second gate to a cell that is already occupied. However, it is allowed to leave either `x` or `y` at `auto` and manually set the other. In the case that `x` is set but `y: auto`, the gate is placed at the current wire and the specified column. In the case that `y` is set and `x: auto`, the gate is placed at the current column and the specified wire but the current column is not advanced to the next column. The parameters `x` and `y` are available for all gates and decorations. 249 | 250 | Manual placement can also be helpful to keep the source code a bit more cleaner. For example, it is possible to move the code for a `gategroup()` or `slice()` command entirely to the bottom to enhance readability. 251 | 252 | #example(text-size: .9em, 253 | ```typ 254 | #quantum-circuit( 255 | 1, $S^dagger$, $H$, ctrl(), $H$, $S$, 1, [\ ], 256 | 3, ctrl(-1), 257 | gategroup(2, 5, x: 1, y: 0, stroke: purple, 258 | label: (pos: bottom, content: text(purple)[CY gate])), 259 | gategroup(2, 3, x: 2, y: 0, stroke: blue, 260 | label: text(blue)[CX gate]), 261 | ) 262 | ``` 263 | ) 264 | 265 | 266 | #pagebreak() 267 | = Circuit Styling 268 | 269 | The #ref-fn("quantum-circuit()") command provides several options for styling the entire circuit. The parameters `row-spacing` and `column-spacing` allow changing the optical density of the circuit by adjusting the spacing between circuit elements vertically and horizontically. 270 | 271 | #example( 272 | ```typ 273 | #quantum-circuit( 274 | row-spacing: 5pt, 275 | column-spacing: 5pt, 276 | 1, $A$, $B$, 1, [\ ], 277 | 1, 1, $S$, 1 278 | ) 279 | ``` 280 | ) 281 | 282 | The `wire`, `color` and `fill` options provide means to customize line strokes and colors. This allows us to easily create "dark-mode" circuits: 283 | 284 | #example( 285 | ```typ 286 | #box(fill: black, quantum-circuit( 287 | wire: .7pt + white, // Wire and stroke color 288 | color: white, // Default foreground and text color 289 | fill: black, // Gate fill color 290 | 1, $X$, ctrl(1), rstick([*?*]), [\ ], 291 | 1,1, targ(), meter(), 292 | )) 293 | ``` 294 | ) 295 | 296 | Furthermore, a common task is changing the total size of a circuit by scaling it up or down. Instead of tweaking all the parameters like `font-size`, `padding`, `row-spacing` etc. you can specify the `scale` option which takes a percentage value: 297 | 298 | #example( 299 | ```typ 300 | #quantum-circuit( 301 | scale: 60%, 302 | 1, $H$, ctrl(1), $H$, 1, [\ ], 303 | 1, 1, targ(), 2 304 | ) 305 | ``` 306 | ) 307 | 308 | Note, that this is different than calling Typst's built-in `scale()` function on the circuit which would scale it without affecting the layout, thus still reserving the same space as if unscaled! 309 | 310 | #pagebreak() 311 | 312 | For an optimally layout, the height for each row is determined by the gates on that wire. For this reason, the wires can have different distances. To better see the effect, let's decrease the `row-spacing`: 313 | 314 | #example( 315 | ```typ 316 | #quantum-circuit( 317 | row-spacing: 2pt, min-row-height: 4pt, 318 | 1, $H$, ctrl(1), $H$, 1, [\ ], 319 | 1, $H$, targ(), $H$, 1, [\ ], 320 | 2, ctrl(1), 2, [\ ], 321 | 1, $H$, targ(), $H$, 1 322 | ) 323 | ``` 324 | ) 325 | 326 | Setting the option `equal-row-heights` to `true` solves this problem (manually spacing the wires with lengths is still possible, see @fine-tuning): 327 | 328 | #example( 329 | ```typ 330 | #quantum-circuit( 331 | equal-row-heights: true, 332 | row-spacing: 2pt, min-row-height: 4pt, 333 | 1, $H$, ctrl(1), $H$, 1, [\ ], 334 | 1, $H$, targ(), $H$, 1, [\ ], 335 | 2, ctrl(1), 2, [\ ], 336 | 1, $H$, targ(), $H$, 1 337 | ) 338 | ``` 339 | ) 340 | 341 | // #example( 342 | // ```typ 343 | // #quantum-circuit( 344 | // scale: 60%, 345 | // 1, gate($H$), ctrl(1), gate($H$), 1, [\ ], 346 | // 1, 1, targ(), 2 347 | // ) 348 | // ```, [ 349 | // #quantum-circuit( 350 | // baseline: "=", 351 | // 2, ctrl(1), 2, [\ ], 352 | // 1, gate($H$), targ(), gate($H$), 1 353 | // ) = 354 | // #quantum-circuit( 355 | // baseline: "=", 356 | // phantom(), ctrl(1), 1, [\ ], 357 | // phantom(content: $H$), ctrl(), phantom(content: $H$), 358 | // ) 359 | // ]) 360 | 361 | 362 | There is another option for #ref-fn("quantum-circuit()") that has a lot of impact on the looks of the diagram: `gate-padding`. This at the same time controls the default gate box padding and the distance of `lstick`s and `rstick`s to the wire. Need really wide or tight circuits? 363 | 364 | #example( 365 | ```typ 366 | #quantum-circuit( 367 | gate-padding: 2pt, 368 | row-spacing: 5pt, column-spacing: 7pt, 369 | lstick($|0〉$, n: 3), $H$, ctrl(1), 370 | ctrl(2), 1, rstick("GHZ", n: 3), [\ ], 371 | 1, $H$, ctrl(), 1, $H$, 1, [\ ], 372 | 1, $H$, 1, ctrl(), $H$, 1 373 | ) 374 | ``` 375 | ) 376 | 377 | 378 | #pagebreak() 379 | 380 | = Gate Gallery 381 | 382 | 383 | #[ 384 | #set par(justify: false) 385 | #import "gallery.typ": gallery 386 | #gallery 387 | ] 388 | #pagebreak() 389 | 390 | 391 | 392 | = Fine-Tuning 393 | 394 | The #ref-fn("quantum-circuit()") command allows not only gates as well as content and string items but only `length` parameters which can be used to tweak the spacing of the circuit. Inserting a `length` value between two gates adds a *horizontal space* of that length between the cells: 395 | 396 | #example( 397 | ```typ 398 | #quantum-circuit( 399 | $X$, $Y$, 10pt, $Z$ 400 | ) 401 | ``` 402 | ) 403 | 404 | In the background, this works like a grid gutter that is set to `0pt` by default. If a length value is inserted between the same two columns on different wires/rows, the maximum value is used for the space. In the same spirit, inserting multiple consecutive length values result in the largest being used, e.g., inserting `5pt, 10pt, 6pt` results in a `10pt` gutter in the corresponding position. 405 | 406 | Putting a a length after a wire break item `[\ ]` produces a *vertical space* between the corresponding wires: 407 | 408 | #example( 409 | ```typ 410 | #quantum-circuit( 411 | $X$, [\ ], $Y$, [\ ], 10pt, $Z$ 412 | ) 413 | ``` 414 | ) 415 | 416 | 417 | 418 | 419 | 420 | #pagebreak() 421 | 422 | = Annotations 423 | 424 | *Quill* provides a way of making custom annotations through the #ref-fn("annotate()") interface. An `annotate()` object may be placed anywhere in the circuit, the position only matters for the draw order in case several annotations would overlap. 425 | 426 | 427 | The `annotate()` command allows for querying cell coordinates of the circuit and passing in a custom draw function to draw globally in the circuit diagram. // This way, basically any decoration 428 | 429 | Let's look at an example: 430 | 431 | #example( 432 | ```typ 433 | #quantum-circuit( 434 | 1, ctrl(1), $H$, meter(), [\ ], 435 | 1, targ(), 1, meter(), 436 | annotate((2, 4), 0, ((x1, x2), y) => { 437 | let brace = math.lr($#block(height: x2 - x1)}$) 438 | place(dx: x1, dy: y, rotate(brace, -90deg, origin: top)) 439 | let content = [Readout circuit] 440 | context { 441 | let size = measure(content) 442 | place( 443 | dx: x1 + (x2 - x1) / 2 - size.width / 2, 444 | dy: y - .6em - size.height, content 445 | ) 446 | } 447 | }) 448 | ) 449 | ``` 450 | ) 451 | 452 | First, the call to `annotate()` asks for the $x$ coordinates of the second and forth column and the $y$ coordinate of the zeroth row (first wire). The draw callback function then gets the corresponding coordinates as arguments and uses them to draw a brace and some text above the cells. Optionally, you can specify whether the annotation should be drawn above or below the circuit by adding `z: above` or `z: "below"`. The default is `"below"`. 453 | 454 | Note, that the circuit does not know how large the annotation is by default. For this reason, the annotation may exceed the bounds of the circuit. This can be fixed by letting the callback return a dictionary with the keys `content`, `dx` and `dy` (the latter two are optional). The content should be measurable, i.e., not be wrapped in a call to `place()`. Instead the placing coordinates can be specified via the keys `dx` and `dy`. 455 | 456 | Another example, here we want to obtain coordinates for the cell centers. We can achieve this by adding $0.5$ to the cell index. The fractional part of the number represents a percentage of the cell width/height. 457 | 458 | #example( 459 | ```typ 460 | #quantum-circuit( 461 | 1, $X$, 2, [\ ], 462 | 1, 2, $Y$, [\ ], 463 | 1, 1, $H$, meter(), 464 | annotate((1.5, 3.5, 2.5), (0.5, 1.5, 2.5), z: "above", 465 | ((x0, x1, x2), (y0, y1, y2)) => { 466 | ( 467 | content: polygon( 468 | (x0, y0), (x1, y1), (x2, y2), 469 | fill: rgb("#1020EE50"), stroke: .5pt + black 470 | ), 471 | ) 472 | }) 473 | ) 474 | ``` 475 | ) 476 | 477 | 478 | #pagebreak() 479 | = Custom Gates 480 | 481 | Quill allows you to create totally customized gates by specifying the `draw-function` argument in #ref-fn("gate()") or #ref-fn("mqgate()"). You will not need to do this however if you just want to change the color of the gate or make it round. For these tasks you can just use the appropriate arguments of the #ref-fn("gate()") command. 482 | 483 | _Note, that the interface for custom gates might still change a bit. _ 484 | 485 | When the circuit is laid out, the draw function is called with two (read-only) arguments: the gate itself and a dictionary that contains information about the circuit style and more. 486 | 487 | Let us look at a little example for a custom gate that just shows the vertical lines of the box but not the horizontal ones. 488 | 489 | #let quill-gate = ```typ 490 | #let draw-quill-gate(gate, draw-params) = { 491 | let stroke = draw-params.wire 492 | let fill = if gate.fill != none { gate.fill } else { draw-params.background } 493 | 494 | box( 495 | gate.content, 496 | fill: fill, stroke: (left: stroke, right: stroke), 497 | inset: draw-params.padding 498 | ) 499 | } 500 | ``` 501 | #box(inset: 1em, quill-gate) 502 | 503 | We can now use it like this: 504 | 505 | #example(scope: (draw-quill-gate: (gate, draw-params) => { 506 | let stroke = draw-params.wire 507 | let fill = if gate.fill != auto { gate.fill } else { draw-params.background } 508 | 509 | box( 510 | gate.content, 511 | fill: fill, stroke: (left: stroke, right: stroke), 512 | inset: draw-params.padding 513 | ) 514 | }), 515 | ```typ 516 | #quantum-circuit( 517 | 1, gate("Quill", draw-function: draw-quill-gate), 1, 518 | ) 519 | ``` 520 | ) 521 | 522 | 523 | The first argument for the draw function contains information about the gate. From that we read the gate's `content` (here `"Quill"`). We create a `box()` with the content and only specify the left and right edge stroke. In order for the circuit to look consistent, we read the circuit style from the draw-params. The key `draw-params.wire` contains the (per-circuit) global wire stroke setting as set through `quantum-circuit(wire: ...)`. Additionally, if a fill color has been specified for the gate, we want to use it. Otherwise, we use `draw-params.background` to be conform with for example dark-mode circuits. Finally, to create space, we add some inset to the box. The key `draw-params.padding` holds the (per-circuit) global gate padding length. 524 | 525 | It is generally possible to read any value from a gate that has been provided in the gate's constructor. Currently, `content`, `fill`, `radius`, `width`, `box` and `data` (containing the optional data argument that can be added in the #ref-fn("gate()") function) can be read from the gate. For multi-qubit gates, the key `multi` contains a dictionary with the keys `target` (specifying the relative target qubit for control wires), `num-qubits`, `wire-count` (the wire count for the control wire) and `extent` (the amount of length to extend above the first and below the last wire). 526 | 527 | All built-in gates are drawn with a dedicated `draw-function` and you can also take a look at the source code for ideas and hints. 528 | 529 | 530 | #pagebreak() 531 | = Function Documentation 532 | 533 | #[ 534 | 535 | #set text(size: 9pt) 536 | 537 | #show raw.where(block: false, lang: "typc"): box.with( 538 | fill: luma(240), 539 | inset: (x: 3pt, y: 0pt), 540 | outset: (y: 3pt), 541 | radius: 2pt, 542 | ) 543 | 544 | 545 | #show heading: set text(size: 1.2em) 546 | #show heading.where(level: 3): it => { align(center, it) } 547 | 548 | #columns(1,[ 549 | This section contains a complete reference for every function in *quill*. 550 | 551 | // #set raw(lang: none) 552 | #set heading(numbering: none) 553 | #{ 554 | 555 | let my-show-example = tidy.show-example.show-example.with( 556 | layout: (code, preview) => pad(x: 15em, grid( 557 | columns: (2fr, auto), 558 | align: horizon, 559 | code, 560 | preview 561 | )) 562 | ) 563 | 564 | let parse-module = tidy.parse-module.with( 565 | label-prefix: "quill:", 566 | scope: dictionary(quill), 567 | old-syntax: false 568 | ) 569 | let show-module = tidy.show-module.with( 570 | show-module-name: false, 571 | first-heading-level: 2, 572 | show-outline: false, 573 | style: dictionary(tidy.styles.default) + (show-example: my-show-example), 574 | sort-functions: none 575 | ) 576 | 577 | 578 | 579 | let show-outline = tidy.styles.default.show-outline.with(style-args: (enable-cross-references: true)) 580 | 581 | let docs = parse-module(read("/src/quantum-circuit.typ")) 582 | let docs-gates = parse-module(read("/src/gates.typ")) 583 | let docs-decorations = parse-module(read("/src/decorations.typ")) 584 | 585 | [*Quantum Circuit*] 586 | show-outline(docs) 587 | [*Gates*] 588 | show-outline(docs-gates) 589 | [*Decorations*] 590 | show-outline(docs-decorations) 591 | 592 | set text(size: .9em) 593 | 594 | show-module(docs) 595 | v(1cm) 596 | show-module(docs-gates) 597 | v(1cm) 598 | show-module(docs-decorations) 599 | 600 | let docs-tequila = parse-module(read("/src/tequila-impl.typ")) 601 | 602 | colbreak() 603 | 604 | heading(outlined: false)[Tequila] 605 | v(2mm) 606 | show-outline(docs-tequila) 607 | show-module(docs-tequila) 608 | 609 | } 610 | ]) 611 | 612 | ] 613 | 614 | 615 | #pagebreak() 616 | = Demo 617 | 618 | #show raw.where(block: true): set text(size: 0.9em) 619 | 620 | 621 | This section demonstrates the use of the *quantum-circuit* library by reproducing some figures from the famous book _Quantum Computation and Quantum Information_ by Nielsen and Chuang @nielsen_2022_quantum. 622 | 623 | == Quantum Teleportation 624 | Quantum teleportation circuit reproducing the Figure 4.15 in @nielsen_2022_quantum. 625 | #insert-example("../../examples/teleportation.typ") 626 | 627 | 628 | == Quantum Phase Estimation 629 | Quantum phase estimation circuit reproducing the Figure 5.2 in @nielsen_2022_quantum. 630 | #insert-example("../../examples/phase-estimation.typ") 631 | 632 | #pagebreak() 633 | 634 | 635 | == Quantum Fourier Transform: 636 | Circuit for performing the quantum Fourier transform, reproducing the Figure 5.1 in @nielsen_2022_quantum. 637 | #insert-example("../../examples/qft.typ") 638 | 639 | 640 | == Shor Nine Qubit Code 641 | 642 | Encoding circuit for the Shor nine qubit code. This diagram reproduces Figure 10.4 in @nielsen_2022_quantum 643 | 644 | #table(columns: (2fr, 1fr), align: horizon, stroke: none, 645 | block(raw({ 646 | let content = read("/examples/shor-nine-qubit-code.typ") 647 | content.slice(content.position("*")+1).trim()}, lang: "typ"), fill: gray.lighten(90%), inset: .8em), 648 | include("/examples/shor-nine-qubit-code.typ") 649 | ) 650 | 651 | #pagebreak() 652 | 653 | 654 | == Fault-Tolerant Measurement 655 | 656 | Circuit for performing fault-tolerant measurement (as Figure 10.28 in @nielsen_2022_quantum). 657 | #insert-example("../../examples/fault-tolerant-measurement.typ") 658 | 659 | 660 | == Fault-Tolerant Gate Construction 661 | The following two circuits reproduce figures from Exercise 10.66 and 10.68 on construction fault-tolerant $pi/8$ and Toffoli gates in @nielsen_2022_quantum. 662 | #insert-example("../../examples/fault-tolerant-pi8.typ") 663 | #insert-example("../../examples/fault-tolerant-toffoli1.typ") 664 | #insert-example("../../examples/fault-tolerant-toffoli2.typ") 665 | 666 | 667 | #pagebreak() 668 | 669 | 670 | 671 | = Tequila 672 | 673 | _Tequila_ is a submodule of *Quill* that adds a completely different way of building circuits. 674 | 675 | #example( 676 | ```typ 677 | #import tequila as tq 678 | 679 | #quantum-circuit( 680 | ..tq.build( 681 | tq.h(0), 682 | tq.cx(0, 1), 683 | tq.cx(0, 2), 684 | tq.measure(0, 3) 685 | ), 686 | [\ ], [\ ], [\ ], setwire(2) 687 | ) 688 | ``` 689 | ) 690 | This is similar to how _QASM_ and _Qiskit_ work: gates are successively applied to the circuit which is then layed out automatically by packing gates as tightly as possible. We start by calling the `tq.build()` function and filling it with quantum operations. This returns a collection of gates which we expand into the circuit with the `..` syntax. 691 | Now, we still have the option to add annotations, groups, slices, or even more gates via manual placement. 692 | 693 | The syntax works analog to Qiskit. Available gates are `x`, `y`, `z`, `h`, `s`, `sdg`, `sx`, `sxdg`, `t`, `tdg`, `p`, `rx`, `ry`, `rz`, `u`, `cx`, `cz`, and `swap`. With `barrier`, an invisible barrier can be inserted to prevent gates on different qubits to be packed tightly. Finally, with `tq.gate` and `tq.mqgate`, a generic gate can be created. These two accept the same styling arguments as the normal `gate` (or `mqgate`). 694 | 695 | Also like Qiskit, all qubit arguments support ranges, e.g., `tq.h(range(5))` adds a Hadamard gate on the first five qubits and `tq.cx((0, 1), (1, 2))` adds two #smallcaps[cx] gates: one from qubit 0 to 1 and one from qubit 1 to 2. 696 | 697 | With Tequila, it is easy to build templates for quantum circuits and to compose circuits of various building blocks. For this purpose, `tq.build()` and the built-in templates all feature optional `x` and `y` arguments to allow placing a subcircuit at an arbitrary position of the circuit. 698 | As an example, Tequila provides a `tq.graph-state()` template for quickly drawing graph state preparation circuits. 699 | 700 | The following example demonstrates how to compose multiple subcircuits. 701 | 702 | #example(scale: 74%, 703 | ```typ 704 | #import tequila as tq 705 | 706 | #quantum-circuit( 707 | ..tq.graph-state((0, 1), (1, 2)), 708 | ..tq.build(y: 3, 709 | tq.p($pi$, 0), 710 | tq.cx(0, (1, 2)), 711 | ), 712 | ..tq.graph-state(x: 6, y: 2, invert: true, (0, 1), (0, 2)), 713 | gategroup(x: 1, 3, 3), 714 | gategroup(x: 1, y: 3, 3, 3), 715 | gategroup(x: 6, y: 2, 3, 3), 716 | slice(x: 5) 717 | ) 718 | ``` 719 | ) 720 | 721 | #block(breakable: false)[ 722 | To demonstrate the creation of templates, we give the (simplified) implementation of `tq.graph-state()` in the following: 723 | 724 | #example( 725 | ```typ 726 | #let graph-state(..edges, x: 1, y: 0) = tq.build( 727 | x: x, y: y, 728 | tq.h(range(num-qubits)), 729 | edges.map(edge => tq.cz(..edge)) 730 | ) 731 | ``` 732 | ) 733 | ] 734 | 735 | // This makes it easier to generate certain classes of circuits, for example in order to have a good starting point for a more complex circuit. 736 | 737 | Another built-in building block is `tq.qft(n)` for inserting a quantum fourier transform (QFT) on $n$ qubits. 738 | 739 | #example(scale: 80%, 740 | ```typ 741 | #quantum-circuit(..tequila.qft(y: 1, 4)) 742 | ``` 743 | ) 744 | 745 | 746 | 747 | #bibliography("references.bib") 748 | --------------------------------------------------------------------------------