from __future__ import annotations
import networkx as nx
import numpy as np
from qutip import Qobj, basis, lindblad_dissipator
[docs]
class StochasticOperator(object):
def __init__(
self,
graph: nx.Graph,
noiseParam: float =None,
sinkNode: int = None,
sinkRate: float = None) -> None:
"""
Stochastic quantum walker on QuTip.
Class containing an open quantum system described by a Lindblad equation obtained from the adjacency matrix.
Theoretical model:
Whitfield, J. D. et al.
Quantum stochastic walks: A generalization of classical random walks and quantum walks.
@author: Lorenzo Buffoni
@github: https://github.com/Buffoni
Parameters
----------
graph : networkx.Graph
The graph representing the quantum walk space.
noiseParam : float, optional
The noise parameter controlling the quantum-classical mix (default is 0 for a fully quantum system).
sinkNode : int, optional
The index of the sink node in the graph (default is None, indicating no sink node).
sinkRate : float, optional
The rate at which probability is transferred to the sink node (default is 1).
"""
self._graph = graph
self.n = len(self._graph)
self._adjacencyMatrix = (
nx.adjacency_matrix(self._graph).todense().astype(complex)
)
self._buildLaplacian()
if noiseParam is None:
self._p = 0.0
else:
self._p = noiseParam
if sinkRate is None:
self._sinkRate = 1.0
else:
self._sinkRate = sinkRate
self._sinkNode = sinkNode
self._quantumHamiltonian = Qobj()
self._classicalHamiltonian = []
[docs]
def buildStochasticOperator(
self,
noiseParam: float = None,
sinkNode: int = None,
sinkRate: float = None) -> None:
"""
Creates the Hamiltonian and the Lindblad operators for the walker given an adjacency matrix
and other parameters.
Parameters
----------
noiseParam : float, optional
The noise parameter controlling the quantum-classical mix (default is 0 for a fully quantum system).
sinkNode : int, optional
The index of the sink node in the graph (default is None, indicating no sink node).
sinkRate : float, optional
The rate at which probability is transferred to the sink node (default is 1).
"""
if noiseParam is not None:
self._p = noiseParam
if sinkRate is not None:
self._sinkRate = sinkRate
if sinkNode is not None:
self._sinkNode = sinkNode
self._buildQuantumHamiltonian()
self._buildClassicalHamiltonian()
[docs]
def _buildLaplacian(self) -> None:
"""
Internal method to build the Laplacian matrix from the adjacency matrix of the graph.
"""
degree = np.sum(self._adjacencyMatrix, axis=0).flat
degree = list(map(lambda x: 1 / x if x > 0 else 0, degree))
self._laplacian = np.multiply(self._adjacencyMatrix, degree)
[docs]
def _buildQuantumHamiltonian(self) -> None:
"""
Internal method to build the quantum Hamiltonian from the graph's adjacency matrix.
"""
if self._sinkNode is not None:
H = Qobj(
(1 - self._p)
* np.pad(self._adjacencyMatrix, [(0, 1), (0, 1)], "constant")
)
else:
H = Qobj((1 - self._p) * self._adjacencyMatrix)
self._quantumHamiltonian = H
[docs]
def _buildClassicalHamiltonian(self) -> None:
"""
Internal method to build the classical Hamiltonian (Lindblad operators).
"""
# print('_buildClassicalHamiltonian Temporarily unavailable')
if self._sinkNode is not None:
L = [
np.sqrt(self._p * self._laplacian[i, j])
* (basis(self.n + 1, i) * basis(self.n + 1, j).dag())
for i in range(self.n)
for j in range(self.n)
if self._laplacian[i, j] > 0
]
S = np.zeros([self.n + 1, self.n + 1])
S[self.n, self._sinkNode] = np.sqrt(2 * self._sinkRate)
L.append(Qobj(S))
else:
L = [
np.sqrt(self._p * self._laplacian[i, j])
* (basis(self.n, i) * basis(self.n, j).dag())
for i in range(self.n)
for j in range(self.n)
if self._laplacian[i, j] > 0
]
# Compute superoperator from list of Lindblad operators
superop = lindblad_dissipator(L[0])
for i in range(len(L)-1):
superop += lindblad_dissipator(L[i+1])
self._classicalHamiltonian = superop
[docs]
def getClassicalHamiltonian(self) -> list:
"""
Returns the classical Hamiltonian (Lindblad operators) of the system.
Returns
-------
list[Qobj]
A list of Qobj representing the Lindblad operators.
"""
# print('getClassicalHamiltonian Temporarily unavailable')
# return None
return self._classicalHamiltonian
[docs]
def setClassicalHamiltonian(self, newClassicalHamiltonian) -> None:
"""
Sets a new classical Hamiltonian for the system.
Parameters
----------
newClassicalHamiltonian : list of Qobj
The new list of Lindblad operators to set as the classical Hamiltonian.
"""
self._classicalHamiltonian = newClassicalHamiltonian
[docs]
def getQuantumHamiltonian(self) -> Qobj:
"""
Returns the quantum Hamiltonian of the system.
Returns
-------
Qobj
The quantum Hamiltonian as a QuTiP object.
"""
return self._quantumHamiltonian
[docs]
def setQuantumHamiltonian(self, newQuantumHamiltonian) -> None:
"""
Sets a new quantum Hamiltonian for the system.
Parameters
----------
newQuantumHamiltonian : Qobj
The new quantum Hamiltonian as a QuTiP object.
"""
self._quantumHamiltonian = newQuantumHamiltonian
[docs]
def setSinkNode(self, newSinkNode) -> None:
"""
Sets a new sink node for the system.
Parameters
----------
newSinkNode : int
The index of the new sink node in the graph.
"""
self._sinkNode = newSinkNode
[docs]
def getSinkNode(self) -> int:
"""
Returns the index of the current sink node in the graph.
Returns
-------
int
The index of the sink node.
"""
return self._sinkNode
[docs]
def getLaplacian(self) -> np.ndarray:
"""
Returns the Laplacian matrix of the graph.
Returns
-------
np.ndarray
The Laplacian matrix.
"""
return self._laplacian