Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quantum-kittens
GitHub Repository: quantum-kittens/platypus
Path: blob/main/notebooks/intro/hello_quantum.py
3855 views
1
#!/usr/bin/env python3
2
3
import copy
4
from io import BytesIO
5
6
from qiskit import BasicAer as Aer
7
from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
8
from qiskit import execute
9
from qiskit.visualization import plot_bloch_multivector
10
from qiskit.quantum_info import Statevector
11
12
from qiskit.quantum_info import Statevector
13
import numpy as np
14
import matplotlib.pyplot as plt
15
from matplotlib.patches import Circle, Rectangle, FancyBboxPatch
16
from ipywidgets import widgets
17
from IPython.display import display
18
19
from qiskit_textbook.widgets._helpers import _img
20
21
darker_purple = (105/255, 41/255, 196/255)
22
dark_purple = (165/255,110/255,255/255)
23
purple = (190/255, 149/255, 255/255)
24
light_purple = (212/255,187/255,255/255)
25
26
white = (242/255,244/255,248/255)
27
light_gray = (221/255,225/255,230/255)
28
dark_gray = (193/255,199/255,205/255)
29
black = (52/255,58/255,63/255)
30
31
32
# background color
33
background_color = white
34
# circle colors for normal and Y circles
35
circle_color = [white,white]
36
# colors for 0 and 1 parts of line and divider
37
line_color = [light_gray,purple,dark_purple]
38
# box colors for qubit boxes and correlation boxes
39
box_color = [light_purple,darker_purple]
40
# outer and inner colors of Bloch point
41
point_color = [dark_gray,dark_gray]
42
43
44
class run_game():
45
# Implements a puzzle, which is defined by the given inputs.
46
47
def __init__(self,initialize, success_condition, allowed_gates, vi, qubit_names={'0':'q[0]', '1':'q[1]'}, eps=0.1, backend=None, shots=1024,mode='line',verbose=False):
48
"""
49
initialize
50
List of gates applied to the initial 00 state to get the starting state of the puzzle.
51
Supported single qubit gates (applied to qubit '0' or '1') are 'x', 'y', 'z', 'h', 'ry(pi/4)'.
52
Supported two qubit gates are 'cz' and 'cx'. Specify only the target qubit.
53
success_condition
54
Values for pauli observables that must be obtained for the puzzle to declare success.
55
allowed_gates
56
For each qubit, specify which operations are allowed in this puzzle. 'both' should be used only for operations that don't need a qubit to be specified ('cz' and 'unbloch').
57
Gates are expressed as a dict with an int as value. If this is non-zero, it specifies the number of times the gate is must be used (no more or less) for the puzzle to be successfully solved. If the value is zero, the player can use the gate any number of times.
58
vi
59
Some visualization information as a three element list. These specify:
60
* which qubits are hidden (empty list if both shown).
61
* whether both circles shown for each qubit (use True for qubit puzzles and False for bit puzzles).
62
* whether the correlation circles (the four in the middle) are shown.
63
qubit_names
64
The two qubits are always called '0' and '1' from the programming side. But for the player, we can display different names.
65
eps=0.1
66
How close the expectation values need to be to the targets for success to be declared.
67
backend=Aer.get_backend('qasm_simulator')
68
Backend to be used by Qiskit to calculate expectation values (defaults to local simulator).
69
shots=1024
70
Number of shots used to to calculate expectation values.
71
mode='circle'
72
Either the standard 'Hello Quantum' visualization can be used (with mode='circle'), or the extended one (mode='y') or the alternative line based one (mode='line').
73
y_boxes = False
74
Whether to show expectation values involving y.
75
verbose=False
76
"""
77
78
def get_total_gate_list():
79
# Get a text block describing allowed gates.
80
81
total_gate_list = ""
82
for qubit in allowed_gates:
83
gate_list = ""
84
for gate in allowed_gates[qubit]:
85
if required_gates[qubit][gate] > 0 :
86
gate_list += ' ' + gate+" (use "+str(required_gates[qubit][gate])+" time"+"s"*(required_gates[qubit][gate]>1)+")"
87
elif allowed_gates[qubit][gate]==0:
88
gate_list += ' '+gate + ' '
89
if gate_list!="":
90
if qubit=="both" :
91
gate_list = "\nAllowed symmetric operations:" + gate_list
92
else :
93
gate_list = "\nAllowed operations for " + qubit_names[qubit] + ":\n" + " "*10 + gate_list
94
total_gate_list += gate_list +"\n"
95
return total_gate_list
96
97
def get_success(required_gates):
98
# Determine whether the success conditions are satisfied, both for expectation values, and the number of gates to be used.
99
100
success = True
101
grid.get_rho()
102
if verbose:
103
print(grid.rho)
104
for pauli in success_condition:
105
success = success and (abs(success_condition[pauli] - grid.rho[pauli])<eps)
106
for qubit in required_gates:
107
for gate in required_gates[qubit]:
108
success = success and (required_gates[qubit][gate]==0)
109
return success
110
111
def show_circuit():
112
gates = get_total_gate_list
113
114
def get_command(gate,qubit):
115
# For a given gate and qubit, return the string describing the corresponding Qiskit string.
116
117
if qubit=='both':
118
qubit = '1'
119
qubit_name = qubit_names[qubit]
120
for name in qubit_names.values():
121
if name!=qubit_name:
122
other_name = name
123
# then make the command (both for the grid, and for printing to screen)
124
if gate in ['x','y','z','h']:
125
real_command = 'grid.qc.'+gate+'(grid.qr['+qubit+'])'
126
clean_command = 'qc.'+gate+'('+qubit_name+')'
127
elif gate in ['ry(pi/4)','ry(-pi/4)']:
128
real_command = 'grid.qc.ry('+'-'*(gate=='ry(-pi/4)')+'np.pi/4,grid.qr['+qubit+'])'
129
clean_command = 'qc.ry('+'-'*(gate=='ry(-pi/4)')+'np.pi/4,'+qubit_name+')'
130
elif gate in ['rx(pi/4)','rx(-pi/4)']:
131
real_command = 'grid.qc.rx('+'-'*(gate=='rx(-pi/4)')+'np.pi/4,grid.qr['+qubit+'])'
132
clean_command = 'qc.rx('+'-'*(gate=='rx(-pi/4)')+'np.pi/4,'+qubit_name+')'
133
elif gate in ['cz','cx','swap']:
134
real_command = 'grid.qc.'+gate+'(grid.qr['+'0'*(qubit=='1')+'1'*(qubit=='0')+'],grid.qr['+qubit+'])'
135
clean_command = 'qc.'+gate+'('+other_name+','+qubit_name+')'
136
return [real_command,clean_command]
137
138
bloch = [None]
139
140
# set up initial state and figure
141
if mode=='y':
142
grid = pauli_grid(backend=backend,shots=shots,mode='line',y_boxes=True)
143
else:
144
grid = pauli_grid(backend=backend,shots=shots,mode=mode)
145
146
self.initializer = []
147
for gate in initialize:
148
command = get_command(gate[0],gate[1])
149
eval(command[0])
150
self.initializer.append(command[1])
151
152
required_gates = copy.deepcopy(allowed_gates)
153
154
# determine which qubits to show in figure
155
if allowed_gates['0']=={} : # if no gates are allowed for qubit 0, we know to only show qubit 1
156
shown_qubit = 1
157
elif allowed_gates['1']=={} : # and vice versa
158
shown_qubit = 0
159
else :
160
shown_qubit = 2
161
162
# show figure
163
grid_view = _img()
164
grid.update_grid(bloch=bloch[0],hidden=vi[0],qubit=vi[1],corr=vi[2],message=get_total_gate_list(),output=grid_view)
165
display(grid_view.widget)
166
167
168
169
description = {'gate':['Choose gate'],'qubit':['Choose '+'qu'*vi[1]+'bit'],'action':['Make it happen!']}
170
171
all_allowed_gates_raw = []
172
for q in ['0','1','both']:
173
all_allowed_gates_raw += list(allowed_gates[q])
174
all_allowed_gates_raw = list(set(all_allowed_gates_raw))
175
176
all_allowed_gates = []
177
for g in ['bloch']:
178
if g in all_allowed_gates_raw:
179
all_allowed_gates.append( g )
180
for g in ['x','y','z','h','cz','cx']:
181
if g in all_allowed_gates_raw:
182
all_allowed_gates.append( g )
183
for g in all_allowed_gates_raw:
184
if g not in all_allowed_gates:
185
all_allowed_gates.append( g )
186
187
gate = widgets.ToggleButtons(options=description['gate']+all_allowed_gates)
188
qubit = widgets.ToggleButtons(options=[''])
189
action = widgets.ToggleButtons(options=[''])
190
191
boxes = widgets.VBox([gate,qubit,action])
192
display(boxes)
193
self.program = []
194
self.qubit_names = qubit_names
195
196
def given_gate(a):
197
# Action to be taken when gate is chosen. This sets up the system to choose a qubit.
198
199
if gate.value:
200
if gate.value in allowed_gates['both']:
201
qubit.options = description['qubit'] + ["not required"]
202
qubit.value = "not required"
203
else:
204
allowed_qubits = []
205
for q in ['1','0']:
206
if (gate.value in allowed_gates[q]) or (gate.value in allowed_gates['both']):
207
allowed_qubits.append(q)
208
allowed_qubit_names = []
209
for q in allowed_qubits:
210
allowed_qubit_names += [qubit_names[q]]
211
qubit.options = description['qubit'] + allowed_qubit_names
212
213
def given_qubit(b):
214
# Action to be taken when qubit is chosen. This sets up the system to choose an action.
215
216
if qubit.value not in ['',description['qubit'][0],'Success!']:
217
action.options = description['action']+['Apply operation']
218
219
def given_action(c):
220
# Action to be taken when user confirms their choice of gate and qubit.
221
# This applied the command, updates the visualization and checks whether the puzzle is solved.
222
223
if action.value not in ['',description['action'][0]]:
224
# apply operation
225
if action.value=='Apply operation':
226
if qubit.value not in ['',description['qubit'][0],'Success!']:
227
# translate bit gates to qubit gates
228
if gate.value=='NOT':
229
q_gate = 'x'
230
elif gate.value=='CNOT':
231
q_gate = 'cx'
232
else:
233
q_gate = gate.value
234
if qubit.value=="not required":
235
q = qubit_names['1']
236
else:
237
q = qubit.value
238
q01 = '0'*(qubit.value==qubit_names['0']) + '1'*(qubit.value==qubit_names['1']) + 'both'*(qubit.value=="not required")
239
if q_gate in ['bloch']:
240
if q01 != bloch[0]:
241
bloch[0] = q01
242
else:
243
bloch[0] = None
244
else:
245
command = get_command(q_gate,q01)
246
eval(command[0])
247
self.program.append( command[1] )
248
if required_gates[q01][gate.value]>0:
249
required_gates[q01][gate.value] -= 1
250
251
grid.update_grid(bloch=bloch[0],hidden=vi[0],qubit=vi[1],corr=vi[2],message=get_total_gate_list(),output=grid_view)
252
253
success = get_success(required_gates)
254
if success:
255
gate.options = ['Success!']
256
qubit.options = ['Success!']
257
action.options = ['Success!']
258
plt.close(grid.fig)
259
else:
260
gate.value = description['gate'][0]
261
qubit.options = ['']
262
action.options = ['']
263
264
gate.observe(given_gate)
265
qubit.observe(given_qubit)
266
action.observe(given_action)
267
268
def get_circuit(self, use_initializer=False):
269
270
q = QuantumRegister(2,'q')
271
b = ClassicalRegister(2,'b')
272
qc = QuantumCircuit(q,b)
273
274
if '[' not in self.qubit_names['0']+self.qubit_names['0']:
275
exec(self.qubit_names['0']+' = q[0]')
276
exec(self.qubit_names['1']+' = q[1]')
277
278
if use_initializer:
279
for line in self.initializer:
280
eval(line)
281
282
for line in self.program:
283
eval(line)
284
285
return qc
286
287
def plot_spheres(self):
288
return plot_bloch_multivector(Statevector(self.get_circuit(use_initializer=True)),reverse_bits=True)
289
290
class pauli_grid():
291
# Allows a quantum circuit to be created, modified and implemented, and visualizes the output in the style of 'Hello Quantum'.
292
293
def __init__(self,backend=Aer.get_backend('qasm_simulator'),shots=1024,mode='circle',y_boxes=False):
294
"""
295
backend=Aer.get_backend('qasm_simulator')
296
Backend to be used by Qiskit to calculate expectation values (defaults to local simulator).
297
shots=1024
298
Number of shots used to to calculate expectation values.
299
mode='circle'
300
Either the standard 'Hello Quantum' visualization can be used (with mode='circle') or the alternative line based one (mode='line').
301
y_boxes=True
302
Whether to display full grid that includes Y expectation values.
303
"""
304
305
self.backend = backend
306
self.shots = shots
307
308
self.y_boxes = y_boxes
309
if self.y_boxes:
310
self.box = {'IZ':(-1, 2), 'IX':(-3, 4), 'IY':(-2, 3),
311
'XI':( 3, 4), 'YI':( 2,3 ), 'ZI':( 1, 2),
312
'XZ':( 2, 5), 'YZ':( 1, 4), 'ZZ':( 0, 3),
313
'XX':( 0, 7), 'YX':(-1, 6), 'ZX':(-2, 5),
314
'XY':( 1, 6), 'YY':( 0, 5), 'ZY':(-1, 4)}
315
else:
316
self.box = {'IZ':(-1, 2),'IX':(-2, 3),
317
'ZI':( 1, 2),'XI':( 2, 3),
318
'XZ':( 1, 4),'ZZ':( 0, 3),
319
'ZX':(-1, 4),'XX':( 0, 5)}
320
321
self.rho = {}
322
for pauli in self.box:
323
self.rho[pauli] = 0.0
324
for pauli in ['ZI','IZ','ZZ']:
325
self.rho[pauli] = 1.0
326
327
self.qr = QuantumRegister(2)
328
self.cr = ClassicalRegister(2)
329
self.qc = QuantumCircuit(self.qr, self.cr)
330
331
self.mode = mode
332
333
if self.mode!='y':
334
figsize=(5,5)
335
else:
336
figsize=(6,6)
337
338
self.fig = plt.figure(figsize=(6,6),facecolor=background_color)
339
self.ax = self.fig.add_subplot(111)
340
plt.axis('off')
341
342
self.bottom = self.ax.text(-3,1,"",size=10,va='top',color='black')
343
344
self.points = {}
345
for pauli in self.box:
346
self.points[pauli] = [ self.ax.add_patch( Circle(self.box[pauli], 0.0, color=point_color[0], zorder=10) ) ]
347
self.points[pauli].append( self.ax.add_patch( Circle(self.box[pauli], 0.0, color=point_color[1], zorder=10) ) )
348
349
self.initial = True
350
351
def get_rho(self):
352
# Runs the circuit specified by self.qc and determines the expectation values for 'ZI', 'IZ', 'ZZ', 'XI', 'IX', 'XX', 'ZX' and 'XZ' (and the ones with Ys too if needed).
353
354
if self.y_boxes:
355
corr = ['ZZ','ZX','XZ','XX','YY','YX','YZ','XY','ZY']
356
ps = ['X','Y','Z']
357
else:
358
corr = ['ZZ','ZX','XZ','XX']
359
ps = ['X','Z']
360
361
362
self.rho = {}
363
364
results = {}
365
for basis in corr:
366
temp_qc = copy.deepcopy(self.qc)
367
for j in range(2):
368
if basis[j]=='X':
369
temp_qc.h(self.qr[j])
370
elif basis[j]=='Y':
371
temp_qc.sdg(self.qr[j])
372
temp_qc.h(self.qr[j])
373
374
if self.backend==None:
375
ket = Statevector([1,0,0,0])
376
ket = ket.from_instruction(temp_qc)
377
results[basis] = ket.probabilities_dict()
378
else:
379
temp_qc.barrier(self.qr)
380
temp_qc.measure(self.qr,self.cr)
381
job = execute(temp_qc, backend=self.backend, shots=self.shots)
382
results[basis] = job.result().get_counts()
383
for string in results[basis]:
384
results[basis][string] = results[basis][string]/self.shots
385
386
prob = {}
387
# prob of expectation value -1 for single qubit observables
388
for j in range(2):
389
390
for p in ps:
391
pauli = {}
392
for pp in ['I']+ps:
393
pauli[pp] = (j==1)*pp + p + (j==0)*pp
394
prob[pauli['I']] = 0
395
for ppp in ps:
396
basis = pauli[ppp]
397
for string in results[basis]:
398
if string[(j+1)%2]=='1':
399
prob[pauli['I']] += results[basis][string]/(2+self.y_boxes)
400
401
# prob of expectation value -1 for two qubit observables
402
for basis in corr:
403
prob[basis] = 0
404
for string in results[basis]:
405
if string[0]!=string[1]:
406
prob[basis] += results[basis][string]
407
408
for pauli in prob:
409
self.rho[pauli] = 1-2*prob[pauli]
410
411
412
413
414
415
def update_grid(self,rho=None,labels=False,bloch=None,hidden=[],qubit=True,corr=True,message="",output=None):
416
"""
417
rho = None
418
Dictionary of expectation values for 'ZI', 'IZ', 'ZZ', 'XI', 'IX', 'XX', 'ZX' and 'XZ'. If supplied, this will be visualized instead of the results of running self.qc.
419
labels = False
420
Determines whether basis labels are printed in the corresponding boxes.
421
bloch = None
422
If a qubit name is supplied, and if mode='line', Bloch circles are displayed for this qubit
423
hidden = []
424
Which qubits have their circles hidden (empty list if both shown).
425
qubit = True
426
Whether both circles shown for each qubit (use True for qubit puzzles and False for bit puzzles).
427
corr = True
428
Whether the correlation circles (the four in the middle) are shown.
429
message
430
A string of text that is displayed below the grid.
431
"""
432
433
def see_if_unhidden(pauli):
434
# For a given Pauli, see whether its circle should be shown.
435
436
unhidden = True
437
# first: does it act non-trivially on a qubit in `hidden`
438
for j in hidden:
439
unhidden = unhidden and (pauli[j]=='I')
440
# second: does it contain something other than 'I' or 'Z' when only bits are shown
441
if qubit==False:
442
for j in range(2):
443
unhidden = unhidden and (pauli[j] in ['I','Z'])
444
# third: is it a correlation pauli when these are not allowed
445
if corr==False:
446
unhidden = unhidden and ((pauli[0]=='I') or (pauli[1]=='I'))
447
# finally: is it actually in rho
448
unhidden = unhidden and (pauli in self.rho)
449
return unhidden
450
451
def add_line(line,pauli_pos,pauli):
452
"""
453
For mode='line', add in the line.
454
455
line = the type of line to be drawn (X, Z or the other one)
456
pauli = the box where the line is to be drawn
457
expect = the expectation value that determines its length
458
"""
459
460
delta = 0.07
461
462
unhidden = see_if_unhidden(pauli)
463
p = (1-self.rho[pauli])/2 # prob of 1 output
464
# in the following, white lines goes from a to b, and black from b to c
465
if unhidden:
466
if line=='X':
467
468
a = ( self.box[pauli_pos][0]-length/2, self.box[pauli_pos][1]-width/2 )
469
c = ( self.box[pauli_pos][0]+length/2, self.box[pauli_pos][1]-width/2 )
470
b = ( p*a[0] + (1-p)*c[0] , p*a[1] + (1-p)*c[1] )
471
472
self.ax.add_patch( Rectangle( a, length*(1-p), width, angle=0, color=line_color[0]) )
473
self.ax.add_patch( Rectangle( b, length*p, width, angle=0, color=line_color[2]) )
474
if length*p>delta:
475
self.ax.add_patch( Rectangle( (b[0]+delta/2,b[1]+delta*0.6), length*p-delta, width-delta, angle=0, color=line_color[1]) )
476
477
elif line=='Z':
478
479
a = ( self.box[pauli_pos][0]-width/2, self.box[pauli_pos][1]-length/2 )
480
c = ( self.box[pauli_pos][0]-width/2, self.box[pauli_pos][1]+length/2 )
481
b = ( p*a[0] + (1-p)*c[0] , p*a[1] + (1-p)*c[1] )
482
483
self.ax.add_patch( Rectangle( a, width, length*(1-p), angle=0, color=line_color[0]) )
484
self.ax.add_patch( Rectangle( b, width, length*p, angle=0, color=line_color[2]) )
485
if length*p>delta:
486
self.ax.add_patch( Rectangle( (b[0]+delta/2,b[1]+delta/2), width-delta, length*p-delta, angle=0, color=line_color[1]) )
487
488
else:
489
490
491
a = ( self.box[pauli_pos][0]-length/(2*np.sqrt(2)), self.box[pauli_pos][1]-length/(2*np.sqrt(2)) )
492
c = ( self.box[pauli_pos][0]+length/(2*np.sqrt(2)), self.box[pauli_pos][1]+length/(2*np.sqrt(2)) )
493
b = ( p*a[0] + (1-p)*c[0] , p*a[1] + (1-p)*c[1] )
494
495
self.ax.add_patch( Rectangle( a, width, length*(1-p), angle=-45, color=line_color[0]) )
496
self.ax.add_patch( Rectangle( b, width, length*p, angle=-45, color=line_color[2]) )
497
if length*p>delta:
498
self.ax.add_patch( Rectangle( (b[0]+delta*np.sqrt(2)/2,b[1]+delta*0.1*np.sqrt(2)/2), width-delta, length*p-delta, angle=-45, color=line_color[1]) )
499
500
return p
501
502
L = 0.98*np.sqrt(2) # box height and width
503
length = 0.75*L # line length
504
width = 0.12*L # line width
505
r = 0.6 # circle radius
506
507
# set the state
508
self.rho = rho
509
if self.rho=={} or self.rho==None:
510
self.get_rho()
511
512
# draw boxes
513
if self.initial:
514
for pauli in self.box:
515
if 'I' in pauli:
516
color = box_color[0]
517
else:
518
color = box_color[1]
519
self.ax.add_patch( Rectangle( (self.box[pauli][0],self.box[pauli][1]-1), L, L, angle=45, color=color) )
520
521
# draw circles
522
for pauli in self.box:
523
unhidden = see_if_unhidden(pauli)
524
if unhidden:
525
if self.mode!='line':
526
prob = (1-self.rho[pauli])/2
527
color=(prob,prob,prob)
528
self.ax.add_patch( Circle(self.box[pauli], r, color=color) )
529
else:
530
if 'Y' in pauli:
531
self.ax.add_patch( Circle(self.box[pauli], r, color=circle_color[1]) )
532
else:
533
self.ax.add_patch( Circle(self.box[pauli], r, color=circle_color[0]) )
534
535
# hide points
536
for pauli in self.points:
537
for point in self.points[pauli]:
538
point.radius = 0
539
540
# update bars if required
541
if self.mode=='line':
542
if bloch in ['0','1']:
543
for other in 'IXZ':
544
px = other*(bloch=='1') + 'X' + other*(bloch=='0')
545
pz = other*(bloch=='1') + 'Z' + other*(bloch=='0')
546
prob_z = add_line('Z',pz,pz)
547
prob_x = add_line('X',pz,px)
548
for j,point in enumerate(self.points[pz]):
549
if see_if_unhidden(pz):
550
point.center = (self.box[pz][0]-(prob_x-0.5)*length, self.box[pz][1]-(prob_z-0.5)*length)
551
point.radius = (j==0)*0.05 + (j==1)*0.04
552
px = 'I'*(bloch=='0') + 'X' + 'I'*(bloch=='1')
553
pz = 'I'*(bloch=='0') + 'Z' + 'I'*(bloch=='1')
554
add_line('Z',pz,pz)
555
add_line('X',px,px)
556
if self.y_boxes:
557
for pauli in ['IY','YI','XY','YX','ZY','YZ','YY']:
558
add_line('ZXY',pauli,pauli)
559
else:
560
for pauli in self.box:
561
for point in self.points[pauli]:
562
point.radius = 0.0
563
if pauli in ['ZI','IZ','ZZ']:
564
add_line('Z',pauli,pauli)
565
if pauli in ['XI','IX','XX']:
566
add_line('X',pauli,pauli)
567
if pauli in ['XZ','ZX']+self.y_boxes*['IY','YI','XY','YX','ZY','YZ','YY']:
568
add_line('ZXY',pauli,pauli)
569
570
self.bottom.set_text(message)
571
572
if labels:
573
for pauli in self.box:
574
plt.text(self.box[pauli][0]-0.18,self.box[pauli][1]-0.85, pauli)
575
576
if self.y_boxes:
577
self.ax.set_xlim([-4,4])
578
self.ax.set_ylim([0,8])
579
else:
580
self.ax.set_xlim([-3,3])
581
self.ax.set_ylim([0,6])
582
583
if output is None:
584
self.fig.canvas.draw()
585
else:
586
plt.close() # prevent the graphic from showing inline
587
output.value = self.fig
588
589
self.initial = False
590
591