├── .gitignore
├── LICENSE.txt
├── README.md
├── examples
├── entanglement-circuit.pdf
├── entanglement-circuit.png
├── entanglement-circuit.svg
├── entanglement.pdf
├── entanglement.png
├── entanglement.svg
├── interference-circuit.pdf
├── interference-circuit.png
├── interference-circuit.svg
├── interference.pdf
├── interference.png
├── interference.svg
├── no-entanglement-circuit.pdf
├── no-entanglement-circuit.png
├── no-entanglement-circuit.svg
├── no-entanglement.pdf
├── no-entanglement.png
├── no-entanglement.svg
├── no-interference-circuit.pdf
├── no-interference-circuit.png
├── no-interference-circuit.svg
├── no-interference.pdf
├── no-interference.png
├── no-interference.svg
└── render_examples.py
├── feynman_path
├── __init__.py
├── __main__.py
├── command.py
└── diagram.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 | /*.egg-info
3 | /build
4 |
5 | # Mac files
6 | .DS_Store
7 |
8 | # cache files
9 | __pycache__/
10 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2021 Casey Duckering
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Feynman Path Sum Diagram for Quantum Circuits
2 |
3 | A visualization tool for the Feynman Path Sum applied to quantum circuits.
4 | The [path integral formulation](https://en.wikipedia.org/wiki/Path_integral_formulation) is an interpretation of quantum mechanics that can aid in understanding superposition and interference.
5 |
6 | Path sum:
7 |
8 |
9 |
10 | Circuit diagram:
11 |
12 |
13 |
14 | How to read a path sum diagram:
15 | - Time flows from left to right as gates are executed on qubits.
16 | - Arrows transition from one state to another and traversing the arrows gives a path to an output.
17 | - Two diverging arrows indicate a split into two potential outcomes.
18 | - An orange arrow indicates a negative sign is added to that outcome.
19 | - When two arrows converge, the amplitudes are summed.
20 | - Quantum interference is when positive and negative amplitudes cancel in this sum.
21 | - The rightmost column lists the possible measurement outcomes along with the final probability amplitudes of measuring each outcome.
22 |
23 | See also: [Bloch sphere visualization](https://github.com/cduck/bloch_sphere)
24 |
25 | # Install
26 |
27 | feynman\_path is available on PyPI:
28 |
29 | ```bash
30 | python3 -m pip install feynman_path
31 | ```
32 |
33 | ## Prerequisites
34 |
35 | Several non-python tools are used to generate the graphics and various output formats.
36 | These non-python dependencies are listed below and platform-specific installation instructions can be found [here](https://github.com/cduck/latextools/#prerequisites).
37 |
38 | - LaTeX: A distribution of LaTeX that provides the `pdflatex` command needs to be installed separately. Used to generate the gate and state labels.
39 | - [pdf2svg](https://github.com/dawbarton/pdf2svg): Used to convert the LaTeX expressions into SVG elements.
40 | - [Inkscape](https://inkscape.org/) (optional): Only required to convert the output to PDF format.
41 | - [Cairo](https://www.cairographics.org/download/) (optional): Only required to convert the output to PNG format.
42 |
43 | ### Ubuntu
44 |
45 | ```bash
46 | sudo apt install texlive pdf2svg inkscape libcairo2 # Or texlive-latex-recommended, or texlive-latex-extra
47 | ```
48 |
49 | ### macOS
50 |
51 | Using [homebrew](https://brew.sh/):
52 |
53 | ```bash
54 | brew install --cask mactex inkscape
55 | brew install pdf2svg cairo
56 | ```
57 |
58 |
59 | # Usage
60 |
61 | This package provides a command line tool to generate diagrams.
62 |
63 | ### Examples
64 | ```bash
65 | feynman_path interference 2 h0 cnot0,1 z1 h0 h1 cnot1,0 h1
66 | ```
67 |
68 |
69 | ```bash
70 | feynman_path interference 2 h0 cnot0,1 z1 h0 h1 cnot1,0 h1 --circuit
71 | ```
72 |
73 |
74 | ### Command line options
75 | ```
76 | $ feynman_path -h
77 | usage: feynman_path [-h] [--svg] [--png] [--pdf] [--sequence] [--circuit]
78 | [--scale SCALE] [--verbose]
79 | name n_qubits gate [gate ...]
80 |
81 | Renders a Feynman path sum diagram for a sequence of quantum gates.
82 |
83 | positional arguments:
84 | name The file name to save (excluding file extension)
85 | n_qubits The number of qubits in the quantum circuit
86 | gate List of gates to apply (e.g. h0 z1 cnot0,1)
87 |
88 | optional arguments:
89 | -h, --help show this help message and exit
90 | --svg Save diagram as an SVG image (default)
91 | --png Save diagram as a PNG image
92 | --pdf Save diagram as a PDF document
93 | --sequence Save a sequence of images that build up the diagram from left
94 | to right as -nn.svg/png/pdf
95 | --circuit Save a standard quantum circuit diagram named
96 | -circuit.svg/png/pdf instead of a Feynman path diagram
97 | --scale SCALE Scales the resolution of the diagram when saved as a PNG
98 | --verbose Print extra progress information
99 | ```
100 |
101 | ### Python Package
102 | feynman\_path also provides a Python 3 package as an alternative to the command line tool. Diagrams can be viewed directly in a Jupyter notebook or saved.
103 |
104 | ```python
105 | import feynman_path
106 |
107 | n_qubits = 3
108 | font = 12
109 | ws_label = 4+0.55*n_qubits # Label width relative to font size
110 | w_time = 60+ws_label*font # Diagram column width
111 | f = feynman_path.Diagram(
112 | n_qubits, font=font, ws_label=ws_label, w_time=w_time)
113 |
114 | f.perform_h(0)
115 | f.perform_cnot(0, 1)
116 | f.perform_z(1)
117 | f.perform_cnot(1, 2, pre_latex=r'\color{red!80!black}')
118 | f.perform_h(0)
119 | f.perform_h(1)
120 | f.perform_cnot(1, 0)
121 |
122 | f.draw() # Display in Jupyter
123 | ```
124 | ```python
125 | f.draw().save_svg('output.svg') # Save SVG
126 | f.draw().set_pixel_scale(2).save_png('output.png') # Save PNG
127 | import latextools
128 | latextools.svg_to_pdf(f.draw()).save('output.pdf') # Save PDF
129 | ```
130 |
131 |
132 | See [examples/render\_examples.py](https://github.com/cduck/feynman_path/blob/master/examples/render_examples.py) for more example code.
133 |
134 | # Examples
135 |
136 | ### Using the CNOT gate to entangle qubits
137 | The [CNOT gate](https://en.wikipedia.org/wiki/Controlled_NOT_gate) (⋅–⨁) can be used to entangle two qubits, creating a [Bell pair](https://en.wikipedia.org/wiki/Bell_state), but for certain input qubit states, the CNOT will have no effect.
138 |
139 | **Create a Bell pair by using a CNOT on the |+0⟩ state (q0=|+⟩, q1=|0⟩):**
140 |
141 |
142 |
143 |
144 |
145 | Note the output (rightmost) column is an entangled state: |00⟩+|11⟩
146 |
147 | ```bash
148 | feynman_path no-entanglement 2 h0 cnot0,1 h0 h1
149 | ```
150 |
151 | **Fail to create a bell pair by using a CNOT on the |++⟩ state (q0=|+⟩, q1=|+⟩):**
152 |
153 |
154 |
155 |
156 |
157 | ```bash
158 | feynman_path no-entanglement 2 h0 h1 cnot0,1 h0 h1
159 | ```
160 |
161 | Note the output (rightmost) column is a separable state: |00⟩
162 |
163 | ### Copying an intermediate qubit state onto an ancilla ruins interference
164 | In classical computing, it is common to inspect intermediate steps of a computation. This can be very useful for debugging. In quantum computing however, this destroys the effect of interference. We can use a [CNOT gate](https://en.wikipedia.org/wiki/Controlled_NOT_gate) (⋅–⨁) to copy an intermediate value onto another qubit to inspect later. Shown below, copying the intermediate value of q1 to q2 changes the output of q0, q1.
165 |
166 | **Original circuit that compute the output q0=1, q1=0:**
167 |
168 |
169 |
170 |
171 |
172 | ```bash
173 | feynman_path interference 2 h0 cnot0,1 z1 h0 h1 cnot1,0 h1
174 | ```
175 |
176 | **The addition of CNOT1,2 to inspect the intermediate value of q1 changes the output of q0:**
177 |
178 |
179 |
180 |
181 |
182 | Note how the path diagram is the same except the arrows at H1 are now split into the upper and lower halves of the diagram and don't interfere anymore.
183 |
184 | ```bash
185 | feynman_path no-interference 3 h0 cnot0,1 z1 cnot1,2 h0 h1 cnot1,0 h1
186 | ```
187 |
--------------------------------------------------------------------------------
/examples/entanglement-circuit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/entanglement-circuit.pdf
--------------------------------------------------------------------------------
/examples/entanglement-circuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/entanglement-circuit.png
--------------------------------------------------------------------------------
/examples/entanglement-circuit.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/entanglement.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/entanglement.pdf
--------------------------------------------------------------------------------
/examples/entanglement.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/entanglement.png
--------------------------------------------------------------------------------
/examples/entanglement.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/interference-circuit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/interference-circuit.pdf
--------------------------------------------------------------------------------
/examples/interference-circuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/interference-circuit.png
--------------------------------------------------------------------------------
/examples/interference-circuit.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/interference.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/interference.pdf
--------------------------------------------------------------------------------
/examples/interference.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/interference.png
--------------------------------------------------------------------------------
/examples/no-entanglement-circuit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/no-entanglement-circuit.pdf
--------------------------------------------------------------------------------
/examples/no-entanglement-circuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/no-entanglement-circuit.png
--------------------------------------------------------------------------------
/examples/no-entanglement-circuit.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/no-entanglement.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/no-entanglement.pdf
--------------------------------------------------------------------------------
/examples/no-entanglement.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/no-entanglement.png
--------------------------------------------------------------------------------
/examples/no-interference-circuit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/no-interference-circuit.pdf
--------------------------------------------------------------------------------
/examples/no-interference-circuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/no-interference-circuit.png
--------------------------------------------------------------------------------
/examples/no-interference-circuit.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/no-interference.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/no-interference.pdf
--------------------------------------------------------------------------------
/examples/no-interference.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cduck/feynman_path/d8d98207c5ad531801a0175c4a3e3c4534cffef1/examples/no-interference.png
--------------------------------------------------------------------------------
/examples/render_examples.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from feynman_path import command
4 |
5 |
6 | print('\nRendering interference')
7 | #$ feynman_path interference 2 h0 cnot0,1 z1 h0 h1 cnot1,0 h1
8 | # Save all formats and the corresponding circuit diagram
9 | for circuit in [False, True]:
10 | command.main('interference',
11 | 2,
12 | 'h0 cnot0,1 z1 h0 h1 cnot1,0 h1'.split(),
13 | svg=True,
14 | png=True,
15 | pdf=True,
16 | scale=3,
17 | sequence=False,
18 | circuit=circuit)
19 |
20 |
21 | print('\nRendering no-interference')
22 | #$ feynman_path no-interference 3 h0 cnot0,1 z1 cnot1,2 h0 h1 cnot1,0 h1
23 | # Save the circuit diagram
24 | command.main('no-interference',
25 | 3,
26 | 'h0 cnot0,1 z1 cnot1,2 h0 h1 cnot1,0 h1'.split(),
27 | svg=True,
28 | png=True,
29 | pdf=True,
30 | scale=3,
31 | sequence=False,
32 | circuit=True)
33 | # Manually create the path diagram so we can highlight CNOT_12 in red
34 | f = command.draw_diagram(3, [])
35 | f.perform_h(0)
36 | f.perform_cnot(0, 1)
37 | f.perform_z(1)
38 | f.perform_cnot(1, 2, pre_latex=r'\color{red!80!black}')
39 | f.perform_h(0)
40 | f.perform_h(1)
41 | f.perform_cnot(1, 0)
42 | f.perform_h(1)
43 | command.save_formats_from_svg(f.draw(), 'no-interference',
44 | svg=True, png=True, pdf=True, scale=3)
45 |
46 |
47 | print('\nRendering entanglement')
48 | #$ feynman_path no-entanglement 2 h0 cnot0,1 h0 h1
49 | # Save all formats and the corresponding circuit diagram
50 | for circuit in [False, True]:
51 | command.main('entanglement',
52 | 2,
53 | 'h0 cnot0,1 h0 h1'.split(),
54 | svg=True,
55 | png=True,
56 | pdf=True,
57 | scale=3,
58 | sequence=False,
59 | circuit=circuit)
60 |
61 |
62 | print('\nRendering no-entanglement')
63 | #$ feynman_path no-entanglement 2 h0 h1 cnot0,1 h0 h1
64 | # Save all formats and the corresponding circuit diagram
65 | for circuit in [False, True]:
66 | command.main('no-entanglement',
67 | 2,
68 | 'h0 h1 cnot0,1 h0 h1'.split(),
69 | svg=True,
70 | png=True,
71 | pdf=True,
72 | scale=3,
73 | sequence=False,
74 | circuit=circuit)
75 |
--------------------------------------------------------------------------------
/feynman_path/__init__.py:
--------------------------------------------------------------------------------
1 | from .diagram import Diagram
2 |
--------------------------------------------------------------------------------
/feynman_path/__main__.py:
--------------------------------------------------------------------------------
1 | from .command import run_from_command_line
2 |
3 | if __name__ == '__main__':
4 | run_from_command_line()
5 |
--------------------------------------------------------------------------------
/feynman_path/command.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import latextools
3 |
4 | from . import diagram
5 |
6 |
7 | def parse_gate(gate_str):
8 | name = gate_str.translate({ord(digit): '-' for digit in '0123456789'}
9 | ).split('-')[0]
10 | args = tuple(int(arg) for arg in gate_str[len(name):].split(','))
11 | return name, args
12 |
13 | def draw_diagram(n_qubits, gates):
14 | '''Draw a path sum diagram'''
15 | ws_label = 4+0.55*n_qubits
16 | f = diagram.Diagram(
17 | n_qubits,
18 | font=12, ws_label=ws_label, w_time=60+ws_label*12
19 | )
20 |
21 | for gate in gates:
22 | name, args = parse_gate(gate)
23 | getattr(f, f'perform_{name}')(*args)
24 |
25 | return f
26 |
27 | def draw_circuit_pdf(n_qubits, gates):
28 | '''Draw a basic circuit diagram using qcircuit in LaTeX'''
29 | lines = [fr'\lstick{{q_{{{i}}}}}' for i in range(n_qubits)]
30 | for gate in gates:
31 | name, args = parse_gate(gate)
32 | if len(args) == 1:
33 | name = name[:1].upper() + name[1:]
34 | for i in range(n_qubits):
35 | if i == args[0]:
36 | lines[i] += fr' & \gate{{{name}}}'
37 | else:
38 | lines[i] += fr' & \qw'
39 | elif len(args) == 2 and name.upper().startswith('C'):
40 | name = name[:1].upper() + name[1:]
41 | targ = (r' & \targ' if name.upper() == 'CNOT'
42 | else fr' & \gate{{{name}}}')
43 | ctrl_len = args[1] - args[0]
44 | for i in range(n_qubits):
45 | if i == args[0]:
46 | lines[i] += fr' & \ctrl{{{ctrl_len}}}'
47 | elif i == args[1]:
48 | lines[i] += targ
49 | else:
50 | lines[i] += fr' & \qw'
51 | else:
52 | raise NotImplementedError(
53 | f'Unsupported circuit diagram gate "{name}"')
54 | for i in range(n_qubits):
55 | lines[i] += r' & \qw \\'
56 |
57 | pdf = latextools.render_qcircuit(
58 | '\n'.join(lines),
59 | lpad=22, rpad=22, tpad=6, bpad=6, r=0.5, c=1,
60 | const_row=True, const_col=True, const_size=True)
61 | return pdf
62 |
63 | def save_formats_from_pdf(p, name, msg='', svg=False, png=False, pdf=False,
64 | scale=1):
65 | if pdf:
66 | p.save(f'{name}.pdf')
67 | print(f'Saved "{name}.pdf"{msg}')
68 | if svg or png:
69 | d = p.as_svg().as_drawing()
70 | if svg:
71 | d.save_svg(f'{name}.svg')
72 | print(f'Saved "{name}.svg"{msg}')
73 | if png:
74 | d.set_pixel_scale(scale)
75 | d.save_png(f'{name}.png')
76 | print(f'Saved "{name}.png"{msg}')
77 |
78 | def save_formats_from_svg(d, name, msg='', svg=False, png=False, pdf=False,
79 | scale=1):
80 | if pdf:
81 | p = latextools.svg_to_pdf(d)
82 | p.save(f'{name}.pdf')
83 | print(f'Saved "{name}.pdf"{msg}')
84 | if svg:
85 | d.save_svg(f'{name}.svg')
86 | print(f'Saved "{name}.svg"{msg}')
87 | if png:
88 | d.set_pixel_scale(scale)
89 | d.save_png(f'{name}.png')
90 | print(f'Saved "{name}.png"{msg}')
91 |
92 | def main(name, n_qubits, gates, svg=False, png=False, pdf=False, sequence=False,
93 | circuit=False, scale=1):
94 | gates_sequence = [gates]
95 | if sequence:
96 | gates_sequence = [gates[:i] for i in range(len(gates)+1)]
97 |
98 | for i, gates in enumerate(gates_sequence):
99 | name_seq = f'{name}-{i:02}' if sequence else name
100 | gates_str = ' '.join(gates)
101 | if circuit:
102 | name_seq += '-circuit'
103 | # Draw a circuit diagram instead
104 | p = draw_circuit_pdf(n_qubits, gates)
105 | save_formats_from_pdf(
106 | p, name_seq, msg=f' with gate sequence "{gates_str}"',
107 | svg=svg, png=png, pdf=pdf, scale=scale
108 | )
109 | else:
110 | # Draw a path diagram
111 | d = draw_diagram(n_qubits, gates).draw()
112 | save_formats_from_svg(
113 | d, name_seq, msg=f' with gate sequence "{gates_str}"',
114 | svg=svg, png=png, pdf=pdf, scale=scale
115 | )
116 |
117 | def run_from_command_line():
118 | parser = argparse.ArgumentParser(
119 | description='Renders a Feynman path sum diagram for a sequence of '
120 | 'quantum gates.')
121 | parser.add_argument('name', type=str, help=
122 | 'The file name to save (excluding file extension)')
123 | parser.add_argument('n_qubits', type=int, help=
124 | 'The number of qubits in the quantum circuit')
125 | parser.add_argument('gate', type=str, nargs='+', help=
126 | 'List of gates to apply (e.g. h0 z1 cnot0,1)')
127 | parser.add_argument('--svg', action='store_true', help=
128 | 'Save diagram as an SVG image (default)')
129 | parser.add_argument('--png', action='store_true', help=
130 | 'Save diagram as a PNG image')
131 | parser.add_argument('--pdf', action='store_true', help=
132 | 'Save diagram as a PDF document')
133 | parser.add_argument('--sequence', action='store_true', help=
134 | 'Save a sequence of images that build up the diagram from left to right as -nn.svg/png/pdf')
135 | parser.add_argument('--circuit', action='store_true', help=
136 | 'Save a standard quantum circuit diagram named -circuit.svg/png/pdf instead of a Feynman path diagram')
137 | parser.add_argument('--scale', type=float, default=1, help=
138 | 'Scales the resolution of the diagram when saved as a PNG')
139 | parser.add_argument('--verbose', action='store_true', help=
140 | 'Print extra progress information')
141 |
142 | args = parser.parse_args()
143 | # Enable verbose
144 | if args.verbose:
145 | diagram.VERBOSE = True
146 | # If no output formats are selected, default to SVG
147 | svg = args.svg or (not args.png and not args.pdf)
148 |
149 | main(name=args.name, n_qubits=args.n_qubits, gates=args.gate, svg=svg,
150 | png=args.png, pdf=args.pdf, sequence=args.sequence,
151 | circuit=args.circuit, scale=args.scale)
152 |
--------------------------------------------------------------------------------
/feynman_path/diagram.py:
--------------------------------------------------------------------------------
1 | import functools
2 | import itertools
3 | import sympy
4 | from sympy.printing.latex import latex
5 | import drawsvg as draw
6 | import latextools
7 |
8 | VERBOSE = False
9 |
10 |
11 | def _disp(svg, msg):
12 | '''Show rendering progress when VERBOSE is True.'''
13 | if not VERBOSE: return svg
14 | try:
15 | from IPython.display import display
16 | except ImportError:
17 | display = print
18 | class DispWrap:
19 | def _repr_svg_(self): return d._repr_svg_()
20 | def __repr__(self): return msg
21 | d = draw.Drawing(50, 20, origin='center')
22 | d.draw(svg, center=True)
23 | display(DispWrap())
24 | return svg
25 |
26 | @functools.lru_cache(maxsize=None)
27 | def render_cache(latex):
28 | return _disp(latextools.render_snippet(
29 | latex,
30 | latextools.pkg.qcircuit,
31 | latextools.pkg.xcolor,
32 | commands=[latextools.cmd.all_math],
33 | pad=1,
34 | ).as_svg(),
35 | msg=f'Rendered LaTeX element: {latex}')
36 |
37 | def sympy_to_math_mode(sym):
38 | '''Returns the latex math code (without surrounding $) for the sympy symbol.
39 | '''
40 | return latex(sym, mode='plain', fold_frac_powers=True)
41 |
42 | def render_label(amp_sym, label):
43 | '''Render preset sympy expressions as nice LaTeX equations.'''
44 | presets = {
45 | sympy.sympify(0): '0',
46 | sympy.sympify(1): '1',
47 | sympy.sympify(-1): '-1',
48 | }
49 | if amp_sym in presets:
50 | amp_latex = presets[amp_sym]
51 | else:
52 | # Generate latex for any ±1 / sqrt(2) ** x
53 | amp_latex = ''
54 | give_up = False
55 | amp_sym_orig = amp_sym
56 | if abs(amp_sym) == -amp_sym:
57 | amp_latex += '-'
58 | amp_sym = -amp_sym
59 | elif abs(amp_sym) == amp_sym:
60 | pass
61 | else:
62 | give_up = True
63 | if not give_up:
64 | x = 0
65 | sqrt2 = sympy.sqrt(2)
66 | while amp_sym < 1:
67 | amp_sym = amp_sym * sqrt2
68 | x += 1
69 | if amp_sym != 1:
70 | give_up = True
71 | else:
72 | inner = f'{2**(x//2)}' if x // 2 > 0 else ''
73 | inner += r'\sqrt{2}' if x % 2 == 1 else ''
74 | amp_latex += fr'\frac1{{{inner}}}'
75 | if give_up:
76 | # The expression displayed may not be simplified in the desired way.
77 | amp_latex = sympy_to_math_mode(amp_sym_orig)
78 | return render_cache(fr'${amp_latex}\ket{{{label}}}$')
79 |
80 |
81 | class Diagram:
82 | def __init__(self, n_qubits, init_state=None,
83 | w_time = 120,
84 | h_state = 40,
85 | font = 12,
86 | gate_font = 16,
87 | ws_label = 6,
88 | arrow_space = 1):
89 | if arrow_space > ws_label/2:
90 | arrow_space = ws_label/2
91 | self.w_time = w_time
92 | self.h_state = h_state
93 | self.font = font
94 | self.gate_font = gate_font
95 | self.w_label = self.font * ws_label
96 | self.arrow_off = self.w_label/2 * arrow_space
97 |
98 | self.d = draw.Group()
99 | self.arrows = {}
100 | self.possible_states = [
101 | ''.join(map(str, qs[::-1]))
102 | for qs in itertools.product(range(2), repeat=n_qubits)
103 | ]
104 | self.num_states = len(self.possible_states)
105 |
106 | if init_state is None:
107 | init_state = {'0'*n_qubits: 1}
108 | self.state_sequence = [init_state]
109 | self.draw_states()
110 |
111 | def draw(self):
112 | w = (len(self.state_sequence)-1) * self.w_time + self.w_label - self.font*0.5
113 | h = (self.num_states-1) * self.h_state + self.font*2 + self.gate_font*3
114 | x = -self.w_label/2 + self.font
115 | y = -(self.num_states-1)/2 * self.h_state - self.font*1.5
116 | d = draw.Drawing(w, h, origin=(x, y))
117 | d.append(self.d)
118 | return d
119 | def _repr_html_(self):
120 | return self.draw()._repr_html_()
121 | def _repr_svg_(self):
122 | return self.draw()._repr_svg_()
123 |
124 | def state_xy(self, key, time):
125 | state_idx = self.possible_states.index(key)
126 | x = time * self.w_time
127 | y = ((self.num_states-1)/2 - state_idx) * self.h_state
128 | return x, y
129 |
130 | def transition_text(self, g, start_time, label):
131 | x = (start_time+0.5) * self.w_time
132 | y = (self.num_states-1)/2 * self.h_state + self.font/2+self.gate_font*3/2
133 | g.draw(render_cache(fr'${label}$'),
134 | x=x, y=y, scale=self.gate_font/12, center=True)
135 |
136 | def state_text(self, g, time, key, amp=1):
137 | state_idx = self.possible_states.index(key)
138 | x, y = self.state_xy(key, time)
139 | g.draw(render_label(sympy.sympify(amp), key),
140 | x=x+self.w_label/2-self.font*0.2, y=y, scale=self.font/12, center=True, text_anchor='end')
141 | if abs(float(amp)) < 1e-8:
142 | # Draw red X over it
143 | ys = self.font/2*1.4
144 | xs = self.w_label/2*0.7
145 | xf = self.font
146 | g.append(draw.Line(x+xf-xs, y-ys, x+xf+xs, y+ys, stroke='red', stroke_width=1))
147 | g.append(draw.Line(x+xf-xs, y+ys, x+xf+xs, y-ys, stroke='red', stroke_width=1))
148 |
149 | def make_arrow(self, color):
150 | key = color
151 | if key in self.arrows:
152 | return self.arrows[key]
153 | arrow = draw.Marker(-0.1, -0.5, 1.1, 0.5, scale=4)
154 | arrow.append(draw.Lines(1, -0.5, 1, 0.5, 0, 0, fill=color, close=True))
155 | self.arrows[key] = arrow
156 | return arrow
157 |
158 | def straight_arrow(self, g, color, *xy_list, width=1):
159 | rev_xy = xy_list[::-1]
160 | rev_xy = [rev_xy[i+1-2*(i%2)] for i in range(len(rev_xy))]
161 | w = 3 * width
162 | g.append(draw.Line(*rev_xy,
163 | stroke=color, stroke_width=w, fill='none',
164 | # Pull the line behind the arrow
165 | stroke_dasharray=f'0 {w*4/2} 1000000',
166 | marker_start=self.make_arrow(color)))
167 |
168 | def gate_arrow(self, g, time1, key1, key2, amp=1):
169 | w = float(abs(amp))
170 | x1, y1 = self.state_xy(key1, time1)
171 | x2, y2 = self.state_xy(key2, time1+1)
172 | x1 += self.arrow_off
173 | x2 -= self.arrow_off
174 | xx1 = x1 + self.w_label/2 - self.arrow_off
175 | xx2 = x2 - self.w_label/2 + self.arrow_off
176 | yy1 = y1 + (y2-y1)*(xx1-x1)/(x2-x1)
177 | yy2 = y2 - (y2-y1)*(x2-xx2)/(x2-x1)
178 | color = '#26f'
179 | if abs(float(amp) - abs(float(amp))) >= 1e-8:
180 | color = '#e70'
181 | self.straight_arrow(g, color, xx1, yy1, xx2, yy2, width=w)
182 |
183 | def draw_states(self):
184 | t = len(self.state_sequence)-1
185 | for key, amp in self.state_sequence[-1].items():
186 | self.state_text(self.d, t, key, amp=amp)
187 |
188 | def add_states(self, new_state):
189 | self.state_sequence.append(new_state)
190 | self.draw_states()
191 | clean_state = {
192 | key: amp
193 | for key, amp in new_state.items()
194 | if abs(float(amp)) >= 1e-8
195 | }
196 | self.state_sequence[-1] = clean_state
197 |
198 | def perform_h(self, q_i, *, pre_latex=f'', name='H'):
199 | new_state = {}
200 | t = len(self.state_sequence)-1
201 | for key, amp in self.state_sequence[-1].items():
202 | is_one = key[q_i] == '1'
203 | digits = list(key)
204 | digits[q_i] = '0'
205 | zero = ''.join(digits)
206 | digits[q_i] = '1'
207 | one = ''.join(digits)
208 | zero_amp = 1/sympy.sqrt(2)
209 | one_amp = -zero_amp if is_one else zero_amp
210 | self.gate_arrow(self.d, t, key, zero, amp=zero_amp)
211 | self.gate_arrow(self.d, t, key, one, amp=one_amp)
212 | if zero not in new_state: new_state[zero] = 0
213 | if one not in new_state: new_state[one] = 0
214 | new_state[zero] += amp*zero_amp
215 | new_state[one] += amp*one_amp
216 | self.transition_text(self.d, t, f'{pre_latex}{name}_{q_i}')
217 | self.add_states(new_state)
218 |
219 | def perform_cnot(self, qi1, qi2, *, pre_latex=f'', name='CNOT'):
220 | new_state = {}
221 | t = len(self.state_sequence)-1
222 | for key, amp in self.state_sequence[-1].items():
223 | is_one = key[qi1] == '1'
224 | is_targ_one = key[qi2] == '1'
225 | digits = list(key)
226 | if is_one:
227 | digits[qi2] = '01'[not is_targ_one]
228 | new_key = ''.join(digits)
229 | self.gate_arrow(self.d, t, key, new_key, amp=1)
230 | if new_key not in new_state: new_state[new_key] = 0
231 | new_state[new_key] += amp
232 | self.transition_text(self.d, t, f'{pre_latex}{name}_{{{qi1}{qi2}}}')
233 | self.add_states(new_state)
234 |
235 | def perform_z(self, q_i, *, pre_latex=f'', name='Z'):
236 | new_state = {}
237 | t = len(self.state_sequence)-1
238 | for key, amp in self.state_sequence[-1].items():
239 | is_one = key[q_i] == '1'
240 | new_amp = -amp if is_one else amp
241 | self.gate_arrow(self.d, t, key, key, amp=new_amp/amp)
242 | if key not in new_state: new_state[key] = 0
243 | new_state[key] += new_amp
244 | self.transition_text(self.d, t, f'{pre_latex}{name}_{{{q_i}}}')
245 | self.add_states(new_state)
246 |
247 | def perform_x(self, q_i, *, pre_latex=f'', name='X'):
248 | new_state = {}
249 | t = len(self.state_sequence)-1
250 | for key, amp in self.state_sequence[-1].items():
251 | is_one = key[q_i] == '1'
252 | digits = list(key)
253 | digits[q_i] = '01'[not is_one]
254 | new_key = ''.join(digits)
255 | self.gate_arrow(self.d, t, key, new_key, amp=1)
256 | if new_key not in new_state:
257 | new_state[new_key] = 0
258 | new_state[new_key] += amp
259 | self.transition_text(self.d, t, f'{pre_latex}{name}_{{{q_i}}}')
260 | self.add_states(new_state)
261 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | import logging
3 | logger = logging.getLogger(__name__)
4 |
5 | name = 'feynman_path'
6 | package_name = 'feynman_path'
7 | version = '0.2.0'
8 |
9 | try:
10 | with open('README.md', 'r') as f:
11 | long_desc = f.read()
12 | except:
13 | logger.warning('Could not open README.md. long_description will be set to None.')
14 | long_desc = None
15 |
16 | setup(
17 | name = package_name,
18 | packages = find_packages(),
19 | entry_points = {
20 | 'console_scripts': [
21 | 'feynman_path=feynman_path.command:run_from_command_line',
22 | ]},
23 | version = version,
24 | description = 'Visualization tool for the Feynman Path Sum applied to quantum circuits',
25 | long_description = long_desc,
26 | long_description_content_type = 'text/markdown',
27 | author = 'Casey Duckering',
28 | #author_email = '',
29 | url = f'https://github.com/cduck/{name}',
30 | download_url = f'https://github.com/cduck/{name}/archive/{version}.tar.gz',
31 | keywords = ['quantum computing', 'feynman path', 'path sum', 'jupyter'],
32 | classifiers = [
33 | 'License :: OSI Approved :: MIT License',
34 | 'Development Status :: 3 - Alpha',
35 | 'Programming Language :: Python :: 3',
36 | 'Framework :: IPython',
37 | 'Framework :: Jupyter',
38 | ],
39 | install_requires = [
40 | 'drawSvg~=2.0',
41 | 'latextools~=0.5',
42 | 'sympy~=1.7',
43 | ],
44 | )
45 |
46 |
--------------------------------------------------------------------------------