@@ -249,61 +249,124 @@ class PulseSequence:
249249 :meth:`__getitem__`), NumPy would otherwise try to create an
250250 ndarray of single-segment :class:`PulseSequence` s.
251251 """
252+ c_opers : ndarray
253+ n_opers : ndarray
254+ c_oper_identifiers : ndarray
255+ n_oper_identifiers : ndarray
256+ c_coeffs : ndarray
257+ n_coeffs : ndarray
258+ dt : ndarray
259+ d : int
260+ basis : Basis
261+
262+ # Initialize attributes that can be set by bound methods to None
263+ _t = None
264+ _tau = None
265+ _omega = None
266+ _eigvals = None
267+ _eigvecs = None
268+ _propagators = None
269+ _total_phases = None
270+ _total_propagator = None
271+ _total_propagator_liouville = None
272+ _control_matrix = None
273+ _control_matrix_pc = None
274+ _filter_function = None
275+ _filter_function_gen = None
276+ _filter_function_pc = None
277+ _filter_function_pc_gen = None
278+ _filter_function_2 = None
279+
280+ def __new__ (cls , * args , ** kwargs ):
281+ new = super ().__new__ (cls )
282+ new ._intermediates = dict ()
283+ return new
284+
285+ def __init__ (self , H_c : Hamiltonian , H_n : Hamiltonian , dt : Coefficients ,
286+ basis : Optional [Basis ] = None ):
287+ if not util .is_sequence_like (dt ):
288+ raise TypeError (f'Expected a sequence of time steps, not { type (dt )} ' )
289+
290+ self .dt = np .asarray (dt )
291+ if not np .isreal (self .dt ).all ():
292+ raise ValueError ('Times dt are not (all) real!' )
293+ if (self .dt < 0 ).any ():
294+ raise ValueError ('Time steps are not (all) positive!' )
295+
296+ self .c_opers , self .c_oper_identifiers , self .c_coeffs = _parse_hamiltonian (
297+ H_c , len (self .dt ), 'H_c'
298+ )
299+ self .n_opers , self .n_oper_identifiers , self .n_coeffs = _parse_hamiltonian (
300+ H_n , len (self .dt ), 'H_n'
301+ )
302+
303+ if self .c_opers .shape [- 2 :] != self .n_opers .shape [- 2 :]:
304+ raise ValueError ('Control and noise Hamiltonian not same dimension!' )
305+
306+ # Dimension of the system
307+ self .d = self .c_opers .shape [- 1 ]
252308
253- def __init__ (self , * args , ** kwargs ) -> None :
254- """Initialize a PulseSequence instance."""
255- # Initialize attributes set by _parse_args to None to satisfy static
256- # code checker
257- self .c_opers = None
258- self .n_opers = None
259- self .c_oper_identifiers = None
260- self .n_oper_identifiers = None
261- self .c_coeffs = None
262- self .n_coeffs = None
263- self .dt = None
264- self .d = None
265- self .basis = None
266-
267- # Parse the input arguments and set attributes
268- # TODO: Jesus, this is in need of refactoring.
269- attributes = ('c_opers' , 'c_oper_identifiers' , 'c_coeffs' ,
270- 'n_opers' , 'n_oper_identifiers' , 'n_coeffs' ,
271- 'dt' , 'd' , 'basis' )
272- if not args :
273- # Bypass args parsing and directly set necessary attributes
274- values = (kwargs [attr ] for attr in attributes )
309+ if basis is None :
310+ # Use generalized Gell-Mann basis by default since we have a nice
311+ # expression for a basis expansion
312+ self .basis = Basis .ggm (self .d )
275313 else :
276- if len (args ) == 4 :
277- # basis given as arg, not kwarg
278- kwargs ['basis' ] = args [- 1 ]
279- elif len (args ) < 3 :
280- posargs = ['H_c' , 'H_n' , 'dt' ]
281- raise TypeError (f'Missing { 3 - len (args )} required positional argument(s): '
282- + f'{ posargs [len (args ):]} ' )
283-
284- values = _parse_args (* args [:3 ], ** kwargs )
285-
286- for attr , value in zip (attributes , values ):
287- setattr (self , attr , value )
288-
289- # Initialize attributes that can be set by bound methods to None
290- self ._t = None
291- self ._tau = None
292- self ._omega = None
293- self ._eigvals = None
294- self ._eigvecs = None
295- self ._propagators = None
296- self ._total_phases = None
297- self ._total_propagator = None
298- self ._total_propagator_liouville = None
299- self ._control_matrix = None
300- self ._control_matrix_pc = None
301- self ._filter_function = None
302- self ._filter_function_gen = None
303- self ._filter_function_pc = None
304- self ._filter_function_pc_gen = None
305- self ._filter_function_2 = None
306- self ._intermediates = dict ()
314+ if not isinstance (basis , Basis ):
315+ raise ValueError ("Expected basis to be an instance of the "
316+ + f"'filter_functions.basis.Basis' class, not { type (basis )} !" )
317+ if basis .shape [1 :] != (self .d , self .d ):
318+ # Make sure the basis has the correct dimension (we allow an
319+ # incomplete set)
320+ raise ValueError ("Expected basis elements to be of shape "
321+ + f"({ self .d } , { self .d } ), not { basis .shape [1 :]} !" )
322+ self .basis = basis
323+
324+ @classmethod
325+ def from_arrays (cls ,
326+ c_opers : ndarray , c_oper_identifiers : ndarray , c_coeffs : ndarray ,
327+ n_opers : ndarray , n_oper_identifiers : ndarray , n_coeffs : ndarray ,
328+ dt : ndarray , basis : Optional [Basis ] = None ):
329+ """Instantiate a :class:`PulseSequence` from NumPy arrays.
330+
331+ This is an alternative constructor to the nested list of lists
332+ structure that is used for initialization. See the class
333+ docstring for more information on the parameters.
334+
335+ Parameters
336+ ----------
337+ c_opers : ndarray, shape (n_cops, d, d)
338+ c_oper_identifiers : ndarray, shape (n_cops,)
339+ c_coeffs : ndarray, shape (n_cops, n_dt)
340+ n_opers : ndarray, shape (n_nops, d, d)
341+ n_oper_identifiers : ndarray, shape (n_nops,)
342+ n_coeffs : ndarray, shape (n_nops, n_dt)
343+ dt : ndarray, shape (n_dt,)
344+ basis : Basis, optional
345+
346+ """
347+ new = cls .__new__ (cls )
348+ new .c_opers = np .asanyarray (c_opers )
349+ new .c_oper_identifiers = np .asanyarray (c_oper_identifiers )
350+ new .c_coeffs = np .asanyarray (c_coeffs )
351+ new .n_opers = np .asanyarray (n_opers )
352+ new .n_oper_identifiers = np .asanyarray (n_oper_identifiers )
353+ new .n_coeffs = np .asanyarray (n_coeffs )
354+ new .dt = np .asanyarray (dt )
355+ new .d = new .c_opers .shape [- 1 ]
356+ new .basis = np .asanyarray (basis ).view (Basis ) if basis is not None else Basis .ggm (new .d )
357+
358+ if not len (new .c_opers ) == len (new .c_oper_identifiers ) == len (new .c_coeffs ):
359+ raise ValueError ('Control Hamiltonian not same length!' )
360+ if not len (new .n_opers ) == len (new .n_oper_identifiers ) == len (new .n_coeffs ):
361+ raise ValueError ('Noise Hamiltonian not same length!' )
362+ if not len (set (new .c_opers .shape [1 :] + new .n_opers .shape [1 :])) == 1 :
363+ raise ValueError ('Control and/or noise Hamiltonian not same, square dimension!' )
364+ if not new .dt .size == new .n_coeffs .shape [1 ] == new .c_coeffs .shape [1 ]:
365+ raise ValueError ('Time steps not same length!' )
366+ if not new .basis .d == new .d :
367+ raise ValueError ('Basis dimension not same as Hamiltonian dimension!' )
368+
369+ return new
307370
308371 def __str__ (self ):
309372 """String method."""
@@ -397,15 +460,14 @@ def __getitem__(self, key) -> 'PulseSequence':
397460 if not new_dt .size :
398461 raise IndexError ('Cannot create empty PulseSequence' )
399462
400- new = self .__class__ (
463+ new = self .__class__ . from_arrays (
401464 c_opers = self .c_opers ,
402465 n_opers = self .n_opers ,
403466 c_oper_identifiers = self .c_oper_identifiers ,
404467 n_oper_identifiers = self .n_oper_identifiers ,
405468 c_coeffs = np .atleast_2d (self .c_coeffs .T [key ]).T ,
406469 n_coeffs = np .atleast_2d (self .n_coeffs .T [key ]).T ,
407470 dt = new_dt ,
408- d = self .d ,
409471 basis = self .basis
410472 )
411473
@@ -1241,48 +1303,6 @@ def _join_equal_segments(pulse: PulseSequence) -> Sequence[Coefficients]:
12411303 return c_coeffs , n_coeffs , dt
12421304
12431305
1244- def _parse_args (H_c : Hamiltonian , H_n : Hamiltonian , dt : Coefficients , ** kwargs ) -> Any :
1245- """
1246- Function to parse the arguments given at instantiation of the
1247- PulseSequence object.
1248- """
1249-
1250- if not util .is_sequence_like (dt ):
1251- raise TypeError (f'Expected a sequence of time steps, not { type (dt )} ' )
1252-
1253- dt = np .asarray (dt )
1254- if not np .isreal (dt ).all ():
1255- raise ValueError ('Times dt are not (all) real!' )
1256- if (dt < 0 ).any ():
1257- raise ValueError ('Time steps are not (all) positive!' )
1258-
1259- control_args = _parse_hamiltonian (H_c , len (dt ), 'H_c' )
1260- noise_args = _parse_hamiltonian (H_n , len (dt ), 'H_n' )
1261-
1262- if control_args [0 ].shape [- 2 :] != noise_args [0 ].shape [- 2 :]:
1263- raise ValueError ('Control and noise Hamiltonian not same dimension!' )
1264-
1265- # Dimension of the system
1266- d = control_args [0 ].shape [- 1 ]
1267-
1268- basis = kwargs .pop ('basis' , None )
1269- if basis is None :
1270- # Use generalized Gell-Mann basis by default since we have a nice
1271- # expression for a basis expansion
1272- basis = Basis .ggm (d )
1273- else :
1274- if not isinstance (basis , Basis ):
1275- raise ValueError ("Expected basis to be an instance of the "
1276- + f"'filter_functions.basis.Basis' class, not { type (basis )} !" )
1277- if basis .shape [1 :] != (d , d ):
1278- # Make sure the basis has the correct dimension (we allow an
1279- # incomplete set)
1280- raise ValueError ("Expected basis elements to be of shape "
1281- + f"({ d } , { d } ), not { basis .shape [1 :]} !" )
1282-
1283- return * control_args , * noise_args , dt , d , basis
1284-
1285-
12861306def _parse_hamiltonian (H : Hamiltonian , n_dt : int , H_str : str ) -> Tuple [Sequence [Operator ],
12871307 Sequence [str ],
12881308 Sequence [Coefficients ]]:
@@ -1639,28 +1659,26 @@ def concatenate_without_filter_function(pulses: Iterable[PulseSequence],
16391659 if not util .all_array_equal ((pulse .basis for pulse in pulses )):
16401660 raise ValueError ('Trying to concatenate PulseSequence instances with different bases!' )
16411661
1642- control_keys = ('c_opers' , 'c_oper_identifiers' , 'c_coeffs' )
1643- noise_keys = ('n_opers' , 'n_oper_identifiers' , 'n_coeffs' )
1644-
1645- control_values = _concatenate_hamiltonian (* [[getattr (pulse , key ) for pulse in pulses ]
1646- for key in control_keys ],
1647- kind = 'control' )
1648- noise_values = _concatenate_hamiltonian (* [[getattr (pulse , key ) for pulse in pulses ]
1649- for key in noise_keys ],
1650- kind = 'noise' )
1651-
1652- attributes = {'dt' : np .concatenate (tuple (pulse .dt for pulse in pulses )),
1653- 'd' : pulses [0 ].d , 'basis' : pulses [0 ].basis }
1654- attributes .update (zip (control_keys , control_values ))
1655- attributes .update (zip (noise_keys , noise_values ))
1662+ * control_hamiltonian , control_mapping = _concatenate_hamiltonian (
1663+ * [[getattr (pulse , key ) for pulse in pulses ]
1664+ for key in ['c_opers' , 'c_oper_identifiers' , 'c_coeffs' ]],
1665+ kind = 'control'
1666+ )
1667+ * noise_hamiltonian , noise_mapping = _concatenate_hamiltonian (
1668+ * [[getattr (pulse , key ) for pulse in pulses ]
1669+ for key in ['n_opers' , 'n_oper_identifiers' , 'n_coeffs' ]],
1670+ kind = 'noise'
1671+ )
1672+ dt = np .concatenate (tuple (pulse .dt for pulse in pulses ))
1673+ basis = pulses [0 ].basis
16561674
1657- newpulse = PulseSequence ( ** attributes )
1675+ newpulse = PulseSequence . from_arrays ( * control_hamiltonian , * noise_hamiltonian , dt , basis )
16581676 # Only cache total duration (whole array of times might be large
16591677 # in case of concatenation)
16601678 newpulse .tau = sum (pulse .tau for pulse in pulses )
16611679
16621680 if return_identifier_mappings :
1663- return newpulse , control_values [ - 1 ], noise_values [ - 1 ]
1681+ return newpulse , control_mapping , noise_mapping
16641682
16651683 return newpulse
16661684
@@ -1905,15 +1923,14 @@ def concatenate_periodic(pulse: PulseSequence, repeats: int,
19051923 # Initialize a new PulseSequence instance with the Hamiltonians sequenced
19061924 # (this is much easier than in the general case, thus do it on the fly)
19071925 dt = np .tile (pulse .dt , repeats )
1908- newpulse = PulseSequence (
1926+ newpulse = PulseSequence . from_arrays (
19091927 c_opers = pulse .c_opers ,
1910- n_opers = pulse .n_opers ,
19111928 c_oper_identifiers = pulse .c_oper_identifiers ,
1912- n_oper_identifiers = pulse .n_oper_identifiers ,
19131929 c_coeffs = np .tile (pulse .c_coeffs , (1 , repeats )),
1930+ n_opers = pulse .n_opers ,
1931+ n_oper_identifiers = pulse .n_oper_identifiers ,
19141932 n_coeffs = np .tile (pulse .n_coeffs , (1 , repeats )),
19151933 dt = dt ,
1916- d = pulse .d ,
19171934 basis = pulse .basis
19181935 )
19191936 newpulse .tau = repeats * pulse .tau
@@ -2011,15 +2028,14 @@ def remap(pulse: PulseSequence, order: Sequence[int], d_per_qubit: int = 2,
20112028 n_oper_identifiers , n_sort_idx = _map_identifiers (pulse .n_oper_identifiers ,
20122029 oper_identifier_mapping )
20132030
2014- remapped_pulse = PulseSequence (
2031+ remapped_pulse = PulseSequence . from_arrays (
20152032 c_opers = c_opers [c_sort_idx ],
20162033 n_opers = n_opers [n_sort_idx ],
20172034 c_oper_identifiers = c_oper_identifiers [c_sort_idx ],
20182035 n_oper_identifiers = n_oper_identifiers [n_sort_idx ],
20192036 c_coeffs = pulse .c_coeffs [c_sort_idx ],
20202037 n_coeffs = pulse .n_coeffs [n_sort_idx ],
20212038 dt = pulse .dt ,
2022- d = pulse .d ,
20232039 basis = pulse .basis
20242040 )
20252041 remapped_pulse .t = pulse ._t
@@ -2429,15 +2445,14 @@ def extend(
24292445 c_sort_idx = np .argsort (c_oper_identifiers )
24302446 n_sort_idx = np .argsort (n_oper_identifiers )
24312447
2432- newpulse = PulseSequence (
2448+ newpulse = PulseSequence . from_arrays (
24332449 c_opers = np .asarray (c_opers )[c_sort_idx ],
24342450 n_opers = np .asarray (n_opers )[n_sort_idx ],
24352451 c_oper_identifiers = np .asarray (c_oper_identifiers )[c_sort_idx ],
24362452 n_oper_identifiers = np .asarray (n_oper_identifiers )[n_sort_idx ],
24372453 c_coeffs = np .asarray (c_coeffs )[c_sort_idx ],
24382454 n_coeffs = np .asarray (n_coeffs )[n_sort_idx ],
24392455 dt = pulses [0 ].dt ,
2440- d = d ,
24412456 basis = basis
24422457 )
24432458 newpulse .t = pulses [0 ]._t
0 commit comments