@@ -135,7 +135,7 @@ def expectation_from_samples(self, freq, qubit_map=None):
135
135
diag = self .backend .np .reshape (diag , self .nqubits * (2 ,))
136
136
if qubit_map is None :
137
137
qubit_map = range (self .nqubits )
138
- diag = self .backend .np .transpose (diag , axes = qubit_map ).ravel ()
138
+ diag = self .backend .np .transpose (diag , qubit_map ).ravel ()
139
139
# select only the elements with non-zero counts
140
140
diag = diag [[int (state , 2 ) for state in freq .keys ()]]
141
141
counts = self .backend .cast (list (freq .values ()), dtype = diag .dtype ) / sum (
@@ -373,6 +373,35 @@ def terms(self):
373
373
self .constant += term .coefficient
374
374
return terms
375
375
376
+ @cached_property
377
+ def diagonal_terms (self ) -> list [list [SymbolicTerm ]]:
378
+ """List of terms that can be diagonalized simultaneously, i.e. that
379
+ commute with each other. In detail each element of the list is a sublist
380
+ of commuting ``SymbolicTerm``s.
381
+ """
382
+ diagonal_terms = []
383
+ terms = self .terms
384
+ # loop until there are no more terms
385
+ while len (terms ) > 0 :
386
+ # take the first (remaining) term
387
+ t0 = terms [0 ]
388
+ diagonal_term = [t0 ]
389
+ removable_indices = {
390
+ 0 ,
391
+ }
392
+ # look for all the following terms that commute with the
393
+ # first one and among themselves
394
+ for i , t1 in enumerate (terms [1 :], 1 ):
395
+ # commutes with all the other terms -> append it
396
+ if all (term .commute (t1 ) for term in diagonal_term ):
397
+ diagonal_term .append (t1 )
398
+ removable_indices .add (i )
399
+ # append the new sublist of commuting terms found
400
+ diagonal_terms .append (diagonal_term )
401
+ # remove them from the original terms
402
+ terms = [term for i , term in enumerate (terms ) if i not in removable_indices ]
403
+ return diagonal_terms
404
+
376
405
@property
377
406
def matrix (self ):
378
407
"""Returns the full matrix representation.
@@ -502,46 +531,56 @@ def expectation_from_circuit(self, circuit: "Circuit", nshots: int = 1000) -> fl
502
531
from qibo import gates
503
532
504
533
rotated_circuits = []
505
- coefficients = []
506
534
Z_observables = []
507
535
qubit_maps = []
508
- for term in self .terms :
509
- # store coefficient
510
- coefficients .append (term .coefficient )
511
- # Only care about non-I terms
512
- non_identity_factors = [
513
- factor for factor in term .factors if factor .name [0 ] != "I"
514
- ]
515
- # build diagonal observable
516
- Z_observables .append (
517
- SymbolicHamiltonian (
518
- prod (Z (factor .target_qubit ) for factor in non_identity_factors ),
536
+ # loop over the terms that can be diagonalized simultaneously
537
+ for terms in self .diagonal_terms :
538
+ # for each term that can be diagonalized simultaneously
539
+ # extract the coefficient, Z observable and qubit map
540
+ # the basis rotation, instead, will be the same
541
+ tmp_obs = []
542
+ measurements = {}
543
+ for term in terms :
544
+ # Only care about non-I terms
545
+ non_identity_factors = []
546
+ # prepare the measurement basis and append it to the circuit
547
+ for factor in term .factors :
548
+ if factor .name [0 ] != "I" :
549
+ non_identity_factors .append (factor )
550
+ q = factor .target_qubit
551
+ if q not in measurements :
552
+ measurements [q ] = gates .M (q , basis = factor .gate .__class__ )
553
+
554
+ # build diagonal observable
555
+ # including the coefficient
556
+ symb_obs = prod (
557
+ Z (factor .target_qubit ) for factor in non_identity_factors
558
+ )
559
+ symb_obs = SymbolicHamiltonian (
560
+ term .coefficient * symb_obs ,
519
561
nqubits = circuit .nqubits ,
520
562
backend = self .backend ,
521
563
)
522
- )
564
+ tmp_obs .append (symb_obs )
565
+
523
566
# Get the qubits we want to measure for each term
524
- qubit_map = sorted (factor .target_qubit for factor in non_identity_factors )
525
- # prepare the measurement basis and append it to the circuit
526
- measurements = [
527
- gates .M (factor .target_qubit , basis = factor .gate .__class__ )
528
- for factor in non_identity_factors
529
- ]
567
+ qubit_maps .append (sorted (measurements .keys ()))
568
+ Z_observables .append (tmp_obs )
569
+
530
570
circ_copy = circuit .copy (True )
531
- circ_copy .add (measurements )
571
+ circ_copy .add (list ( measurements . values ()) )
532
572
rotated_circuits .append (circ_copy )
533
- # for mapping the obtained sample frequencies to the original qubits
534
- qubit_maps .append (qubit_map )
535
- frequencies = [
536
- result .frequencies ()
537
- for result in self .backend .execute_circuits (rotated_circuits , nshots = nshots )
538
- ]
539
- return sum (
540
- coeff * obs .expectation_from_samples (freq , qubit_map )
541
- for coeff , freq , obs , qubit_map in zip (
542
- coefficients , frequencies , Z_observables , qubit_maps
543
- )
544
- )
573
+
574
+ # execute the circuits
575
+ results = self .backend .execute_circuits (rotated_circuits , nshots = nshots )
576
+
577
+ # construct the expectation value for each diagonal term
578
+ # and sum all together
579
+ expval = 0.0
580
+ for res , obs , qmap in zip (results , Z_observables , qubit_maps ):
581
+ freq = res .frequencies ()
582
+ expval += sum (o .expectation_from_samples (freq , qmap ) for o in obs )
583
+ return self .constant + expval
545
584
546
585
def expectation_from_samples (self , freq : dict , qubit_map : list = None ) -> float :
547
586
"""
0 commit comments