Skip to content

Commit 739808d

Browse files
authored
ProjectorSum object for TFQ (#4364)
This is a follow-up on #4331 which introduced ProjectorString objects. The present PR aims at adding ProjectorSum objects.
1 parent e6c584b commit 739808d

File tree

7 files changed

+520
-0
lines changed

7 files changed

+520
-0
lines changed

cirq-core/cirq/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@
256256
PhasedXZGate,
257257
PhaseFlipChannel,
258258
ProjectorString,
259+
ProjectorSum,
259260
RandomGateChannel,
260261
qft,
261262
Qid,

cirq-core/cirq/json_resolver_cache.py

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ def two_qubit_matrix_gate(matrix):
121121
'PhasedXPowGate': cirq.PhasedXPowGate,
122122
'PhasedXZGate': cirq.PhasedXZGate,
123123
'ProjectorString': cirq.ProjectorString,
124+
'ProjectorSum': cirq.ProjectorSum,
124125
'RandomGateChannel': cirq.RandomGateChannel,
125126
'QuantumFourierTransformGate': cirq.QuantumFourierTransformGate,
126127
'RepetitionsStoppingCriteria': cirq.work.RepetitionsStoppingCriteria,

cirq-core/cirq/ops/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
LinearCombinationOfOperations,
130130
PauliSum,
131131
PauliSumLike,
132+
ProjectorSum,
132133
)
133134

