Diagram#

class discopy.quantum.optics.Diagram(dom, cod, boxes, offsets, layers=None)[source]#

Bases: Diagram

Linear optical network seen as a diagram of beam splitters, phase shifters and Mach-Zender interferometers.

Example

>>> BS >> BS
optics.Diagram(dom=PRO(2), cod=PRO(2), boxes=[BS, BS], offsets=[0, 0])
property array#

The array corresponding to the diagram. Builds a block diagonal matrix for each layer and then multiplies them in sequence.

Example

>>> np.shape(to_matrix(BS).array)
(2, 2)
>>> np.shape(to_matrix(BS >> BS).array)
(2, 2)
>>> np.shape(to_matrix(BS @ BS @ BS).array)
(6, 6)
>>> assert np.allclose(
...     to_matrix(MZI(0, 0)).array, np.array([[0, 1], [1, 0]]))
>>> assert np.allclose(to_matrix(MZI(0, 0) >> MZI(0, 0)).array,
...                    to_matrix(Id(2)).array)
amp(x, y, permanent=<function npperm>)[source]#

Evaluates the amplitude of an optics.Diagram on input x and output y, when sending indistinguishable photons.

Parameters:
  • x (List[int]) – Input vector of occupation numbers

  • y (List[int]) – Output vector of occupation numbers

  • permanent (callable, optional) – Use another function for computing the permanent or set permanent = np.determinant to compute fermionic statistics

Example

>>> network = MZI(0.2, 0.4) @ MZI(0.2, 0.4)                      >> Id(1) @ MZI(0.2, 0.4) @ Id(1)
>>> amplitude = network.amp([1, 0, 0, 1], [1, 0, 1, 0])
>>> probability = np.abs(amplitude) ** 2
>>> assert probability > 0.05
eval(n_photons, permanent=<function npperm>)[source]#

Evaluates the matrix acting on the Fock space given number of photons.

Parameters:
  • n_photons (int) – Number of photons

  • permanent (callable, optional) – Use another function for computing the permanent (e.g. from thewalrus)

Example

>>> for i, _ in enumerate(occupation_numbers(3, 2)): assert np.isclose(
...       sum(np.absolute(MZI(0.2, 0.4).eval(3)[i])**2), 1)
>>> network = MZI(0.2, 0.4) @ Id(1) >> Id(1) @ MZI(0.2, 0.4)
>>> for i, _ in enumerate(occupation_numbers(2, 3)): assert np.isclose(
...       sum(np.absolute(network.eval(2)[i])**2), 1)
indist_prob(x, y, permanent=<function npperm>)[source]#

Evaluates the probability for indistinguishable bosons by taking the born rule of the amplitude.

Parameters:
  • x (List[int]) – Input vector of occupation numbers

  • y (List[int]) – Output vector of occupation numbers

  • permanent (callable, optional) – Use another function for computing the permanent (e.g. from thewalrus)

Example

>>> box = MZI(0.2, 0.6)
>>> assert np.isclose(sum([box.indist_prob([3, 0], y)
...                        for y in occupation_numbers(3, 2)]), 1)
>>> network = box @ box @ box >> Id(1) @ box @ box @ Id(1)
>>> assert np.isclose(sum([network.indist_prob([0, 1, 0, 1, 1, 1], y)
...                        for y in occupation_numbers(4, 6)]), 1)
dist_prob(x, y, permanent=<function npperm>)[source]#

Evaluates probability of an optics.Diagram for input x and output y, when sending distinguishable particles.

Parameters:
  • x (List[int]) – Input vector of occupation numbers

  • y (List[int]) – Output vector of occupation numbers

  • permanent (callable, optional) – Use another function for computing the permanent (e.g. from thewalrus)

Example

>>> box = MZI(1.2, 0.6)
>>> assert np.isclose(sum([box.dist_prob([3, 0], y)
...                        for y in occupation_numbers(3, 2)]), 1)
>>> network = box @ box @ box >> Id(1) @ box @ box @ Id(1)
>>> assert np.isclose(sum([network.dist_prob([0, 1, 0, 1, 1, 1], y)
...                        for y in occupation_numbers(4, 6)]), 1)
pdist_prob(x, y, S, permanent=<function npperm>)[source]#

Calculates the probabilities for partially distinguishable photons.

Parameters:
  • x (List[int]) – Input vector of occupation numbers

  • y (List[int]) – Output vector of occupation numbers

  • S (np.array) – Symmetric matrix of mutual distinguishabilities of shape (n_photons, n_photons)

  • permanent (callable, optional) – Use another function for computing the permanent

Example

Check the Hong-Ou-Mandel effect:

>>> BS = BBS(0)
>>> x = [1, 1]
>>> S = np.eye(2)
>>> assert np.isclose(BS.pdist_prob(x, x, S), 0.5)
>>> S = np.ones((2, 2))
>>> assert np.isclose(BS.pdist_prob(x, x, S), 0)
>>> S = lambda p: np.array([[1, p], [p, 1]])
>>> for p in [0.1*x for x in range(11)]:
...     assert np.isclose(BS.pdist_prob(x, x, S(p)), 0.5 * (1 - p **2))
cl_distribution(x)[source]#

Computes the distribution of classical light in the outputs given an input distribution x.

Parameters:

x (List[float]) – Input vector of positive reals (intensities), expected to sum to 1. If the vector is not normalised the output will have the same normalisation factor.

Example

>>> TBS(0.25).cl_distribution([2/3, 1/3])
array([0.5, 0.5])
>>> assert np.allclose(TBS(0.25).cl_distribution([2/3, 1/3]),
...                    TBS(0.25).cl_distribution([1/5, 4/5]))
>>> BS = TBS(0.25)
>>> d = BS @ BS >> Id(1) @ BS @ Id(1)
>>> d.cl_distribution([0, 1/2, 1/2, 0])
array([0.25, 0.25, 0.25, 0.25])
indist_prob_ub(x, y)[source]#

Evaluates probability of an optics.Diagram for input x and output y, when sending distinguishable particles, excluding bunching output probabilities.

Parameters:
  • x (List[int]) – Input vector of occupation numbers

  • y (List[int]) – Output vector of occupation numbers

Example

>>> BS = TBS(0.25)
>>> d = BS @ BS >> Id(1) @ BS @ Id(1)
>>> unbunch = [s for s in occupation_numbers(2, 4) if set(s)=={0,1}]
>>> assert np.isclose(sum([d.indist_prob_ub([1, 1, 0, 0], y)
...                        for y in unbunch]), 1)
id#

alias of Id