from __future__ import annotations
from typing import Union
import numpy as np
from scipy.linalg import inv
import json
from qwak.Errors import StateOutOfBounds, NonUnitaryState
from utils.jsonTools import json_matrix_to_complex, complex_matrix_to_json
[docs]
class State:
def __init__(self, n: int, nodeList: list = None,
customStateList: list = None) -> None:
"""Object is initialized with a mandatory user inputted dimension, an optional
stateList parameter which will be used to create the amplitudes for each node in the state
and an internal stateVec which will be a Numpy ndarray representing the column vector.
Parameters
----------
n : int
Desired dimension of the state.
nodeList : list, optional
List containing what nodes will have uniform superposition in the state, by default None.
customStateList : list, optional
Custom amplitudes for the state, by default None.
"""
self._n = n
if nodeList is None:
self._nodeList = []
else:
self._nodeList = nodeList
if customStateList is None:
self._customStateList = []
else:
self._customStateList = customStateList
self._stateVec = np.zeros((self._n, 1), dtype=complex)
[docs]
def buildState(
self,
nodeList: list = None,
customStateList: list = None) -> None:
"""Builds state vector from state list, by creating a balanced superposition of all
nodes in the nodeList.
This will be changed in the future to make nodeList make more sense.
Parameters
----------
nodeList : list, optional
List containing what nodes will have uniform superposition in the state, by default None.
customStateList : list, optional
Custom amplitudes for the state, by default None.
"""
if nodeList is not None:
self.resetState()
self._nodeList = nodeList
if customStateList is not None:
self.resetState()
self._customStateList = customStateList
if self._customStateList:
self._checkUnitaryStateList(self._customStateList)
for customState in self._customStateList:
self._checkStateOutOfBounds(customState[0])
self._stateVec[customState[0]] = customState[1]
else:
nodeAmp = np.sqrt(len(self._nodeList))
for state in self._nodeList:
self._checkStateOutOfBounds(state)
self._stateVec[state] = 1 / nodeAmp
[docs]
def _checkStateOutOfBounds(self, node: int) -> None:
"""Checks if the state is out of bounds for the system.
Parameters
----------
node : int
Node to check.
Raises
------
StateOutOfBounds
Out of bounds exception.
"""
if node >= self._n:
raise StateOutOfBounds(
f"State {node} is out of bounds for system of size {self._n} ([0-{self._n - 1}])."
)
[docs]
def _checkUnitaryStateList(self, customStateList) -> None:
"""Checks if the sum of the square of the amplitudes is 1.
Parameters
----------
customStateList : list
Custom state list.
Raises
------
NonUnitaryState
Non unitary state exception.
"""
unitaryState = 0
for state in customStateList:
unitaryState += np.abs(state[1]) ** 2
unitaryState = round(unitaryState, 5)
if unitaryState != float(1):
raise NonUnitaryState(
f"The sum of the square of the amplitudes is -- {unitaryState} -- instead of 1."
)
[docs]
def herm(self) -> np.ndarray:
"""Returns the Hermitian conjugate of the state vector.
Returns
-------
np.ndarray
Hermitian conjugate of the state vector.
"""
return self._stateVec.H
[docs]
def inv(self) -> np.ndarray:
"""Returns the inverse of the state vector.
Returns
-------
np.ndarray
Inverse of the state vector.
"""
return inv(self._stateVec)
[docs]
def resetState(self) -> None:
"""Resets the components of the State."""
self._stateVec = np.zeros((self._n, 1), dtype=complex)
[docs]
def setDim(self, newDim: int, newNodeList: list = None) -> None:
"""Sets the current state dimension to a user defined one.
Parameters
----------
newDim : int
New state dimension.
newNodeList : list, optional
List containing the new nodes, by default None.
"""
self._n = newDim
self._stateVec = np.zeros((self._n, 1), dtype=complex)
if newNodeList is not None:
self._nodeList = newNodeList
[docs]
def getDim(self) -> int:
"""Gets the current state dimension.
Returns
-------
int
State dimension.
"""
return self._n
[docs]
def setNodeList(self, newNodeList: list) -> None:
"""Sets current node list to a user inputted one.
Parameters
----------
newNodeList : list
List containing the new nodes.
"""
self._nodeList = newNodeList
[docs]
def getNodeList(self) -> list:
"""Gets the current list of nodes.
Returns
-------
list
Current list of nodes.
"""
return self._nodeList
[docs]
def setStateVec(self, newVec: np.ndarray) -> None:
"""Sets the column vector associated with the state to a user defined one.
Parameters
----------
newVec : np.ndarray
New column vector for the state.
"""
self._stateVec = newVec
[docs]
def getStateVec(self) -> np.ndarray:
"""Gets the column vector associated with the state.
Returns
-------
np.ndarray
Vector of the State.
"""
return self._stateVec
[docs]
def setState(self, newState: State) -> None:
"""Sets all the parameters of the current state to user defined ones.
Parameters
----------
newState : State
New state.
"""
self._n = newState.getDim()
self._nodeList = newState.getNodeList()
self._stateVec = newState.getStateVec()
[docs]
def to_json(self) -> str:
"""In contrast, the to_json method is not marked with the @classmethod decorator because
it is a method that is called on an instance of the Operator class.
This means that it can access the attributes of the instance on which it is called, and it
uses these attributes to generate the JSON string representation of the Operator instance.
Since it requires access to the attributes of a specific Operator instance, it cannot be
called on the Operator class itself.
Returns
-------
str
JSON string representation of the Operator instance.
"""
state_dict = {
"n": self._n,
"node_list": self._nodeList,
"custom_state_list": self._customStateList,
"state_vec": complex_matrix_to_json(
self._stateVec.tolist()),
}
return json.dumps(state_dict)
[docs]
@classmethod
def from_json(cls, json_var: Union[str, dict]):
"""The from_json method is marked with the @classmethod decorator because it is a method that is called on the class itself,
rather than on an instance of the class.
This is necessary because it is used to create a new instance of the Operator class from a JSON string,
and it does not require an instance of the Operator class to do so.
Parameters
----------
json_var : Union([str, dict])
JSON string or dictionary representation of the Operator instance.
Returns
-------
Operator
Operator instance from JSON string or dictionary representation.
"""
if isinstance(json_var, str):
state_dict = json.loads(json_var)
elif isinstance(json_var, dict):
state_dict = json_var
n = state_dict["n"]
node_list = state_dict["node_list"]
custom_state_list = state_dict["custom_state_list"]
state_vec = np.array(
json_matrix_to_complex(
state_dict["state_vec"]),
dtype=complex)
state = cls(n, node_list, custom_state_list)
state.setStateVec(state_vec)
return state
def __mul__(self, other: np.ndarray) -> np.ndarray:
"""Left-side multiplication for the State class.
Parameters
----------
other : np.ndarray
Another Numpy ndarray to multiply the state by.
Returns
-------
np.ndarray
Array of the multiplication
"""
return self._stateVec * other
def __rmul__(self, other: np.ndarray) -> np.ndarray:
"""Left-side multiplication for the State class.
Parameters
----------
other : np.ndarray
Another Numpy ndarray to multiply the state by.
Returns
-------
np.ndarray
Array of the multiplication.
"""
return other * self._stateVec
def __matmul__(self, other: np.ndarray) -> np.ndarray:
"""Matrix multiplication for the State class.
Parameters
----------
other : np.ndarray
Another Numpy ndarray to multiply the state by.
Returns
-------
np.ndarray
Array of the multiplication.
"""
return self._stateVec @ other
def __str__(self) -> str:
"""String representation of the State class.
Returns
-------
str
State string.
"""
return f"{self._stateVec}"
def __repr__(self) -> str:
"""String representation of the State class.
Returns
-------
str
State string.
"""
return f"N: {self._n}\n" \
f"Node list: {self._nodeList}\n" \
f"Custom Node list: {self._customStateList}\n" \
f"State:\n\t{self._stateVec}"