230 likes | 246 Views
Explore the practical framework for simulating quantum networking protocols over noisy channels. Learn how superdense coding enhances fiber optic bandwidth, driving network efficiency. Discover SQUANCH, a Python simulator tailored for quantum networks, efficiently modeling entangled quantum systems. Simulate errors, quantum gates, fiber optic lines, and more with custom error models. Enhance your understanding of quantum networking technologies with SQUANCH.
E N D
8 September 2017 Ben Bartlett INQNET –Palo Alto Foundry A practical framework for simulating quantum networking protocols over noisy information channels
Motivation: quantum algorithms for classical networks Using superdense coding to improve fiber optic bandwidth
Introduction – quantum superdense coding • Superdense coding allows you to send two bits with one photon. Alice encodes two bits in the sign and phase of her photon: Alice and Bob share an entangled resource: Bob disentangled sign and phase and reconstructs the bits from measurement:
Superdense coding in networks • Superdense coding for increased transmission rate • Quantum superdense coding allows you to send two classical bits with one photon in a pre-distributed entangled pair • Network distributes entangled pairs between talkative nodes during low-demand times, stored in “memory” • During periods of high demand, entanglement is consumed via QSDC to (up to) double network transmission speed Effective transmission speed Channel use Distribute entangled pairs during down time Spike in network demand Transmission rate is doubled while pairs are being consumed Entanglement distribution doesn’t transmit data
Simulating quantum networks What we can learn from it and how we do it
Simulating quantum networks – motivation • Why simulate? • Simulating quantum networks allows us to test ideas and protocols for technology that is being developed • Over 70% of (classical) network research papers reference a network simulator • No widespread simulation framework for quantum networks • Can help us better define the needed technology to implement a networking idea • Characterize and simulate errors, figure out how to correct / compensate for them • Example: simulating superdense coding scheme realistically with published experimental values • Quantum gates: error rates • Fiber optic lines: attenuation, depolarizing/dephasing errors • Quantum memories: qubit type, coherence times, manipulation fidelity • Quantum node-channel links: properties taken from electro-optic coupling • Given these technologies, how well can our protocol perform?
Simulating quantum networks –a practical framework • SQUANCH – a simulator for quantum networks and channels • SQUANCH is a Python framework for creating performant simulations of quantum information processing and transmission • Computationally optimized for dealing with large numbers of entangled quantum subsystems • Simulating large amounts of entangled photons to transmit classical or quantum data • SQUANCH can also be used as a general-purpose quantum computing library, but it has a different focus: quantum computing vs quantum networks • Existing quantum computing frameworks such as Rigetti Forest and Microsoft LIQ are more developed/flexible • Both are limited to simulating a single system of qubits at a time • Simulating many smaller quantum systems involves lots of computational overhead • SQUANCH is capable of efficiently simulating hundreds of millions of disjoint quantum systems simultaneously
SQUANCH design overview Qubit Agent Channels Gates Errors QSystem QStream • Maintains references to the -qubit parent QSystem and an index ranging from to • Computationally cheap to use • Represents the state of a multi-particle quantum system • Can be instantiated as a wrapper for QStream for better cache locality • Includes methods for applying quantum gates and simulating wavefunction collapse • Applies unitary operators to the state of a QSystem or Qubit • Tensor-expands operators to fit an -qubit system and caches the results • Simulates corruption, attenuation, dephasing errors on qubits • Customizable error models • Represents an ensemble of disjoint -qubit systems • State is shared 1D array of C-doubles, behaves like 3D array of complex64 • Generalizes the notion of an actor (Alice, Bob, etc.) that can send/receive and process classical and quantum information • Run in separate processes • Connected by channels • Simulates noisy classical and quantum channels • Synchronizes clocks between agents on qubit transfer • Inherits configurable error models Qubit Qubit QStream QSystem
Building complex networks in SQUANCH • Agents and channels • Agents are entities that can process and share quantum and classical information • Classical memory and channels • Quantum memory and channels • Simulate errors on qubit transfer and in memory • Processor implemented by overriding agent.run() • Agents maintain their own clocks and memory pools • Different QStreams point to the same region of memory • Channels allow agents to communicate • Channels track speed-of-light delays and synchronize clocks between agents exchanging dependencies • Quantum channels simulate errors on transmitted qubits • Qubits passed by index to channel queues; clocks synchronized and various error models applied on retrieval Quantum-classical “agent” Processor Quantum memory Classical memory Quantum channel Classical channel Quantum channel Apply errors From agent To agent
QStream Apply errors • Memory structure and time-synchronization • Agents run concurrently in their own processes, extend base Agent() class and override run() • Process objects point to shared memory space to operate on Hilbert space • Qubits passed between processes by (system, qubit) index • Natural runtime concurrency safety from qubit ownership (processes block to wait on qubit transfer) alice= Alice(mem, data=data) bob = Bob(mem, out=outDict) Process <Alice> Process <Bob> connectAgents(alice, bob, length=1.0) class Bob(Agent):defrun(self):fori, qSysinenumerate(self.stream): a= self.qrecv(alice)_, b= qSys.qubitsifaisnotNoneandbisnotNone:CNOT(a, b)H(a)self.data[2*i] = a.measure()self.data[2*i+1] = b.measure()self.output(self.data) classAlice(Agent):defrun(self):forqSysinself.stream:bit1 = self.data.pop(0) bit2 = self.data.pop(0)q, _= qSys.qubitsifqisnotNone:ifbit2 == 1: X(q)ifbit1 == 1: Z(q)self.qsend(bob, q) qChannelOut stream other stream cmem qmem cmem qmem qChannelIn other Memory mem = sharedctypes.RawArray(ctypes.c_double, numSystems* dim * dim)arr= np.frombuffer(mem, dtype= np.complex64).reshape((numSystems, dim, dim))QStream.reformatArray(arr) Memory
QStream Apply errors • Memory structure and time-synchronization • Agents maintain their own clocks, synchronize on dependent qubit transfer • Example: Alice is at 1.5 s, Bob is at 2s, separated by 300 m. Alice transfers qubits with 10 ps pulse width to Bob. Alice’s clock is now s, and Bob’s clock is s. • Time-synchronization allows for performance metrics on networking algorithms alice= Alice(mem, data=data) bob = Bob(mem, out=outDict) Process <Alice> Process <Bob> connectAgents(alice, bob, length=1.0) class Bob(Agent):defrun(self):fori, qSysinenumerate(self.stream): a= self.qrecv(alice)_, b= qSys.qubitsifaisnotNoneandbisnotNone:CNOT(a, b)H(a)self.data[2*i] = a.measure()self.data[2*i+1] = b.measure()self.output(self.data) classAlice(Agent):defrun(self):forqSysinself.stream:bit1 = self.data.pop(0) bit2 = self.data.pop(0)q, _= qSys.qubitsifqisnotNone:ifbit2 == 1: X(q)ifbit1 == 1: Z(q)self.qsend(bob, q) qChannelOut stream other stream cmem qmem cmem qmem qChannelIn other Memory mem = sharedctypes.RawArray(ctypes.c_double, numSystems* dim * dim)arr= np.frombuffer(mem, dtype= np.complex64).reshape((numSystems, dim, dim))QStream.reformatArray(arr) Memory
Performance and ease of use How does SQUANCH compare to similar frameworks?
Performance comparisons • Test metric: serializing an image (12800 bits) and sending it via superdense coding over a noisy quantum channel • Results: • IBM Q: • Running locally with Quintuple (Python): 5.47 sec • Rigetti Forest / pyQuil: • Running on Rigetti QVM (Lisp backend): 418.5 sec • Running locally (Python backend): 101.2 sec • SQUANCH (running locally): • Without agents: 0.68 sec • With two agents: 0.78 sec • Key point: not necessarily better; SQUANCH is designed for different use cases than a quantum computing simulator Quantum channel
IBM Q Rigetti Forest Microsoft LIQ SQUANCH (with agents) #if INTERACTIVE #r @"..\bin\Liquid1.dll" #else namespace Microsoft.Research.Liquid // Tell the compiler our namespace #endif open System // Open any support libraries open Microsoft.Research.Liquid // Get necessary Liquid libraries open Util // General utilites open Operations // Basic gates and operations open Tests // Just gets us the RenderTest call fordumping module Script = // The script module allows forincr. loading // Define an EPR function let EPR (qs:Qubits) = H qs; CNOT qs let teleport (qs:Qubits) = let q0,q1,q2 = qs.[0],qs.[1],qs.[2] // Extract 3 qubits // Give names to the first three qubits LabelL "Src" [q0] LabelL "\\ket{0}" [q1] LabelL "\\ket{0}" [q2] EPR[q1;q2]; CNOT qs; H qs // EPR 1,2, then CNOT 0,1 and H 0 M[q1]; BC X [q1;q2] // Conditionally apply X M[q0]; BC Z [q0;q2] // Conditionally apply Z LabelR "Dest" [q2] // Label output let teleportRun (teleF:Qubits->unit) (cnt:int) = show "============ TELEPORT =============" let k = Ket() // Get a random number between -1.0and +1.0 let rVal = let rnd = Random(DateTime.Now.Ticks |> int) fun () -> 2.0 * rnd.NextDouble() - 1.0 for i in 0..(cnt-1) do letqs = k.Reset(3) let q0 =qs.[0] // Force the first qubit into a random location on the Bloch sphere q0.StateSet(rVal(),rVal(),rVal(),rVal()) show "Initial State: %O"q0 let _ = teleF qs show "Final State: %O (bits:%d%d)"qs.[2] (q0.Bit.v) (qs.[1].Bit.v) show "==================================" show "" [<LQD>] let Teleport() = // Run directly teleportRun teleport 10 // Look up the teleport circuit let ket = Ket(3) let circ = Circuit.Compile teleport ket.Qubits let circ2 = circ.Fold() circ2.Dump() // Don't do render's if we're really running under HPC (wasn't portable) iftrue then RenderTest "Teleport" circ ket // Run the circuit let teleF (qs:Qubits) = circ.Run qs teleportRun teleF 10 // Maked a wrapped gate out of teleport let teleGate = let gate (qs:Qubits) = new Gate( Qubits = qs.Length, Name = "teleGate", Op = WrapOp teleport ) fun (qs:Qubits) -> (gate qs).Run qs let ket = Ket(3) let circ = Circuit.Compile teleGate ket.Qubits let circ2 = circ.GrowGates(ket) show "Original circuit gates: %d" (circ.GateCount()) show " Grown circuit gates: %d" (circ2.GateCount()) show "Original circuit:" circ.Dump() show "Grown circuit:" circ2.Dump() let teleF (qs:Qubits) = circ2.Run qs teleportRun teleF 10 #if INTERACTIVE do Script.Teleport() // If interactive, then run the routine automatically #endif """ Quantum teleportation example based on an OpenQASM example. """ importsys importos sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) fromqiskitimportQuantumProgram importQconfig backend ="ibmqx_qasm_simulator" coupling_map= {0: [1, 2], 1: [2], 2: [], 3: [2, 4], 4: [2]} QPS_SPECS = { "circuits": [{ "name": "teleport", "quantum_registers": [{ "name": "q", "size": 3 }], "classical_registers": [ {"name": "c0", "size": 1}, {"name": "c1", "size": 1}, {"name": "c2", "size": 1}, ]}] } qp=QuantumProgram(specs=QPS_SPECS) qc =qp.get_circuit("teleport") q =qp.get_quantum_register("q") c0 =qp.get_classical_register("c0") c1 =qp.get_classical_register("c1") c2 =qp.get_classical_register("c2") # Prepare an initial state qc.u3(0.3, 0.2, 0.1, q[0]) # Prepare a Bell pair qc.h(q[1]) qc.cx(q[1], q[2]) # Barrier following state preparation qc.barrier(q) # Measure in the Bell basis qc.cx(q[0], q[1]) qc.h(q[0]) qc.measure(q[0], c0[0]) qc.measure(q[1], c1[0]) # Apply a correction qc.z(q[2]).c_if(c0, 1) qc.x(q[2]).c_if(c1, 1) qc.measure(q[2], c2[0]) qp.set_api(Qconfig.APItoken, Qconfig.config["url"]) # Experiment does not support feedback, so we use the simulator # First version: not mapped result =qp.execute(["teleport"], backend=backend, coupling_map=None, shots=1024) print(result) print(result.get_counts("teleport")) # Second version: mapped to qx2 coupling graph result =qp.execute(["teleport"], backend=backend, coupling_map=coupling_map, shots=1024) print(result) print(result.get_ran_qasm("teleport")) print(result.get_counts("teleport")) frompyquil.quilimport Program importpyquil.apiasapi frompyquil.gatesimport X, Z, H, CNOT defmake_bell_pair(q1, q2): """Makes a bell pair between qubits q1 and q2 """ return Program(H(q1), CNOT(q1, q2)) defteleport(start_index, end_index, ancilla_index): """Teleport a qubit from start to end using an ancilla qubit """ p =make_bell_pair(end_index, ancilla_index) # do the teleportation p.inst(CNOT(start_index, ancilla_index)) p.inst(H(start_index)) # measure the results and store them in registers [0] and [1] p.measure(start_index, 0) p.measure(ancilla_index, 1) p.if_then(1, X(2)) p.if_then(0, Z(2)) p.measure(end_index, 2) return p if __name__ =='__main__': qvm=api.SyncConnection() # initialize qubit 0 in |1> teleport_demo= Program(X(0)) teleport_demo+= teleport(0, 2, 1) print"Teleporting |1> state: ", qvm.run(teleport_demo, [2]) # initialize qubit 0 in |0> teleport_demo= Program() teleport_demo+= teleport(0, 2, 1) print"Teleporting |0> state: ", qvm.run(teleport_demo, [2]) # initialize qubit 0 in |+> teleport_demo= Program(H(0)) teleport_demo+= teleport(0, 2, 1) print"Teleporting |+> state: ", qvm.run(teleport_demo, [2], 10) print Program(X(0)).measure(0, 0).if_then(0, Program(X(1))) fromsquanch.agentimport* fromsquanch.gatesimport* fromsquanch.qstreamimport* classAlice(Agent): '''Alice sends qubits to Bob using a shared Bell pair''' defrun(self): forqSysinself.stream: # Generate a Bell pair and send half of it to Bob q, a, b =qSys.qubits H(a) CNOT(a, b) self.qsend(bob, b) # Perform the teleportation CNOT(q, a) H(q) bobZ=q.measure() bobX=a.measure() self.csend(bob, [bobX, bobZ]) classBob(Agent): '''Bob receives qubits from Alice and measures the results''' defrun(self): results = [] for _ inself.stream: b =self.qrecv(alice) doX, doZ=self.crecv(alice) ifdoX==1and b isnotNone: X(b) ifdoZ==1and b isnotNone: Z(b) results.append(b.measure()) self.output(results) # Allocate memory and output structures mem =sharedHilbertSpace(3, 10) out =sharedOutputDict() # Prepare the initial states stream =QStream.fromArray(mem) states = [1, 0, 1, 0, 1, 0, 1, 0, 1, 0] forqSys, state inzip(stream, states): if state ==1: X(qSys.qubit(0)) # Flip the qubits for 1's # Make the agents alice=Alice(mem) bob =Bob(mem, out = out) connectAgents(alice, bob, length =0.0) # Run everything alice.start(); bob.start() alice.join(); bob.join() print out["Bob"]
IBM Q Rigetti Forest Microsoft LIQ SQUANCH (without agents) #if INTERACTIVE #r @"..\bin\Liquid1.dll" #else namespace Microsoft.Research.Liquid // Tell the compiler our namespace #endif open System // Open any support libraries open Microsoft.Research.Liquid // Get necessary Liquid libraries open Util // General utilites open Operations // Basic gates and operations open Tests // Just gets us the RenderTest call fordumping module Script = // The script module allows forincr. loading // Define an EPR function let EPR (qs:Qubits) = H qs; CNOT qs let teleport (qs:Qubits) = let q0,q1,q2 = qs.[0],qs.[1],qs.[2] // Extract 3 qubits // Give names to the first three qubits LabelL "Src" [q0] LabelL "\\ket{0}" [q1] LabelL "\\ket{0}" [q2] EPR[q1;q2]; CNOT qs; H qs // EPR 1,2, then CNOT 0,1 and H 0 M[q1]; BC X [q1;q2] // Conditionally apply X M[q0]; BC Z [q0;q2] // Conditionally apply Z LabelR "Dest" [q2] // Label output let teleportRun (teleF:Qubits->unit) (cnt:int) = show "============ TELEPORT =============" let k = Ket() // Get a random number between -1.0and +1.0 let rVal = let rnd = Random(DateTime.Now.Ticks |> int) fun () -> 2.0 * rnd.NextDouble() - 1.0 for i in 0..(cnt-1) do letqs = k.Reset(3) let q0 =qs.[0] // Force the first qubit into a random location on the Bloch sphere q0.StateSet(rVal(),rVal(),rVal(),rVal()) show "Initial State: %O"q0 let _ = teleF qs show "Final State: %O (bits:%d%d)"qs.[2] (q0.Bit.v) (qs.[1].Bit.v) show "==================================" show "" [<LQD>] let Teleport() = // Run directly teleportRun teleport 10 // Look up the teleport circuit let ket = Ket(3) let circ = Circuit.Compile teleport ket.Qubits let circ2 = circ.Fold() circ2.Dump() // Don't do render's if we're really running under HPC (wasn't portable) iftrue then RenderTest "Teleport" circ ket // Run the circuit let teleF (qs:Qubits) = circ.Run qs teleportRun teleF 10 // Maked a wrapped gate out of teleport let teleGate = let gate (qs:Qubits) = new Gate( Qubits = qs.Length, Name = "teleGate", Op = WrapOp teleport ) fun (qs:Qubits) -> (gate qs).Run qs let ket = Ket(3) let circ = Circuit.Compile teleGate ket.Qubits let circ2 = circ.GrowGates(ket) show "Original circuit gates: %d" (circ.GateCount()) show " Grown circuit gates: %d" (circ2.GateCount()) show "Original circuit:" circ.Dump() show "Grown circuit:" circ2.Dump() let teleF (qs:Qubits) = circ2.Run qs teleportRun teleF 10 #if INTERACTIVE do Script.Teleport() // If interactive, then run the routine automatically #endif """ Quantum teleportation example based on an OpenQASM example. """ importsys importos sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) fromqiskitimportQuantumProgram importQconfig backend ="ibmqx_qasm_simulator" coupling_map= {0: [1, 2], 1: [2], 2: [], 3: [2, 4], 4: [2]} QPS_SPECS = { "circuits": [{ "name": "teleport", "quantum_registers": [{ "name": "q", "size": 3 }], "classical_registers": [ {"name": "c0", "size": 1}, {"name": "c1", "size": 1}, {"name": "c2", "size": 1}, ]}] } qp=QuantumProgram(specs=QPS_SPECS) qc =qp.get_circuit("teleport") q =qp.get_quantum_register("q") c0 =qp.get_classical_register("c0") c1 =qp.get_classical_register("c1") c2 =qp.get_classical_register("c2") # Prepare an initial state qc.u3(0.3, 0.2, 0.1, q[0]) # Prepare a Bell pair qc.h(q[1]) qc.cx(q[1], q[2]) # Barrier following state preparation qc.barrier(q) # Measure in the Bell basis qc.cx(q[0], q[1]) qc.h(q[0]) qc.measure(q[0], c0[0]) qc.measure(q[1], c1[0]) # Apply a correction qc.z(q[2]).c_if(c0, 1) qc.x(q[2]).c_if(c1, 1) qc.measure(q[2], c2[0]) qp.set_api(Qconfig.APItoken, Qconfig.config["url"]) # Experiment does not support feedback, so we use the simulator # First version: not mapped result =qp.execute(["teleport"], backend=backend, coupling_map=None, shots=1024) print(result) print(result.get_counts("teleport")) # Second version: mapped to qx2 coupling graph result =qp.execute(["teleport"], backend=backend, coupling_map=coupling_map, shots=1024) print(result) print(result.get_ran_qasm("teleport")) print(result.get_counts("teleport")) frompyquil.quilimport Program importpyquil.apiasapi frompyquil.gatesimport X, Z, H, CNOT defmake_bell_pair(q1, q2): """Makes a bell pair between qubits q1 and q2 """ return Program(H(q1), CNOT(q1, q2)) defteleport(start_index, end_index, ancilla_index): """Teleport a qubit from start to end using an ancilla qubit """ p =make_bell_pair(end_index, ancilla_index) # do the teleportation p.inst(CNOT(start_index, ancilla_index)) p.inst(H(start_index)) # measure the results and store them in registers [0] and [1] p.measure(start_index, 0) p.measure(ancilla_index, 1) p.if_then(1, X(2)) p.if_then(0, Z(2)) p.measure(end_index, 2) return p if __name__ =='__main__': qvm=api.SyncConnection() # initialize qubit 0 in |1> teleport_demo= Program(X(0)) teleport_demo+= teleport(0, 2, 1) print"Teleporting |1> state: ", qvm.run(teleport_demo, [2]) # initialize qubit 0 in |0> teleport_demo= Program() teleport_demo+= teleport(0, 2, 1) print"Teleporting |0> state: ", qvm.run(teleport_demo, [2]) # initialize qubit 0 in |+> teleport_demo= Program(H(0)) teleport_demo+= teleport(0, 2, 1) print"Teleporting |+> state: ", qvm.run(teleport_demo, [2], 10) print Program(X(0)).measure(0, 0).if_then(0, Program(X(1))) fromsquanch.gatesimport* fromsquanch.qstreamimport* # Prepare the initial states stream = QStream(3, 10) states = [1, 0, 1, 0, 1, 0, 1, 0, 1, 0] forqSys, state inzip(stream, states): if state ==1: X(qSys.qubit(0)) # Flip the qubits for 1's # Do the teleportation forqSysin stream: q, a, b =qSys.qubits H(a) CNOT(a, b) CNOT(q, a) H(q) ifa.measure(): X(b) ifq.measure(): Z(b) print"Qubit collapses to {}".format(q.measure())
SQUANCH: live coding demo A demonstration of simulating complex scenarios easily with SQUANCH (Alternately, a demonstration of my propensity to make careless coding mistakes)
Simulating the superdense network protocol with agents 648,000 bits sent via 324,000 photons Bob’s image Alice’s image Bob Charlie Alice Classical memory Processor Processor Processor 1km attenuated channel Classical memory Quantum memory Quantum memory 0.5km attenuated channel 0.5km attenuated channel
More simulations: naïve interception attack Eve Bob Charlie Alice Bob’s data is half corrupted, reveals eavesdropper. Eve recovers only random noise. Classical memory Classical memory Processor Processor Processor Processor 0.5km Classical memory Quantum memory Quantum memory 0.5km 0.5km 0.5km
Additional demonstrations, documentation, and more Repository Documentation
Future work Creating a quantum networking playground
Future work • SQUANCH framework development • GPU vectorization • Possible with shared GPU memory in CUDA • Implement more sophisticated characterization of errors in channels and quantum memories • Release the framework as an open source project • In later stages of getting intellectual property rights and licensing from AT&T • Deploy package on pip • Host documentation on readthedocs.io • Things to do with SQUANCH • More demonstrations for documentation • Calgary three-party teleportation experiment (Valivarthi, et al.) • Counterfactual quantum communication (Cao, et al.) • Use SQUANCH to simulate and characterize AT&T + Fermilab teleportation experiments
Ben Bartlett INQNET –Palo Alto Foundry