134135
from cirq.ops.mixed_unitary_channel import (

cirq-core/cirq/ops/linear_combinations.py

+210
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from collections import defaultdict
1515
from typing import (
1616
AbstractSet,
17+
Any,
1718
Dict,
1819
Iterable,
1920
Mapping,
@@ -31,12 +32,14 @@
3132
from sympy.logic.boolalg import And, Not, Or, Xor
3233
from sympy.core.expr import Expr
3334
from sympy.core.symbol import Symbol
35+
from scipy.sparse import csr_matrix
3436

3537
from cirq import linalg, protocols, qis, value
3638
from cirq._doc import document
3739
from cirq.linalg import operator_spaces
3840
from cirq.ops import identity, raw_types, pauli_gates, pauli_string
3941
from cirq.ops.pauli_string import PauliString, _validate_qubit_mapping
42+
from cirq.ops.projector import ProjectorString
4043
from cirq.value.linear_dict import _format_terms
4144

4245
if TYPE_CHECKING:
@@ -739,3 +742,210 @@ def __format__(self, format_spec: str) -> str:
739742

740743
def __str__(self) -> str:
741744
return self.__format__('.3f')
745+
746+
747+
def _projector_string_from_projector_dict(projector_dict, coefficient=1.0):
748+
return ProjectorString(dict(projector_dict), coefficient)
749+
750+
751+
@value.value_equality(approximate=True)
752+
class ProjectorSum:
753+
def __init__(
754+
self, linear_dict: Optional[value.LinearDict[FrozenSet[Tuple[raw_types.Qid, int]]]] = None
755+
):
756+
"""Constructor for ProjectorSum
757+
758+
Args:
759+
linear_dict: A linear dictionary from a set of tuples of (Qubit, integer) to a complex
760+
number. The tuple is a projector onto the qubit and the complex number is the
761+
weight of these projections.
762+
"""
763+
self._linear_dict: value.LinearDict[FrozenSet[Tuple[raw_types.Qid, int]]] = (
764+
linear_dict if linear_dict is not None else value.LinearDict({})
765+
)
766+
767+
def _value_equality_values_(self):
768+
return self._linear_dict
769+
770+
def _json_dict_(self) -> Dict[str, Any]:
771+
linear_dict = []
772+
for projector_dict, scalar in dict(self._linear_dict).items():
773+
key = [[k, v] for k, v in dict(projector_dict).items()]
774+
linear_dict.append([key, scalar])
775+
return {
776+
'cirq_type': self.__class__.__name__,
777+
'linear_dict': linear_dict,
778+
}
779+
780+
@classmethod
781+
def _from_json_dict_(cls, linear_dict, **kwargs):
782+
converted_dict = {}
783+
for projector_string in linear_dict:
784+
projector_dict = {x[0]: x[1] for x in projector_string[0]}
785+
scalar = projector_string[1]
786+
key = frozenset(projector_dict.items())
787+
converted_dict[key] = scalar
788+
return cls(linear_dict=value.LinearDict(converted_dict))
789+
790+
@classmethod
791+
def from_projector_strings(
792+
cls, terms: Union[ProjectorString, List[ProjectorString]]
793+
) -> 'ProjectorSum':
794+
"""Builds a ProjectorSum from one or more ProjectorString(s).
795+
796+
Args:
797+
terms: Either a single ProjectorString or a list of ProjectorStrings.
798+
799+
Returns:
800+
A ProjectorSum.
801+
"""
802+
if isinstance(terms, ProjectorString):
803+
terms = [terms]
804+
termdict: DefaultDict[FrozenSet[Tuple[raw_types.Qid, int]], value.Scalar] = defaultdict(
805+
lambda: 0.0
806+
)
807+
for pstring in terms:
808+
key = frozenset(pstring.projector_dict.items())
809+
termdict[key] += pstring.coefficient
810+
return cls(linear_dict=value.LinearDict(termdict))
811+
812+
def copy(self) -> 'ProjectorSum':
813+
return ProjectorSum(self._linear_dict.copy())
814+
815+
def matrix(self, projector_qids: Optional[Iterable[raw_types.Qid]] = None) -> csr_matrix:
816+
"""Returns the matrix of self in computational basis of qubits.
817+
818+
Args:
819+
projector_qids: Ordered collection of qubits that determine the subspace in which the
820+
matrix representation of the ProjectorSum is to be computed. Qbits absent from
821+
self.qubits are acted on by the identity. Defaults to the qubits of the
822+
projector_dict.
823+
824+
Returns:
825+
A sparse matrix that is the projection in the specified basis.
826+
"""
827+
return sum(
828+
coeff * _projector_string_from_projector_dict(vec).matrix(projector_qids)
829+
for vec, coeff in self._linear_dict.items()
830+
)
831+
832+
def expectation_from_state_vector(
833+
self,
834+
state_vector: np.ndarray,
835+
qid_map: Mapping[raw_types.Qid, int],
836+
) -> float:
837+
"""Compute the expectation value of this ProjectorSum given a state vector.
838+
839+
Projects the state vector onto the sum of projectors and computes the expectation of the
840+
measurements.
841+
842+
Args:
843+
state_vector: An array representing a valid state vector.
844+
qubit_map: A map from all qubits used in this ProjectorSum to the indices of the qubits
845+
that `state_vector` is defined over.
846+
Returns:
847+
The expectation value of the input state.
848+
"""
849+
return sum(
850+
coeff
851+
* _projector_string_from_projector_dict(vec).expectation_from_state_vector(
852+
state_vector, qid_map
853+
)
854+
for vec, coeff in self._linear_dict.items()
855+
)
856+
857+
def expectation_from_density_matrix(
858+
self,
859+
state: np.ndarray,
860+
qid_map: Mapping[raw_types.Qid, int],
861+
) -> float:
862+
"""Expectation of the sum of projections from a density matrix.
863+
864+
Projects the density matrix onto the sum of projectors and computes the expectation of the
865+
measurements.
866+
867+
Args:
868+
state: An array representing a valid density matrix.
869+
qubit_map: A map from all qubits used in this ProjectorSum to the indices of the qubits
870+
that `state_vector` is defined over.
871+
Returns:
872+
The expectation value of the input state.
873+
"""
874+
return sum(
875+
coeff
876+
* _projector_string_from_projector_dict(vec).expectation_from_density_matrix(
877+
state, qid_map
878+
)
879+
for vec, coeff in self._linear_dict.items()
880+
)
881+
882+
def __iter__(self):
883+
for vec, coeff in self._linear_dict.items():
884+
yield _projector_string_from_projector_dict(vec, coeff)
885+
886+
def __len__(self) -> int:
887+
return len(self._linear_dict)
888+
889+
def __truediv__(self, a: value.Scalar):
890+
return self.__mul__(1 / a)
891+
892+
def __bool__(self) -> bool:
893+
return bool(self._linear_dict)
894+
895+
def __iadd__(self, other: Union['ProjectorString', 'ProjectorSum']):
896+
if isinstance(other, ProjectorString):
897+
other = ProjectorSum.from_projector_strings(other)
898+
elif not isinstance(other, ProjectorSum):
899+
return NotImplemented
900+
self._linear_dict += other._linear_dict
901+
return self
902+
903+
def __add__(self, other: Union['ProjectorString', 'ProjectorSum']):
904+
if isinstance(other, ProjectorString):
905+
other = ProjectorSum.from_projector_strings(other)
906+
elif not isinstance(other, ProjectorSum):
907+
return NotImplemented
908+
result = self.copy()
909+
result += other
910+
return result
911+
912+
def __isub__(self, other: Union['ProjectorString', 'ProjectorSum']):
913+
if isinstance(other, ProjectorString):
914+
other = ProjectorSum.from_projector_strings(other)
915+
elif not isinstance(other, ProjectorSum):
916+
return NotImplemented
917+
self._linear_dict -= other._linear_dict
918+
return self
919+
920+
def __sub__(self, other: Union['ProjectorString', 'ProjectorSum']):
921+
if isinstance(other, ProjectorString):
922+
other = ProjectorSum.from_projector_strings(other)
923+
elif not isinstance(other, ProjectorSum):
924+
return NotImplemented
925+
result = self.copy()
926+
result -= other
927+
return result
928+
929+
def __neg__(self):
930+
factory = type(self)
931+
return factory(-self._linear_dict)
932+
933+
def __imul__(self, other: value.Scalar):
934+
if not isinstance(other, numbers.Complex):
935+
return NotImplemented
936+
self._linear_dict *= other
937+
return self
938+
939+
def __mul__(self, other: value.Scalar):
940+
if not isinstance(other, numbers.Complex):
941+
return NotImplemented
942+
result = self.copy()
943+
result *= other
944+
return result
945+
946+
def __rmul__(self, other: value.Scalar):
947+
if not isinstance(other, numbers.Complex):
948+
return NotImplemented
949+
result = self.copy()
950+
result *= other
951+
return result

0 commit comments

Comments
 (0)