"""Ansatze: various photonic quantum circuits"""
import copy
from typing import Any
import networkx as nx
import numpy as np
import torch
from scipy.optimize import root
from ..qmath import is_unitary
from .circuit import QumodeCircuit
from .qmath import sort_dict_fock_basis, takagi
from .state import FockState
[docs]
class Clements(QumodeCircuit):
"""Clements circuit."""
def __init__(
self,
nmode: int,
init_state: Any,
cutoff: int | None = None,
basis: bool = True,
phi_first: bool = True,
noise: bool = False,
mu: float = 0,
sigma: float = 0.1,
) -> None:
super().__init__(
nmode=nmode,
init_state=init_state,
cutoff=cutoff,
basis=basis,
name='Clements',
noise=noise,
mu=mu,
sigma=sigma,
)
self.phi_first = phi_first
wires1 = self.wires[1::2]
wires2 = self.wires[2::2]
if not phi_first:
for wire in self.wires:
self.ps(wire, encode=True)
for i in range(nmode):
if i % 2 == 0:
for j in range(len(wires1)):
self.mzi([wires1[j] - 1, wires1[j]], phi_first=phi_first, encode=True)
else:
for j in range(len(wires2)):
self.mzi([wires2[j] - 1, wires2[j]], phi_first=phi_first, encode=True)
if phi_first:
for wire in self.wires:
self.ps(wire, encode=True)
[docs]
def dict2data(self, angle_dict: dict, dtype=torch.float) -> torch.Tensor:
"""Convert the dictionary of angles to the input data for the circuit."""
angle_dict = angle_dict.copy()
for key in angle_dict:
angle = angle_dict[key]
if not isinstance(angle, torch.Tensor):
angle = torch.tensor(angle)
angle_dict[key] = angle.reshape(-1)
data = []
columns = np.array([0] * self.nmode)
wires1 = self.wires[1::2]
wires2 = self.wires[2::2]
if not self.phi_first:
for i in range(self.nmode):
data.append(angle_dict[(i, columns[i])])
columns[i] += 1
for i in range(self.nmode):
if i % 2 == 0:
for j in range(len(wires1)):
wire = wires1[j] - 1
if self.phi_first:
phi = angle_dict[(wire, columns[wire])]
theta = angle_dict[(wire, columns[wire] + 1)]
else:
theta = angle_dict[(wire, columns[wire])]
phi = angle_dict[(wire, columns[wire] + 1)]
data.append(theta)
data.append(phi)
columns[wire] += 2
else:
for j in range(len(wires2)):
wire = wires2[j] - 1
if self.phi_first:
phi = angle_dict[(wire, columns[wire])]
theta = angle_dict[(wire, columns[wire] + 1)]
else:
theta = angle_dict[(wire, columns[wire])]
phi = angle_dict[(wire, columns[wire] + 1)]
data.append(theta)
data.append(phi)
columns[wire] += 2
if self.phi_first:
for i in range(self.nmode):
data.append(angle_dict[(i, columns[i])])
columns[i] += 1
return torch.cat(data).to(dtype)
[docs]
class GaussianBosonSampling(QumodeCircuit):
"""Gaussian Boson Sampling circuit."""
def __init__(
self,
nmode: int,
squeezing: Any,
unitary: Any,
cutoff: int | None = None,
backend: str = 'gaussian',
basis: bool = True,
detector: str = 'pnrd',
noise: bool = False,
mu: float = 0,
sigma: float = 0.1,
) -> None:
if not isinstance(squeezing, torch.Tensor):
squeezing = torch.tensor(squeezing).reshape(-1)
if not isinstance(unitary, torch.Tensor):
unitary = torch.tensor(unitary, dtype=torch.cfloat).reshape(-1, nmode)
assert unitary.dtype in (torch.cfloat, torch.cdouble)
assert unitary.shape[-1] == unitary.shape[-2] == nmode
assert is_unitary(unitary)
if cutoff is None:
cutoff = 3
super().__init__(
nmode=nmode,
init_state='vac',
cutoff=cutoff,
backend=backend,
basis=basis,
detector=detector,
name='GBS',
noise=noise,
mu=mu,
sigma=sigma,
)
for i in range(self.nmode):
self.s(i, squeezing[i])
self.clements(unitary)
[docs]
class GraphGBS(GaussianBosonSampling):
"""Simulate Gaussian Boson Sampling for graph problems."""
def __init__(
self,
adj_mat: Any,
cutoff: int | None = None,
mean_photon_num: int | None = None,
detector: str = 'pnrd',
noise: bool = False,
mu: float = 0,
sigma: float = 0.1,
) -> None:
if not isinstance(adj_mat, torch.Tensor):
adj_mat = torch.tensor(adj_mat)
assert torch.allclose(adj_mat, adj_mat.mT)
self.adj_mat = adj_mat
nmode = self.adj_mat.size()[-1]
if mean_photon_num is None:
mean_photon_num = nmode
unitary, lambd = takagi(adj_mat)
c = self.norm_factor_c(mean_photon_num, lambd)[0]
self.c = c
lambda_c = lambd * c
squeezing = np.arctanh(lambda_c)
super().__init__(
nmode=nmode,
squeezing=squeezing,
unitary=unitary,
cutoff=cutoff,
backend='gaussian',
basis=False,
detector=detector,
noise=noise,
mu=mu,
sigma=sigma,
)
self.name = 'GraphGBS'
self.to(adj_mat.dtype)
[docs]
@staticmethod
def norm_factor_c(n_num, lambd, trials=20):
"""Get the normalization factor c of squeezing parameters for given total mean photon numbers."""
lambd = np.array(lambd)
def f(c, lambd, n_num):
ave_n = (lambd * c) ** 2 / (1 - (lambd * c) ** 2)
return sum(ave_n) - n_num
sol_re = []
for _ in range(trials):
x_0 = np.random.uniform(0, 1 / max(lambd), 1)[0]
re = root(f, x_0, (lambd, n_num))
if 0 < re.x < 1 / max(lambd):
sol_re.append(re.x[0])
return sol_re
[docs]
@staticmethod
def postselect(samples: dict, nodes_list: list) -> list:
"""Postselect the results with the fixed node subgraph."""
dic_list = [{} for _ in range(len(nodes_list))]
for key in samples:
temp = sum(key.state.tolist()) if isinstance(key, FockState) else sum(key)
if temp in nodes_list:
temp_idx = nodes_list.index(temp)
dic_list[temp_idx][key] = samples[key]
return dic_list
[docs]
@staticmethod
def graph_density(graph: nx.Graph, samples: dict) -> dict:
"""Get all subgraph densities."""
samples_ = copy.deepcopy(samples)
for key in samples_:
temp_prob = copy.deepcopy(samples_[key])
if isinstance(key, FockState):
idx = torch.nonzero(key.state).squeeze()
else:
idx = torch.nonzero(torch.tensor(key)).squeeze()
density = nx.density(graph.subgraph(idx.tolist()))
samples_[key] = [temp_prob, density]
sort_samples = sort_dict_fock_basis(samples_, 1)
return sort_samples