Skip to content

Commit

Permalink
Merge pull request #157 from sequence-toolbox/RnD
Browse files Browse the repository at this point in the history
Version 0.5.4
  • Loading branch information
Alex-Kolar authored Dec 9, 2022
2 parents 9e12549 + 121883f commit a2d4da6
Show file tree
Hide file tree
Showing 14 changed files with 114 additions and 34 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.5.3]
### Changed
- Bug fixes to the GUI and QKD tests

## [0.5.4]
### Added
- Added some additional error checking to individual quantum states
- This includes additional error checking in the `Photon` class

### Changed
- The `entangle` method for the `Photon` and `FreeQuantumState` classes has been changed to `combine_state`
- Updated chapter 1 tutorial in the documentation to match code example

### Removed
- residual `json5` references in examples
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
author = 'Xiaoliang Wu, Joaquin Chung, Alexander Kolar, Eugene Wang, Tian Zhong, Rajkumar Kettimuthu, Martin Suchara'

# The full version, including alpha/beta/rc tags
release = '0.5.3'
release = '0.5.4'


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion example/absorptive_experiment.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import List, Dict, Any
from pathlib import Path

from json5 import dump
from json import dump
import numpy as np

from sequence.components.bsm import make_bsm
Expand Down
2 changes: 1 addition & 1 deletion example/absorptive_graph.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from json5 import load
from json import load
import matplotlib.pyplot as plt


Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name="sequence",
version="0.5.3",
version="0.5.4",
author="Xiaoliang Wu, Joaquin Chung, Alexander Kolar, Alexander Kiefer, Eugene Wang, Tian Zhong, Rajkumar Kettimuthu, Martin Suchara",
author_email="[email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]",
description="Simulator of Quantum Network Communication: SEQUENCE-Python is a prototype version of the official SEQUENCE release.",
Expand Down
4 changes: 2 additions & 2 deletions src/components/bsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ def get(self, photon, **kwargs):
return

# entangle photons to measure
self.photons[0].entangle(self.photons[1])
self.photons[0].combine_state(self.photons[1])

# measure in bell basis
res = Photon.measure_multiple(self.bell_basis, self.photons, self.get_generator())
Expand Down Expand Up @@ -338,7 +338,7 @@ def get(self, photon, **kwargs):
if self.get_generator().random() < self.phase_error:
self.photons[1].apply_phase_error()
# entangle photons to measure
self.photons[0].entangle(self.photons[1])
self.photons[0].combine_state(self.photons[1])

# measure in bell basis
res = Photon.measure_multiple(self.bell_basis, self.photons, self.get_generator())
Expand Down
6 changes: 3 additions & 3 deletions src/components/light_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def emit(self, state_list):
encoding_type=self.encoding_type,
use_qm=True)

new_photon0.entangle(new_photon1)
new_photon0.combine_state(new_photon1)
new_photon0.set_state((complex(0), complex(0), complex(0), complex(1)))
self.send_photons(time, [new_photon0, new_photon1])
self.photon_counter += 1
Expand All @@ -177,7 +177,7 @@ def emit(self, state_list):

new_photon0.is_null = True
new_photon1.is_null = True
new_photon0.entangle(new_photon1)
new_photon0.combine_state(new_photon1)
new_photon0.set_state((complex(1), complex(0), complex(0), complex(0)))
self.send_photons(time, [new_photon0, new_photon1])

Expand All @@ -201,7 +201,7 @@ def emit(self, state_list):
location=self,
encoding_type=self.encoding_type)

