@@ -58,10 +58,20 @@ class ApplyUnitaryArgs:
5858 dtype as the target tensor.
5959 axes: Which axes the unitary effect is being applied to (e.g. the
6060 qubits that the gate is operating on).
61+ subspaces: Which subspace (in the computational basis) the unitary
62+ effect is being applied to, on each axis. By default it applies
63+ to subspace 0..d-1 on each axis, where d is the dimension of the
64+ unitary effect on that axis. Subspaces on each axis must be
65+ representable as a slice, so the dimensions specified here need to
66+ have a consistent step size.
6167 """
6268
6369 def __init__ (
64- self , target_tensor : np .ndarray , available_buffer : np .ndarray , axes : Iterable [int ]
70+ self ,
71+ target_tensor : np .ndarray ,
72+ available_buffer : np .ndarray ,
73+ axes : Iterable [int ],
74+ subspaces : Optional [Sequence [Tuple [int , ...]]] = None ,
6575 ):
6676 """Inits ApplyUnitaryArgs.
6777
@@ -75,11 +85,27 @@ def __init__(
7585 dtype as the target tensor.
7686 axes: Which axes the unitary effect is being applied to (e.g. the
7787 qubits that the gate is operating on).
78-
88+ subspaces: Which subspace (in the computational basis) the unitary
89+ effect is being applied to, on each axis. By default it applies
90+ to subspace 0..d-1 on each axis, where d is the dimension of
91+ the unitary effect on that axis. Subspaces on each axis must be
92+ representable as a slice, so the dimensions specified here need
93+ to have a consistent step size.
94+ Raises:
95+ ValueError: If the subspace count does not equal the axis count, if
96+ any subspace has zero dimensions, or if any subspace has
97+ dimensions specified without a consistent step size.
7998 """
8099 self .target_tensor = target_tensor
81100 self .available_buffer = available_buffer
82101 self .axes = tuple (axes )
102+ if subspaces is not None :
103+ if len (self .axes ) != len (subspaces ):
104+ raise ValueError ('Subspace count does not match axis count.' )
105+ for subspace , axis in zip (subspaces , self .axes ):
106+ if any (s >= target_tensor .shape [axis ] for s in subspace ):
107+ raise ValueError ('Subspace specified does not exist in axis.' )
108+ self .slices = None if subspaces is None else tuple (map (_to_slice , subspaces ))
83109
84110 @staticmethod
85111 def default (
@@ -125,7 +151,7 @@ def with_axes_transposed_to_start(self) -> 'ApplyUnitaryArgs':
125151 return ApplyUnitaryArgs (target_tensor , available_buffer , range (len (self .axes )))
126152
127153 def _for_operation_with_qid_shape (
128- self , indices : Iterable [int ], qid_shape : Tuple [int , ...]
154+ self , indices : Iterable [int ], slices : Tuple [Union [ int , slice ] , ...]
129155 ) -> 'ApplyUnitaryArgs' :
130156 """Creates a sliced and transposed view of `self` appropriate for an
131157 operation with shape `qid_shape` on qubits with the given indices.
@@ -138,14 +164,14 @@ def _for_operation_with_qid_shape(
138164 Args:
139165 indices: Integer indices into `self.axes` specifying which qubits
140166 the operation applies to.
141- qid_shape : The qid shape of the operation, the expected number of
142- quantum levels in each qubit the operation applies to.
167+ slices : The slices of the operation, the subdimension in each qubit
168+ the operation applies to.
143169
144170 Returns: A new `ApplyUnitaryArgs` where `sub_args.target_tensor` and
145171 `sub_args.available_buffer` are sliced and transposed views of
146172 `self.target_tensor` and `self.available_buffer` respectively.
147173 """
148- slices = [ slice (0 , size ) for size in qid_shape ]
174+ slices = tuple ( size if isinstance ( size , slice ) else slice (0 , size ) for size in slices )
149175 sub_axes = [self .axes [i ] for i in indices ]
150176 axis_set = set (sub_axes )
151177 other_axes = [axis for axis in range (len (self .target_tensor .shape )) if axis not in axis_set ]
@@ -369,8 +395,12 @@ def _strat_apply_unitary_from_apply_unitary(
369395 func = getattr (unitary_value , '_apply_unitary_' , None )
370396 if func is None :
371397 return NotImplemented
372- op_qid_shape = qid_shape_protocol .qid_shape (unitary_value , (2 ,) * len (args .axes ))
373- sub_args = args ._for_operation_with_qid_shape (range (len (op_qid_shape )), op_qid_shape )
398+ if args .slices is None :
399+ op_qid_shape = qid_shape_protocol .qid_shape (unitary_value , (2 ,) * len (args .axes ))
400+ slices = tuple (slice (0 , size ) for size in op_qid_shape )
401+ else :
402+ slices = args .slices
403+ sub_args = args ._for_operation_with_qid_shape (range (len (slices )), slices )
374404 sub_result = func (sub_args )
375405 if sub_result is NotImplemented or sub_result is None :
376406 return sub_result
@@ -390,8 +420,15 @@ def _strat_apply_unitary_from_unitary(
390420 if matrix is NotImplemented or matrix is None :
391421 return matrix
392422
393- val_qid_shape = qid_shape_protocol .qid_shape (unitary_value , default = (2 ,) * len (args .axes ))
394- sub_args = args ._for_operation_with_qid_shape (range (len (val_qid_shape )), val_qid_shape )
423+ if args .slices is None :
424+ val_qid_shape = qid_shape_protocol .qid_shape (unitary_value , default = (2 ,) * len (args .axes ))
425+ slices = tuple (slice (0 , size ) for size in val_qid_shape )
426+ else :
427+ slices = args .slices
428+ val_qid_shape = tuple (
429+ ((s .step if s .stop is None else s .stop ) - s .start ) // (s .step or 1 ) for s in slices
430+ )
431+ sub_args = args ._for_operation_with_qid_shape (range (len (slices )), slices )
395432 matrix = matrix .astype (sub_args .target_tensor .dtype )
396433 if len (val_qid_shape ) == 1 and val_qid_shape [0 ] <= 2 :
397434 # Special case for single-qubit, 2x2 or 1x1 operations.
@@ -557,3 +594,18 @@ def _incorporate_result_into_target(
557594 return args .available_buffer
558595 sub_args .target_tensor [...] = sub_result
559596 return args .target_tensor
597+
598+
599+ def _to_slice (subspace_def : Tuple [int , ...]):
600+ if len (subspace_def ) < 1 :
601+ raise ValueError (f'Subspace { subspace_def } has zero dimensions.' )
602+
603+ if len (subspace_def ) == 1 :
604+ return slice (subspace_def [0 ], subspace_def [0 ] + 1 , 1 )
605+
606+ step = subspace_def [1 ] - subspace_def [0 ]
607+ for i in range (len (subspace_def ) - 1 ):
608+ if subspace_def [i + 1 ] - subspace_def [i ] != step :
609+ raise ValueError (f'Subspace { subspace_def } does not have consistent step size.' )
610+ stop = subspace_def [- 1 ] + step
611+ return slice (subspace_def [0 ], stop if stop >= 0 else None , step )
0 commit comments