From 5ec5ba382204b9d67eb2269d2020a553e099e25f Mon Sep 17 00:00:00 2001 From: gsilvi Date: Tue, 5 Jul 2022 13:03:32 +0200 Subject: [PATCH 1/5] implemented perform_y added 2 new colors to handle the complex phase --- feynman_path/diagram.py | 59 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/feynman_path/diagram.py b/feynman_path/diagram.py index 16066ff..4ce5b30 100755 --- a/feynman_path/diagram.py +++ b/feynman_path/diagram.py @@ -4,7 +4,7 @@ from sympy.printing.latex import latex import drawSvg as draw import latextools - +PI = 3.1415926535 VERBOSE = False @@ -138,7 +138,7 @@ def state_text(self, g, time, key, amp=1): x, y = self.state_xy(key, time) g.draw(render_label(sympy.sympify(amp), key), x=x+self.w_label/2-self.font*0.2, y=y, scale=self.font/12, center=True, text_anchor='end') - if abs(float(amp)) < 1e-8: + if self.get_abs(amp) < 1e-8: # Draw red X over it ys = self.font/2*1.4 xs = self.w_label/2*0.7 @@ -175,10 +175,41 @@ def gate_arrow(self, g, time1, key1, key2, amp=1): xx2 = x2 - self.w_label/2 + self.arrow_off yy1 = y1 + (y2-y1)*(xx1-x1)/(x2-x1) yy2 = y2 - (y2-y1)*(x2-xx2)/(x2-x1) - color = '#26f' - if abs(float(amp) - abs(float(amp))) >= 1e-8: - color = '#e70' + #map the amplitude to a "wheel" of colors + color = self.wheel_color(amp) + self.straight_arrow(g, color, xx1, yy1, xx2, yy2, width=w) + + def wheel_color(self,amp): + angle = sympy.arg(amp) + color = '#26f' + # Divide the unitary circle in 4 area/colors + angle = angle % (2*PI) + if angle <= PI/4: + #blue + color = '#26f' + elif angle <= 3*PI/4: + #purple + color = '#800080' + elif angle <= 5*PI/4: + # orange + color = '#e70' + elif angle <= 7*PI/4: + # yellow + color = '#ffce00' + elif angle <= 8*PI/4: + # blue + color = '#26f' + return color + + def get_abs(self, amp): + """ + Check if the amplitude is complex. If so, return the magnitude. + """ + if isinstance(amp, sympy.Expr): + if amp.is_complex: + return abs(amp) + return abs(float(amp)) def draw_states(self): t = len(self.state_sequence)-1 @@ -191,7 +222,7 @@ def add_states(self, new_state): clean_state = { key: amp for key, amp in new_state.items() - if abs(float(amp)) >= 1e-8 + if self.get_abs(amp) >= 1e-8 } self.state_sequence[-1] = clean_state @@ -258,3 +289,19 @@ def perform_x(self, q_i, *, pre_latex=f'', name='X'): new_state[new_key] += amp self.transition_text(self.d, t, f'{pre_latex}{name}_{{{q_i}}}') self.add_states(new_state) + + def perform_y(self, q_i, *, pre_latex=f'', name='Y'): + new_state = {} + t = len(self.state_sequence)-1 + for key, amp in self.state_sequence[-1].items(): + is_one = key[q_i] == '1' + digits = list(key) + digits[q_i] = '01'[not is_one] + new_key = ''.join(digits) + new_amp = -sympy.I*amp if is_one else sympy.I*amp + self.gate_arrow(self.d, t, key, new_key, amp=new_amp/amp) + if new_key not in new_state: + new_state[new_key] = 0 + new_state[new_key] += new_amp + self.transition_text(self.d, t, f'{pre_latex}{name}_{{{q_i}}}') + self.add_states(new_state) From bab9f4be4d1882650a5554e37f6f7b37f45f35d9 Mon Sep 17 00:00:00 2001 From: Giorgio Silvi <97681236+gsilviHQS@users.noreply.github.com> Date: Wed, 6 Jul 2022 08:50:20 +0200 Subject: [PATCH 2/5] Update feynman_path/diagram.py Co-authored-by: Casey Duckering --- feynman_path/diagram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feynman_path/diagram.py b/feynman_path/diagram.py index 4ce5b30..7422adc 100755 --- a/feynman_path/diagram.py +++ b/feynman_path/diagram.py @@ -175,7 +175,7 @@ def gate_arrow(self, g, time1, key1, key2, amp=1): xx2 = x2 - self.w_label/2 + self.arrow_off yy1 = y1 + (y2-y1)*(xx1-x1)/(x2-x1) yy2 = y2 - (y2-y1)*(x2-xx2)/(x2-x1) - #map the amplitude to a "wheel" of colors + # map the amplitude to a "wheel" of colors color = self.wheel_color(amp) self.straight_arrow(g, color, xx1, yy1, xx2, yy2, width=w) From cc762eba8bd657d9f5bb8208f3b74aaddf230d28 Mon Sep 17 00:00:00 2001 From: gsilvi Date: Fri, 8 Jul 2022 10:51:28 +0200 Subject: [PATCH 3/5] factorized single gates + color wheel also command accept floats and add simplify to amplitudes. In case the amplitude is too complicated return the value in digits --- README.md | 3 +- feynman_path/command.py | 8 +- feynman_path/diagram.py | 162 +++++++++++++++++++++------------------- 3 files changed, 93 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 933a536..e60bd04 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ f.perform_cnot(1, 2, pre_latex=r'\color{red!80!black}') f.perform_h(0) f.perform_h(1) f.perform_cnot(1, 0) +f.perform_h(1) f.draw() # Display in Jupyter ``` @@ -145,7 +146,7 @@ The [CNOT gate](https://en.wikipedia.org/wiki/Controlled_NOT_gate) (⋅–⨁) c Note the output (rightmost) column is an entangled state: |00⟩+|11⟩ ```bash -feynman_path no-entanglement 2 h0 cnot0,1 h0 h1 +feynman_path entanglement 2 h0 cnot0,1 h0 h1 ``` **Fail to create a bell pair by using a CNOT on the |++⟩ state (q0=|+⟩, q1=|+⟩):** diff --git a/feynman_path/command.py b/feynman_path/command.py index 9832876..3a6f115 100644 --- a/feynman_path/command.py +++ b/feynman_path/command.py @@ -4,10 +4,16 @@ from . import diagram +def int_or_float(s): + if s.isdigit(): + return int(s) + else: + return float(s) + def parse_gate(gate_str): name = gate_str.translate({ord(digit): '-' for digit in '0123456789'} ).split('-')[0] - args = tuple(int(arg) for arg in gate_str[len(name):].split(',')) + args = tuple(int_or_float(arg) for arg in gate_str[len(name):].split(',')) return name, args def draw_diagram(n_qubits, gates): diff --git a/feynman_path/diagram.py b/feynman_path/diagram.py index 4ce5b30..4f8f916 100755 --- a/feynman_path/diagram.py +++ b/feynman_path/diagram.py @@ -4,7 +4,8 @@ from sympy.printing.latex import latex import drawSvg as draw import latextools -PI = 3.1415926535 +from colorsys import hls_to_rgb + VERBOSE = False @@ -177,31 +178,18 @@ def gate_arrow(self, g, time1, key1, key2, amp=1): yy2 = y2 - (y2-y1)*(x2-xx2)/(x2-x1) #map the amplitude to a "wheel" of colors color = self.wheel_color(amp) - self.straight_arrow(g, color, xx1, yy1, xx2, yy2, width=w) - def wheel_color(self,amp): - angle = sympy.arg(amp) - color = '#26f' - # Divide the unitary circle in 4 area/colors - angle = angle % (2*PI) - if angle <= PI/4: - #blue - color = '#26f' - elif angle <= 3*PI/4: - #purple - color = '#800080' - elif angle <= 5*PI/4: - # orange - color = '#e70' - elif angle <= 7*PI/4: - # yellow - color = '#ffce00' - elif angle <= 8*PI/4: - # blue - color = '#26f' - return color - + def wheel_color(self, amp): + """ Maps the amplitude to a color wheel. """ + offset = 7*sympy.pi/6 # offset to have blue for angle zero + angle = sympy.arg(amp)+ offset + angle = angle % (2*sympy.pi) + angle = (angle / (2*sympy.pi)).evalf(2) + mag = min(1,abs(self.get_amp_as_value(amp,n=1))) + rgb = hls_to_rgb(angle,0.6,mag) + rgb = tuple(int(x*255) for x in rgb) + return '#%02x%02x%02x' % (rgb) def get_abs(self, amp): """ Check if the amplitude is complex. If so, return the magnitude. @@ -210,13 +198,27 @@ def get_abs(self, amp): if amp.is_complex: return abs(amp) return abs(float(amp)) + + def get_amp_as_value(self, amp, n, digits=2): + """ + Check if the amplitude is a sympy element. + If so, and has more than n ops return the value in digits + """ + if hasattr(amp, "count_ops"): + if amp.count_ops() > n: + amp = amp.evalf(digits) + return amp def draw_states(self): t = len(self.state_sequence)-1 for key, amp in self.state_sequence[-1].items(): + amp = self.get_amp_as_value(amp,n=4) + #amp = 0 if abs(amp)<1e-8 else amp self.state_text(self.d, t, key, amp=amp) def add_states(self, new_state): + for key,amp in new_state.items(): + new_state[key]= sympy.simplify(amp) self.state_sequence.append(new_state) self.draw_states() clean_state = { @@ -226,27 +228,6 @@ def add_states(self, new_state): } self.state_sequence[-1] = clean_state - def perform_h(self, q_i, *, pre_latex=f'', name='H'): - new_state = {} - t = len(self.state_sequence)-1 - for key, amp in self.state_sequence[-1].items(): - is_one = key[q_i] == '1' - digits = list(key) - digits[q_i] = '0' - zero = ''.join(digits) - digits[q_i] = '1' - one = ''.join(digits) - zero_amp = 1/sympy.sqrt(2) - one_amp = -zero_amp if is_one else zero_amp - self.gate_arrow(self.d, t, key, zero, amp=zero_amp) - self.gate_arrow(self.d, t, key, one, amp=one_amp) - if zero not in new_state: new_state[zero] = 0 - if one not in new_state: new_state[one] = 0 - new_state[zero] += amp*zero_amp - new_state[one] += amp*one_amp - self.transition_text(self.d, t, f'{pre_latex}{name}_{q_i}') - self.add_states(new_state) - def perform_cnot(self, qi1, qi2, *, pre_latex=f'', name='CNOT'): new_state = {} t = len(self.state_sequence)-1 @@ -263,45 +244,70 @@ def perform_cnot(self, qi1, qi2, *, pre_latex=f'', name='CNOT'): self.transition_text(self.d, t, f'{pre_latex}{name}_{{{qi1}{qi2}}}') self.add_states(new_state) - def perform_z(self, q_i, *, pre_latex=f'', name='Z'): - new_state = {} - t = len(self.state_sequence)-1 - for key, amp in self.state_sequence[-1].items(): - is_one = key[q_i] == '1' - new_amp = -amp if is_one else amp - self.gate_arrow(self.d, t, key, key, amp=new_amp/amp) - if key not in new_state: new_state[key] = 0 - new_state[key] += new_amp - self.transition_text(self.d, t, f'{pre_latex}{name}_{{{q_i}}}') - self.add_states(new_state) def perform_x(self, q_i, *, pre_latex=f'', name='X'): - new_state = {} - t = len(self.state_sequence)-1 - for key, amp in self.state_sequence[-1].items(): - is_one = key[q_i] == '1' - digits = list(key) - digits[q_i] = '01'[not is_one] - new_key = ''.join(digits) - self.gate_arrow(self.d, t, key, new_key, amp=1) - if new_key not in new_state: - new_state[new_key] = 0 - new_state[new_key] += amp - self.transition_text(self.d, t, f'{pre_latex}{name}_{{{q_i}}}') - self.add_states(new_state) - + X_gate = [[0, 1], [1, 0]] + self.perform_single_gate( q_i, pre_latex, name, X_gate) + def perform_y(self, q_i, *, pre_latex=f'', name='Y'): + Y_gate = [[0, -sympy.I], [sympy.I, 0]] + self.perform_single_gate( q_i, pre_latex, name, Y_gate) + + def perform_z(self, q_i, *, pre_latex=f'', name='Z'): + Z_gate = [[1, 0], [0, -1]] + self.perform_single_gate( q_i, pre_latex, name, Z_gate) + + def perform_h(self, q_i, *, pre_latex=f'', name='H'): + sqrt2 = sympy.sqrt(2) + H_gate = [[1/sqrt2, 1/sqrt2], + [1/sqrt2, -1/sqrt2]] + self.perform_single_gate( q_i, pre_latex, name, H_gate) + + def perform_rx(self, q_i, half_turns, *, pre_latex=f'', name='Rx'): + theta = sympy.pi*half_turns + Rx_gate = [[sympy.cos(theta/2), -sympy.I*sympy.sin(theta/2)], + [-sympy.I*sympy.sin(theta/2), sympy.cos(theta/2)]] + self.perform_single_gate( q_i, pre_latex, name, Rx_gate) + + def perform_ry(self, q_i, half_turns, *, pre_latex=f'', name='Ry'): + theta = sympy.pi*half_turns + Ry_gate = [[sympy.cos(theta/2), -sympy.sin(theta/2)], + [sympy.sin(theta/2), sympy.cos(theta/2)]] + self.perform_single_gate( q_i, pre_latex, name, Ry_gate) + + def perform_rz(self, q_i, half_turns, *, pre_latex=f'', name='Rz'): + theta = sympy.pi*half_turns + Rz_gate = [[ sympy.exp(-sympy.I*theta/2), 0], + [0, sympy.exp(sympy.I*theta/2)]] + self.perform_single_gate( q_i, pre_latex, name, Rz_gate) + + def perform_single_gate(self, q_i, pre_latex, name, gate_matrix): new_state = {} t = len(self.state_sequence)-1 for key, amp in self.state_sequence[-1].items(): is_one = key[q_i] == '1' digits = list(key) - digits[q_i] = '01'[not is_one] - new_key = ''.join(digits) - new_amp = -sympy.I*amp if is_one else sympy.I*amp - self.gate_arrow(self.d, t, key, new_key, amp=new_amp/amp) - if new_key not in new_state: - new_state[new_key] = 0 - new_state[new_key] += new_amp - self.transition_text(self.d, t, f'{pre_latex}{name}_{{{q_i}}}') + digits[q_i] = '0' + zero = ''.join(digits) + digits[q_i] = '1' + one = ''.join(digits) + if is_one: + zero_amp = gate_matrix[0][1] + one_amp = gate_matrix[1][1] + else: + zero_amp = gate_matrix[0][0] + one_amp = gate_matrix[1][0] + + if zero_amp != 0: + self.gate_arrow(self.d, t, key, zero, amp=zero_amp) + if zero not in new_state: new_state[zero] = 0 + new_state[zero] += zero_amp*amp + if one_amp != 0: + self.gate_arrow(self.d, t, key, one, amp=one_amp) + if one not in new_state: new_state[one] = 0 + new_state[one] += one_amp*amp + + self.transition_text(self.d, t, f'{pre_latex}{name}_{q_i}') self.add_states(new_state) + + From 98ad0e304cc1a3feb26ad0479b063f04de0f2215 Mon Sep 17 00:00:00 2001 From: gsilvi Date: Fri, 15 Jul 2022 17:02:39 +0200 Subject: [PATCH 4/5] better visualizatio in my opinion. where you can follow the sign through the color of the lines --- feynman_path/diagram.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/feynman_path/diagram.py b/feynman_path/diagram.py index 389cf37..7ccf76f 100755 --- a/feynman_path/diagram.py +++ b/feynman_path/diagram.py @@ -167,7 +167,7 @@ def straight_arrow(self, g, color, *xy_list, width=1): marker_start=self.make_arrow(color))) def gate_arrow(self, g, time1, key1, key2, amp=1): - w = float(abs(amp)) + w = max(float(abs(amp)),.3) x1, y1 = self.state_xy(key1, time1) x2, y2 = self.state_xy(key2, time1+1) x1 += self.arrow_off @@ -238,7 +238,7 @@ def perform_cnot(self, qi1, qi2, *, pre_latex=f'', name='CNOT'): if is_one: digits[qi2] = '01'[not is_targ_one] new_key = ''.join(digits) - self.gate_arrow(self.d, t, key, new_key, amp=1) + self.gate_arrow(self.d, t, key, new_key, amp=amp) if new_key not in new_state: new_state[new_key] = 0 new_state[new_key] += amp self.transition_text(self.d, t, f'{pre_latex}{name}_{{{qi1}{qi2}}}') @@ -299,11 +299,11 @@ def perform_single_gate(self, q_i, pre_latex, name, gate_matrix): one_amp = gate_matrix[1][0] if zero_amp != 0: - self.gate_arrow(self.d, t, key, zero, amp=zero_amp) + self.gate_arrow(self.d, t, key, zero, amp=zero_amp*amp) if zero not in new_state: new_state[zero] = 0 new_state[zero] += zero_amp*amp if one_amp != 0: - self.gate_arrow(self.d, t, key, one, amp=one_amp) + self.gate_arrow(self.d, t, key, one, amp=one_amp*amp) if one not in new_state: new_state[one] = 0 new_state[one] += one_amp*amp From f48ac9076d2a6a75e654098685a55bc1488ca42b Mon Sep 17 00:00:00 2001 From: gsilvi Date: Fri, 15 Jul 2022 17:06:01 +0200 Subject: [PATCH 5/5] color always bright --- feynman_path/diagram.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/feynman_path/diagram.py b/feynman_path/diagram.py index 7ccf76f..81fa684 100755 --- a/feynman_path/diagram.py +++ b/feynman_path/diagram.py @@ -167,7 +167,7 @@ def straight_arrow(self, g, color, *xy_list, width=1): marker_start=self.make_arrow(color))) def gate_arrow(self, g, time1, key1, key2, amp=1): - w = max(float(abs(amp)),.3) + w = max(float(abs(amp)),.1) x1, y1 = self.state_xy(key1, time1) x2, y2 = self.state_xy(key2, time1+1) x1 += self.arrow_off @@ -186,8 +186,7 @@ def wheel_color(self, amp): angle = sympy.arg(amp)+ offset angle = angle % (2*sympy.pi) angle = (angle / (2*sympy.pi)).evalf(2) - mag = min(1,abs(self.get_amp_as_value(amp,n=1))) - rgb = hls_to_rgb(angle,0.6,mag) + rgb = hls_to_rgb(angle,0.6,1) rgb = tuple(int(x*255) for x in rgb) return '#%02x%02x%02x' % (rgb) def get_abs(self, amp):