new_photon0.entangle(new_photon1)
new_photon0.combine_state(new_photon1)
new_photon0.set_state((state[0], complex(0), complex(0), state[1]))
self.send_photons(time, [new_photon0, new_photon1])
self.photon_counter += 1
Expand Down
20 changes: 13 additions & 7 deletions src/components/photon.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Photons may be encoded directly with polarization or time bin schemes, or may herald the encoded state of single atom memories.
"""
from typing import Dict, Any, List, Union, TYPE_CHECKING
from numpy import log2

if TYPE_CHECKING:
from numpy.random._generator import Generator
Expand Down Expand Up @@ -51,7 +52,8 @@ def __init__(self, name: str, timeline: "Timeline", wavelength=0, location=None,
wavelength (int): wavelength of photon (in nm) (default 0).
location (Entity): location of the photon (default None).
encoding_type (Dict[str, Any]): encoding type of photon (from encoding module) (default polarization).
quantum_state (Union[int, Tuple[complex]]): reference key for quantum manager, or complex coefficients for photon's quantum state.
quantum_state (Union[int, Tuple[complex]]):
reference key for quantum manager, or complex coefficients for photon's quantum state.
Default state is (1, 0).
If left blank and `use_qm` is true, will create new key from timeline quantum manager.
use_qm (bool): determines if the quantum state is obtained from the quantum manager or stored locally.
Expand All @@ -78,21 +80,25 @@ def __init__(self, name: str, timeline: "Timeline", wavelength=0, location=None,
self.quantum_state = quantum_state
else:
if quantum_state is None:
quantum_state=(complex(1), complex(0))
quantum_state = (complex(1), complex(0))
else:
assert type(quantum_state) is tuple
assert all([abs(a) <= 1.01 for a in quantum_state]), "Illegal value with abs > 1 in photon state"
assert abs(sum([abs(a) ** 2 for a in quantum_state]) - 1) < 1e-5, "Squared amplitudes do not sum to 1"
num_qubits = log2(len(quantum_state))
assert num_qubits == 1, "Length of amplitudes for single photon should be 2"
self.quantum_state = FreeQuantumState()
self.quantum_state.state = quantum_state

def __del__(self):
if self.use_qm and self.timeline is not None:
self.timeline.quantum_manager.remove(self.quantum_state)

def entangle(self, photon):
"""Method to entangle photons (see `QuantumState` module).
def combine_state(self, photon):
"""Method to combine quantum states of photons (see `QuantumState` module).
This method does not modify the current state of the photon, but combines the internal quantum state object.
This ensures that two photons share a quantum state object.
This ensures that two photons share a quantum state object describing a product space.
"""

if self.use_qm:
Expand All @@ -101,7 +107,7 @@ def entangle(self, photon):
self.timeline.quantum_manager.get(photon.quantum_state).keys
qm.run_circuit(Photon._entangle_circuit, all_keys)
else:
self.quantum_state.entangle(photon.quantum_state)
self.quantum_state.combine_state(photon.quantum_state)

def random_noise(self, rng: "Generator"):
"""Method to add random noise to photon's state (see `QuantumState` module)."""
Expand Down Expand Up @@ -151,7 +157,7 @@ def measure(basis, photon: "Photon", rng: "Generator"):

@staticmethod
def measure_multiple(basis, photons: List["Photon"], rng: "Generator"):
"""Method to measure 2 entangled photons (see `QuantumState` module).
"""Method to measure 2 entangled photons (see `FreeQuantumState` module).
Args:
basis (List[List[complex]]): basis (given as lists of complex coefficients) with which to measure the photons.
Expand Down
2 changes: 1 addition & 1 deletion src/components/spdc_lens.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def get(self, photon, **kwargs):
photon.wavelength /= 2
new_photon = deepcopy(photon)

photon.entangle(new_photon)
photon.combine_state(new_photon)
photon.set_state((state[0], complex(0), complex(0), state[1]))

self._receivers[0].get(photon)
Expand Down
17 changes: 13 additions & 4 deletions src/kernel/quantum_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def __init__(self, amplitudes: List[complex], keys: List[int]):

