|
14 | 14 | from collections import defaultdict
|
15 | 15 | from typing import (
|
16 | 16 | AbstractSet,
|
| 17 | + Any, |
17 | 18 | Dict,
|
18 | 19 | Iterable,
|
19 | 20 | Mapping,
|
|
31 | 32 | from sympy.logic.boolalg import And, Not, Or, Xor
|
32 | 33 | from sympy.core.expr import Expr
|
33 | 34 | from sympy.core.symbol import Symbol
|
| 35 | +from scipy.sparse import csr_matrix |
34 | 36 |
|
35 | 37 | from cirq import linalg, protocols, qis, value
|
36 | 38 | from cirq._doc import document
|
37 | 39 | from cirq.linalg import operator_spaces
|
38 | 40 | from cirq.ops import identity, raw_types, pauli_gates, pauli_string
|
39 | 41 | from cirq.ops.pauli_string import PauliString, _validate_qubit_mapping
|
| 42 | +from cirq.ops.projector import ProjectorString |
40 | 43 | from cirq.value.linear_dict import _format_terms
|
41 | 44 |
|
42 | 45 | if TYPE_CHECKING:
|
@@ -739,3 +742,210 @@ def __format__(self, format_spec: str) -> str:
|
739 | 742 |
|
740 | 743 | def __str__(self) -> str:
|
741 | 744 | 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