11import numpy as np
22
3- from src .core .utils import hermitian , unitary
3+ from src .core .utils import (
4+ hermitian ,
5+ indefinite ,
6+ negative_semidefinite ,
7+ positive_semidefinite ,
8+ tensor ,
9+ unitary ,
10+ )
411
512
613class Operator :
714 def __init__ (self , matrix : np .ndarray , name : str = None ):
15+ """Initializes an Operator object which represents a quantum operator.
16+
17+ Parameter matrix: The matrix representation of the operator.
18+ Precondition: matrix is a numpy array.
19+
20+ Parameter name: The name of the operator.
21+ Precondition: name is a string or None.
22+ """
23+ assert isinstance (matrix , np .ndarray ), "matrix must be a numpy array"
24+ assert isinstance (name , str ) or name is None , "name must be a string or None"
825 self .matrix = matrix
926 self .name = name
1027 self .dimension = matrix .shape [0 ]
1128 self .is_hermitian = hermitian (matrix )
1229 self .is_unitary = unitary (matrix )
13- self .is_positive_semidefinite = np .all (np .linalg .eigvals (matrix ) >= 0 )
14- self .is_negative_semidefinite = np .all (np .linalg .eigvals (matrix ) <= 0 )
15- self .is_indefinite = np .any (np .linalg .eigvals (matrix ) > 0 ) and np .any (
16- np .linalg .eigvals (matrix ) < 0
17- )
30+ self .is_positive_semidefinite = positive_semidefinite (matrix )
31+ self .is_negative_semidefinite = negative_semidefinite (matrix )
32+ self .is_indefinite = indefinite (matrix )
1833
19- def is_hermitian (self ):
20- return hermitian (self .matrix )
34+ def is_hermitian (self ):
35+ """Returns whether the operator is Hermitian."""
36+ return hermitian (self .matrix )
2137
22- def is_unitary (self ):
23- return unitary (self .matrix )
38+ def is_unitary (self ):
39+ """Returns whether the operator is unitary."""
40+ return unitary (self .matrix )
2441
25- def is_positive_semidefinite (self ):
26- return np .all (np .linalg .eigvals (self .matrix ) >= 0 )
42+ def is_positive_semidefinite (self ):
43+ """Returns whether the operator is positive semidefinite."""
44+ return positive_semidefinite (self .matrix )
2745
28- def is_negative_semidefinite (self ):
29- return np .all (np .linalg .eigvals (self .matrix ) <= 0 )
46+ def is_negative_semidefinite (self ):
47+ """Returns whether the operator is negative semidefinite."""
48+ return negative_semidefinite (self .matrix )
3049
31- def is_indefinite (self ):
32- return np .any (np .linalg .eigvals (self .matrix ) > 0 ) and np .any (
33- np .linalg .eigvals (self .matrix ) < 0
34- )
50+ def is_indefinite (self ):
51+ """Returns whether the operator is indefinite."""
52+ return indefinite (self .matrix )
3553
3654 def __str__ (self ):
55+ """Returns a string representation of the operator."""
3756 return f"Operator(matrix={ self .matrix } )"
3857
3958 def __repr__ (self ):
59+ """Returns a string representation of the operator."""
4060 return f"Operator(matrix={ self .matrix } )"
4161
4262 def __eq__ (self , other ):
63+ """Returns whether the operator is equal to another operator."""
4364 return np .allclose (self .matrix , other .matrix )
4465
4566 def __ne__ (self , other ):
67+ """Returns whether the operator is not equal to another operator."""
4668 return not np .allclose (self .matrix , other .matrix )
4769
4870 def to_operator_set (self ):
71+ """Returns an OperatorSet containing the operator."""
4972 return OperatorSet ([self ])
5073
51- def to_local_operator (self , local_dim : int = 2 ):
52- local_dim = int (local_dim )
53- dim = self .dimension
54- n_sites = 0
55- p = 1
56- while p < dim :
57- p *= local_dim
58- n_sites += 1
59- if p != dim :
60- raise ValueError (
61- f"operator dimension { dim } is not { local_dim } **n for a non-negative "
62- "integer n; cannot wrap as LocalOperator on a contiguous block"
63- )
64- return LocalOperator (self .matrix , list (range (n_sites )), local_dim )
74+ def to_local_operator (self , sites : list [int ], local_dim : int = 2 ):
75+ """Returns a LocalOperator representing the operator acting on the given sites."""
76+ return LocalOperator (self .matrix , sites , local_dim )
77+
78+ def set_name (self , name : str | None ):
79+ """Sets the operator name."""
80+ assert isinstance (name , str ) or name is None , "name must be a string or None"
81+ self .name = name
6582
6683
6784class LocalOperator (Operator ):
6885 """
69- Operator acting non-trivially on the given `sites` of a tensor-product space.
70-
71- ``sites`` must be distinct integers forming a single contiguous block (e.g.
72- ``[1, 2, 3]`` or ``[3, 2, 1]``); gaps are not supported yet.
73-
74- The `matrix` dimension must match ``local_dim ** len(sites)``.
86+ Represents an operator that acts non-trivially on the given `sites` of a
87+ tensor-product space.
7588 """
7689
77- def __init__ (
78- self ,
79- matrix : np . ndarray ,
80- sites : list [ int ],
81- local_dim : int = 2 ,
82- ):
83- sites_list = list ( sites )
84- if not all ( isinstance ( site , int ) for site in sites_list ):
85- raise TypeError ( "all `sites` entries must be integers" )
86- if len ( set ( sites_list )) != len ( sites_list ):
87- raise ValueError ( "all `sites` entries must be unique" )
88- if len ( sites_list ) > 1 :
89- lo , hi = min ( sites_list ), max ( sites_list )
90- if hi - lo + 1 != len ( sites_list ):
91- raise ValueError (
92- "non-consecutive site indices are not supported yet; "
93- "` sites` must be a contiguous range of integers (e.g. "
94- "`[0, 1, 2]`). "
95- f"Got { sites_list !r } ."
96- )
97-
98- matrix = np . asarray ( matrix )
99- if matrix . ndim != 2 or matrix . shape [ 0 ] != matrix . shape [ 1 ]:
100- raise ValueError ( "`matrix` must be a square 2D array" )
101-
102- local_dim = int ( local_dim )
103- if local_dim <= 0 :
104- raise ValueError ( "` local_dim` must be a positive integer")
105-
106- expected_dim = local_dim ** len (sites_list )
90+ def __init__ (self , matrix : np . ndarray , sites : list [ int ], local_dim : int = 2 ):
91+ """Initializes a LocalOperator object which represents an operator that
92+ acts non-trivially on the given sites.
93+
94+ Parameter matrix: The matrix representation of the operator.
95+ Precondition: matrix is a numpy array.
96+
97+ Parameter sites: The sites on which the operator acts non-trivially.
98+ Precondition: sites is a list of contiguous integers.
99+
100+ Parameter local_dim: The local dimension of the operator.
101+ Precondition: local_dim is a positive integer."""
102+ assert isinstance ( matrix , np . ndarray ), "matrix must be a numpy array"
103+ assert (
104+ matrix . ndim == 2 and matrix . shape [ 0 ] == matrix . shape [ 1 ]
105+ ), "matrix must be a square 2D array "
106+ assert isinstance ( sites , list ), " sites must be a list "
107+ assert all (
108+ isinstance ( site , int ) for site in sites
109+ ), "all sites must be integers"
110+ assert len ( set ( sites )) == len ( sites ), "all sites must be unique"
111+ assert len ( sites ) >= 1 , "there must be at least one site"
112+ assert max ( sites ) - min ( sites ) + 1 == len (
113+ sites
114+ ), """sites
115+ must be a contiguous range of integers"""
116+ assert isinstance ( local_dim , int ), "local_dim must be an integer"
117+ assert local_dim > 0 , " local_dim must be a positive integer"
118+
119+ expected_dim = local_dim ** len (sites )
107120 if matrix .shape [0 ] != expected_dim :
108121 raise ValueError (
109122 "invalid number of sites for the given matrix: "
110- f"got len(sites)={ len (sites_list )} and local_dim={ local_dim } , "
123+ f"got len(sites)={ len (sites )} and local_dim={ local_dim } , "
111124 f"expected matrix dimension { expected_dim } x{ expected_dim } , "
112125 f"but got { matrix .shape [0 ]} x{ matrix .shape [1 ]} "
113126 )
114127
115128 super ().__init__ (matrix )
116- self .sites = sites_list
129+ self .sites = sites
117130 self .local_dim = local_dim
118131
132+ def get_sites (self ):
133+ """Returns the sites on which the local operator acts non-trivially."""
134+ return self .sites
135+
136+ def get_local_dim (self ):
137+ """Returns the local dimension of the local operator."""
138+ return self .local_dim
139+
140+ def get_matrix (self ):
141+ """Returns the matrix representation of the local operator."""
142+ return self .matrix
143+
144+ def to_operator (self ):
145+ """Returns an Operator representing the local operator."""
146+ return Operator (self .matrix )
147+
148+ def to_full_operator (self , total_sites : int ):
149+ """Embed this local operator into a full system of ``total_sites``.
150+
151+ The operator acts on its contiguous block ``self.sites`` and identity
152+ acts on all other sites.
153+
154+ Parameter total_sites: The total number of sites in the full system.
155+ Precondition: total_sites is a positive integer.
156+ """
157+ assert isinstance (total_sites , int ), "total_sites must be an integer"
158+ assert total_sites >= 1 , "total_sites must be positive"
159+
160+ lo , hi = min (self .sites ), max (self .sites )
161+ assert lo >= 0 , "site indices must be non-negative"
162+ if hi >= total_sites :
163+ raise ValueError (
164+ f"total_sites={ total_sites } is too small for local sites { self .sites } "
165+ )
166+
167+ eye = np .eye (self .local_dim , dtype = self .matrix .dtype )
168+ left_id = [eye ] * lo
169+ right_id = [eye ] * (total_sites - hi - 1 )
170+ full_matrix = tensor (left_id + [self .matrix ] + right_id )
171+ return Operator (full_matrix )
172+
119173 def __str__ (self ):
174+ """Returns a string representation of the local operator."""
120175 return (
121176 "LocalOperator("
122177 f"sites={ self .sites } , "
@@ -126,6 +181,7 @@ def __str__(self):
126181 )
127182
128183 def __repr__ (self ):
184+ """Returns a string representation of the local operator."""
129185 return (
130186 "LocalOperator("
131187 f"matrix={ self .matrix !r} , "
@@ -137,20 +193,38 @@ def __repr__(self):
137193
138194class OperatorSet :
139195 def __init__ (self , operators : list [Operator ]):
196+ """Initializes an OperatorSet object which represents a set of quantum operators.
197+
198+ Parameter operators: The list of operators to initialize the operator set with.
199+ Precondition: operators is a list of Operator objects.
200+ """
201+ assert isinstance (operators , list ), "operators must be a list"
202+ assert all (
203+ isinstance (op , Operator ) for op in operators
204+ ), "all operators must be Operator objects"
140205 self .operators = operators
141206 self .operator_count = len (operators )
142207
143208 def __iter__ (self ):
209+ """Returns an iterator over the operators in the operator set."""
144210 return iter (self .operators )
145211
146212 def __len__ (self ):
213+ """Returns the number of operators in the operator set."""
147214 return len (self .operators )
148215
149- def __getitem__ (self , item ):
150- return self .operators [item ]
216+ def __getitem__ (self , index : int ):
217+ """Returns the operator at the given index."""
218+ assert isinstance (index , int ), "index must be an integer"
219+ assert (
220+ index >= 0 and index < self .operator_count
221+ ), "index must be within the range of the operator set"
222+ return self .operators [index ]
151223
152224 def __str__ (self ):
225+ """Returns a string representation of the operator set."""
153226 return f"OperatorSet(operator_count={ self .operator_count } )"
154227
155228 def __repr__ (self ):
229+ """Returns a string representation of the operator set."""
156230 return f"OperatorSet(operators={ self .operators !r} )"
0 commit comments