# check formatting
assert all([abs(a) <= 1.01 for a in amplitudes]), "Illegal value with abs > 1 in ket vector"
assert abs(sum([a ** 2 for a in amplitudes]) - 1) < 1e-5, "Squared amplitudes do not sum to 1"
assert abs(sum([abs(a) ** 2 for a in amplitudes]) - 1) < 1e-5, "Squared amplitudes do not sum to 1"
num_qubits = log2(len(amplitudes))
assert num_qubits.is_integer(), "Length of amplitudes should be 2 ** n, where n is the number of qubits"
assert num_qubits == len(keys), "Length of amplitudes should be 2 ** n, where n is the number of qubits"
Expand Down Expand Up @@ -149,8 +149,8 @@ def __init__(self):
self.state = (complex(1), complex(0))
self.entangled_states = [self]

def entangle(self, another_state: "FreeQuantumState"):
"""Method to entangle two quantum states.
def combine_state(self, another_state: "FreeQuantumState"):
"""Method to tensor multiply two quantum states.
Arguments:
another_state (QuantumState): state to entangle current state with.
Expand Down Expand Up @@ -186,12 +186,21 @@ def set_state(self, state: Tuple[complex]):
"""Method to change entangled state of multiple quantum states.
Args:
state (Tuple[complex]): new coefficients for state. Should be 2^n in length, where n is the length of `entangled_states`.
state (Tuple[complex]): new coefficients for state.
Should be 2^n in length, where n is the length of `entangled_states`.
Side Effects:
Modifies the `state` field for current and entangled states.
"""

# check formatting of state
assert all([abs(a) <= 1.01 for a in state]), "Illegal value with abs > 1 in quantum state"
assert abs(sum([abs(a) ** 2 for a in state]) - 1) < 1e-5, "Squared amplitudes do not sum to 1"
num_qubits = log2(len(state))
assert num_qubits.is_integer(), "Length of amplitudes should be 2 ** n, where n is the number of qubits"
assert num_qubits == len(self.entangled_states), \
"Length of amplitudes should be 2 ** n, where n is the number of qubits"

for qs in self.entangled_states:
qs.state = state

Expand Down
4 changes: 2 additions & 2 deletions tests/components/test_bsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,13 @@ def get_generator(self):
photons0[1] = Photon("", tl, encoding_type=absorptive, location=0, use_qm=True)
photons0[0].is_null = True
photons0[1].is_null = True
photons0[0].entangle(photons0[1])
photons0[0].combine_state(photons0[1])
photons0[0].set_state((complex(1), complex(0), complex(0), complex(0)))

# pair 1 (not null)
photons1[0] = Photon("", tl, encoding_type=absorptive, location=1, use_qm=True)
photons1[1] = Photon("", tl, encoding_type=absorptive, location=1, use_qm=True)
photons1[0].entangle(photons1[1])
photons1[0].combine_state(photons1[1])
photons1[0].set_state((complex(0), complex(0), complex(0), complex(1)))

# send part of each pair to bsm
Expand Down
6 changes: 3 additions & 3 deletions tests/components/test_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def get_generator(self):
p0 = Photon("", tl, encoding_type=absorptive, use_qm=True)
p1 = Photon("", tl, encoding_type=absorptive, use_qm=True)
p0.is_null = True
p0.entangle(p1)
p0.combine_state(p1)
p0.set_state(psi_minus)
qsd.get(p0)
qsd.get(p1)
Expand All @@ -300,7 +300,7 @@ def get_generator(self):
p0 = Photon("", tl, encoding_type=absorptive, use_qm=True)
p1 = Photon("", tl, encoding_type=absorptive, use_qm=True)
p0.is_null = True
p0.entangle(p1)
p0.combine_state(p1)
p0.set_state(psi_minus)
qsd.get(p0)
qsd.get(p1)
Expand All @@ -317,7 +317,7 @@ def get_generator(self):
p0 = Photon("", tl, encoding_type=absorptive, use_qm=True)
p1 = Photon("", tl, encoding_type=absorptive, use_qm=True)
p0.is_null = True
p0.entangle(p1)
p0.combine_state(p1)
p0.set_state(psi_minus)
qsd.get(p0)
qsd.get(p1)
Expand Down
23 changes: 19 additions & 4 deletions tests/components/test_photon.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import numpy as np
import pytest

from sequence.kernel.timeline import Timeline
from sequence.components.photon import Photon
Expand All @@ -13,11 +14,11 @@ def test_init():
assert photon.quantum_state.state == (complex(1), complex(0))


def test_entangle():
def test_combine_state():
tl = Timeline()
photon1 = Photon("p1", tl)
photon2 = Photon("p2", tl)
photon1.entangle(photon2)
photon1.combine_state(photon2)

state1 = photon1.quantum_state
state2 = photon2.quantum_state
Expand All @@ -33,12 +34,26 @@ def test_entangle():
def test_set_state():
tl = Timeline()
photon = Photon("", tl)

test_state = (complex(0), complex(1))
photon.set_state(test_state)

for i, coeff in enumerate(photon.quantum_state.state):
assert coeff == test_state[i]

# non-unit amplitudes
test_state = (complex(2), complex(0))
with pytest.raises(AssertionError, match="Illegal value with abs > 1 in quantum state"):
photon.set_state(test_state)

test_state = (complex(0), complex(0))
with pytest.raises(AssertionError, match="Squared amplitudes do not sum to 1"):
photon.set_state(test_state)

# incorrect size
test_state = (complex(1), complex(0), complex(0), complex(0))
with pytest.raises(AssertionError):
photon.set_state(test_state)


def test_measure():
tl = Timeline()
Expand All @@ -54,7 +69,7 @@ def test_measure_multiple():
tl = Timeline()
photon1 = Photon("p1", tl)
photon2 = Photon("p2", tl)
photon1.entangle(photon2)
photon1.combine_state(photon2)

basis = ((complex(1), complex(0), complex(0), complex(0)),
(complex(0), complex(1), complex(0), complex(0)),
Expand Down
46 changes: 42 additions & 4 deletions tests/kernel/test_quantum_state.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,49 @@
from sequence.kernel.quantum_state import FreeQuantumState
from sequence.utils.encoding import polarization
from math import sqrt
from numpy.random import default_rng
import pytest

from sequence.kernel.quantum_state import KetState, FreeQuantumState
from sequence.utils.encoding import polarization


rng = default_rng()


def test_build_ket():
keys = [0]

amps = [complex(1), complex(0)]
_ = KetState(amps, keys)

amps = [complex(sqrt(1/2)), complex(sqrt(1/2))]
_ = KetState(amps, keys)

amps = [complex(0), complex(1.j)]
_ = KetState(amps, keys)

# test with different size
amps = [complex(1), complex(0), complex(0), complex(0)]
_ = KetState(amps, [0, 1])

# test non-unit amplitudes
amps = [complex(3/2), complex(0)]
with pytest.raises(AssertionError, match="Illegal value with abs > 1 in ket vector"):
_ = KetState(amps, keys)

amps = [complex(0), complex(0)]
with pytest.raises(AssertionError, match="Squared amplitudes do not sum to 1"):
_ = KetState(amps, keys)

# test with invalid no. of amplitudes
amps = [complex(1), complex(0), complex(0)]
with pytest.raises(AssertionError):
_ = KetState(amps, keys)

amps = [complex(1), complex(0), complex(0), complex(0)]
with pytest.raises(AssertionError):
_ = KetState(amps, keys)


def test_measure():
qs = FreeQuantumState()
states = [(complex(1), complex(0)),
Expand Down Expand Up @@ -62,7 +100,7 @@ def test_measure_entangled():
for _ in range(100):
qs1.set_state_single(s)
qs2 = FreeQuantumState()
qs1.entangle(qs2)
qs1.combine_state(qs2)
res = qs1.measure(b, rng)
if res:
counter += 1
Expand All @@ -79,7 +117,7 @@ def test_measure_entangled():
for _ in range(1000):
qs1.set_state_single(s)
qs2 = FreeQuantumState()
qs1.entangle(qs2)
qs1.combine_state(qs2)
res = qs1.measure(b, rng)
if res:
counter += 1
Expand Down

0 comments on commit a2d4da6

Please sign in to comment.