13
13
#![ allow( clippy:: too_many_arguments) ]
14
14
#![ allow( clippy:: upper_case_acronyms) ]
15
15
16
- use hashbrown:: HashMap ;
16
+ use ahash:: RandomState ;
17
+ use hashbrown:: { HashMap , HashSet } ;
18
+ use indexmap:: IndexSet ;
17
19
use num_complex:: { Complex64 , ComplexFloat } ;
18
20
use smallvec:: { smallvec, SmallVec } ;
19
21
use std:: cmp:: Ordering ;
@@ -29,14 +31,19 @@ use pyo3::Python;
29
31
use ndarray:: prelude:: * ;
30
32
use numpy:: PyReadonlyArray2 ;
31
33
use pyo3:: pybacked:: PyBackedStr ;
34
+ use rustworkx_core:: petgraph:: stable_graph:: NodeIndex ;
32
35
33
36
use qiskit_circuit:: circuit_data:: CircuitData ;
37
+ use qiskit_circuit:: dag_circuit:: { DAGCircuit , NodeType } ;
34
38
use qiskit_circuit:: dag_node:: DAGOpNode ;
35
39
use qiskit_circuit:: operations:: { Operation , Param , StandardGate } ;
36
40
use qiskit_circuit:: slice:: { PySequenceIndex , SequenceIndex } ;
37
41
use qiskit_circuit:: util:: c64;
38
42
use qiskit_circuit:: Qubit ;
39
43
44
+ use crate :: nlayout:: PhysicalQubit ;
45
+ use crate :: target_transpiler:: Target ;
46
+
40
47
pub const ANGLE_ZERO_EPSILON : f64 = 1e-12 ;
41
48
42
49
#[ pyclass( module = "qiskit._accelerate.euler_one_qubit_decomposer" ) ]
@@ -69,6 +76,7 @@ impl OneQubitGateErrorMap {
69
76
}
70
77
}
71
78
79
+ #[ derive( Debug ) ]
72
80
#[ pyclass( sequence) ]
73
81
pub struct OneQubitGateSequence {
74
82
pub gates : Vec < ( StandardGate , SmallVec < [ f64 ; 3 ] > ) > ,
@@ -571,7 +579,7 @@ pub fn generate_circuit(
571
579
Ok ( res)
572
580
}
573
581
574
- #[ derive( Clone , Debug , Copy ) ]
582
+ #[ derive( Clone , Debug , Copy , Eq , Hash , PartialEq ) ]
575
583
#[ pyclass( module = "qiskit._accelerate.euler_one_qubit_decomposer" ) ]
576
584
pub enum EulerBasis {
577
585
U321 ,
@@ -684,24 +692,6 @@ fn compare_error_fn(
684
692
}
685
693
}
686
694
687
- fn compute_error (
688
- gates : & [ ( StandardGate , SmallVec < [ f64 ; 3 ] > ) ] ,
689
- error_map : Option < & OneQubitGateErrorMap > ,
690
- qubit : usize ,
691
- ) -> ( f64 , usize ) {
692
- match error_map {
693
- Some ( err_map) => {
694
- let num_gates = gates. len ( ) ;
695
- let gate_fidelities: f64 = gates
696
- . iter ( )
697
- . map ( |gate| 1. - err_map. error_map [ qubit] . get ( gate. 0 . name ( ) ) . unwrap_or ( & 0. ) )
698
- . product ( ) ;
699
- ( 1. - gate_fidelities, num_gates)
700
- }
701
- None => ( gates. len ( ) as f64 , gates. len ( ) ) ,
702
- }
703
- }
704
-
705
695
fn compute_error_term ( gate : & str , error_map : & OneQubitGateErrorMap , qubit : usize ) -> f64 {
706
696
1. - error_map. error_map [ qubit] . get ( gate) . unwrap_or ( & 0. )
707
697
}
@@ -724,15 +714,6 @@ fn compute_error_str(
724
714
}
725
715
}
726
716
727
- #[ pyfunction]
728
- pub fn compute_error_one_qubit_sequence (
729
- circuit : & OneQubitGateSequence ,
730
- qubit : usize ,
731
- error_map : Option < & OneQubitGateErrorMap > ,
732
- ) -> ( f64 , usize ) {
733
- compute_error ( & circuit. gates , error_map, qubit)
734
- }
735
-
736
717
#[ pyfunction]
737
718
pub fn compute_error_list (
738
719
circuit : Vec < PyRef < DAGOpNode > > ,
@@ -965,72 +946,231 @@ pub fn params_zxz(unitary: PyReadonlyArray2<Complex64>) -> [f64; 4] {
965
946
params_zxz_inner ( mat)
966
947
}
967
948
968
- type OptimizeDecompositionReturn = Option < ( ( f64 , usize ) , ( f64 , usize ) , OneQubitGateSequence ) > ;
949
+ fn compute_error_term_from_target ( gate : & str , target : & Target , qubit : PhysicalQubit ) -> f64 {
950
+ 1. - target. get_error ( gate, & [ qubit] ) . unwrap_or ( 0. )
951
+ }
952
+
953
+ fn compute_error_from_target_one_qubit_sequence (
954
+ circuit : & OneQubitGateSequence ,
955
+ qubit : PhysicalQubit ,
956
+ target : Option < & Target > ,
957
+ ) -> ( f64 , usize ) {
958
+ match target {
959
+ Some ( target) => {
960
+ let num_gates = circuit. gates . len ( ) ;
961
+ let gate_fidelities: f64 = circuit
962
+ . gates
963
+ . iter ( )
964
+ . map ( |gate| compute_error_term_from_target ( gate. 0 . name ( ) , target, qubit) )
965
+ . product ( ) ;
966
+ ( 1. - gate_fidelities, num_gates)
967
+ }
968
+ None => ( circuit. gates . len ( ) as f64 , circuit. gates . len ( ) ) ,
969
+ }
970
+ }
969
971
970
972
#[ pyfunction]
971
- pub fn optimize_1q_gates_decomposition (
972
- runs : Vec < Vec < PyRef < DAGOpNode > > > ,
973
- qubits : Vec < usize > ,
974
- bases : Vec < Vec < PyBackedStr > > ,
975
- simplify : bool ,
976
- error_map : Option < & OneQubitGateErrorMap > ,
977
- atol : Option < f64 > ,
978
- ) -> Vec < OptimizeDecompositionReturn > {
979
- runs. iter ( )
980
- . enumerate ( )
981
- . map ( |( index, raw_run) | -> OptimizeDecompositionReturn {
982
- let mut error = match error_map {
983
- Some ( _) => 1. ,
984
- None => raw_run. len ( ) as f64 ,
973
+ #[ pyo3( signature = ( dag, * , target=None , basis_gates=None , global_decomposers=None ) ) ]
974
+ pub ( crate ) fn optimize_1q_gates_decomposition (
975
+ py : Python ,
976
+ dag : & mut DAGCircuit ,
977
+ target : Option < & Target > ,
978
+ basis_gates : Option < HashSet < String > > ,
979
+ global_decomposers : Option < Vec < String > > ,
980
+ ) -> PyResult < ( ) > {
981
+ let runs: Vec < Vec < NodeIndex > > = dag. collect_1q_runs ( ) . unwrap ( ) . collect ( ) ;
982
+ let dag_qubits = dag. num_qubits ( ) ;
983
+ let mut target_basis_per_qubit: Vec < Option < IndexSet < EulerBasis , RandomState > > > =
984
+ vec ! [ None ; dag_qubits] ;
985
+ let mut basis_gates_per_qubit: Vec < Option < HashSet < & str > > > = vec ! [ None ; dag_qubits] ;
986
+ for raw_run in runs {
987
+ let mut error = match target {
988
+ Some ( _) => 1. ,
989
+ None => raw_run. len ( ) as f64 ,
990
+ } ;
991
+ let qubit: PhysicalQubit = if let NodeType :: Operation ( inst) = & dag. dag [ raw_run[ 0 ] ] {
992
+ dag. get_qubits ( inst. qubits ) [ 0 ] . into ( )
993
+ } else {
994
+ unreachable ! ( "nodes in runs will always be op nodes" )
995
+ } ;
996
+ if !dag. calibrations_empty ( ) {
997
+ let mut has_calibration = false ;
998
+ for node in & raw_run {
999
+ if dag. has_calibration_for_index ( py, * node) ? {
1000
+ has_calibration = true ;
1001
+ break ;
1002
+ }
1003
+ }
1004
+ if has_calibration {
1005
+ continue ;
1006
+ }
1007
+ }
1008
+ if basis_gates_per_qubit[ qubit. 0 as usize ] . is_none ( ) {
1009
+ let basis_gates = match target {
1010
+ Some ( target) => Some (
1011
+ target
1012
+ . operation_names_for_qargs ( Some ( & smallvec ! [ qubit] ) )
1013
+ . unwrap ( ) ,
1014
+ ) ,
1015
+ None => {
1016
+ let basis = basis_gates. as_ref ( ) ;
1017
+ basis. map ( |basis| basis. iter ( ) . map ( |x| x. as_str ( ) ) . collect ( ) )
1018
+ }
985
1019
} ;
986
- let qubit = qubits[ index] ;
987
- let operator = & raw_run
988
- . iter ( )
989
- . map ( |node| {
990
- if let Some ( err_map) = error_map {
991
- error *=
992
- compute_error_term ( node. instruction . operation . name ( ) , err_map, qubit)
993
- }
994
- node. instruction
995
- . operation
996
- . matrix ( & node. instruction . params )
997
- . expect ( "No matrix defined for operation" )
998
- } )
999
- . fold (
1000
- [
1001
- [ Complex64 :: new ( 1. , 0. ) , Complex64 :: new ( 0. , 0. ) ] ,
1002
- [ Complex64 :: new ( 0. , 0. ) , Complex64 :: new ( 1. , 0. ) ] ,
1003
- ] ,
1004
- |mut operator, node| {
1005
- matmul_1q ( & mut operator, node) ;
1006
- operator
1020
+ basis_gates_per_qubit[ qubit. 0 as usize ] = basis_gates;
1021
+ }
1022
+ let basis_gates = & basis_gates_per_qubit[ qubit. 0 as usize ] . as_ref ( ) ;
1023
+
1024
+ if target_basis_per_qubit[ qubit. 0 as usize ] . is_none ( ) {
1025
+ let mut target_basis_set: IndexSet < EulerBasis , RandomState > = match target {
1026
+ Some ( _target) => EULER_BASIS_MAP
1027
+ . iter ( )
1028
+ . enumerate ( )
1029
+ . filter_map ( |( idx, gates) | {
1030
+ if !gates
1031
+ . iter ( )
1032
+ . all ( |gate| basis_gates. as_ref ( ) . unwrap ( ) . contains ( gate) )
1033
+ {
1034
+ return None ;
1035
+ }
1036
+ let basis = EULER_BASIS_NAMES [ idx] ;
1037
+ Some ( basis)
1038
+ } )
1039
+ . collect ( ) ,
1040
+ None => match & global_decomposers {
1041
+ Some ( bases) => bases
1042
+ . iter ( )
1043
+ . map ( |basis| EulerBasis :: __new__ ( basis) . unwrap ( ) )
1044
+ . collect ( ) ,
1045
+ None => match basis_gates {
1046
+ Some ( gates) => EULER_BASIS_MAP
1047
+ . iter ( )
1048
+ . enumerate ( )
1049
+ . filter_map ( |( idx, basis_gates) | {
1050
+ if !gates. iter ( ) . all ( |gate| basis_gates. as_ref ( ) . contains ( gate) ) {
1051
+ return None ;
1052
+ }
1053
+ let basis = EULER_BASIS_NAMES [ idx] ;
1054
+ Some ( basis)
1055
+ } )
1056
+ . collect ( ) ,
1057
+ None => EULER_BASIS_NAMES . iter ( ) . copied ( ) . collect ( ) ,
1007
1058
} ,
1008
- ) ;
1009
- let old_error = if error_map. is_some ( ) {
1010
- ( 1. - error, raw_run. len ( ) )
1011
- } else {
1012
- ( error, raw_run. len ( ) )
1059
+ } ,
1013
1060
} ;
1014
- let target_basis_vec: Vec < EulerBasis > = bases[ index]
1015
- . iter ( )
1016
- . map ( |basis| EulerBasis :: __new__ ( basis) . unwrap ( ) )
1017
- . collect ( ) ;
1018
- unitary_to_gate_sequence_inner (
1019
- aview2 ( operator) ,
1020
- & target_basis_vec,
1021
- qubit,
1022
- error_map,
1023
- simplify,
1024
- atol,
1025
- )
1026
- . map ( |out_seq| {
1027
- let new_error = compute_error_one_qubit_sequence ( & out_seq, qubit, error_map) ;
1028
- ( old_error, new_error, out_seq)
1061
+ if target_basis_set. contains ( & EulerBasis :: U3 )
1062
+ && target_basis_set. contains ( & EulerBasis :: U321 )
1063
+ {
1064
+ target_basis_set. swap_remove ( & EulerBasis :: U3 ) ;
1065
+ }
1066
+ if target_basis_set. contains ( & EulerBasis :: ZSX )
1067
+ && target_basis_set. contains ( & EulerBasis :: ZSXX )
1068
+ {
1069
+ target_basis_set. swap_remove ( & EulerBasis :: ZSX ) ;
1070
+ }
1071
+ target_basis_per_qubit[ qubit. 0 as usize ] = Some ( target_basis_set) ;
1072
+ }
1073
+ let target_basis_set = target_basis_per_qubit[ qubit. 0 as usize ] . as_ref ( ) . unwrap ( ) ;
1074
+ let target_basis_vec: Vec < EulerBasis > = target_basis_set. iter ( ) . copied ( ) . collect ( ) ;
1075
+ let operator = raw_run
1076
+ . iter ( )
1077
+ . map ( |node_index| {
1078
+ let node = & dag. dag [ * node_index] ;
1079
+ if let NodeType :: Operation ( inst) = node {
1080
+ if let Some ( target) = target {
1081
+ error *= compute_error_term_from_target ( inst. op . name ( ) , target, qubit) ;
1082
+ }
1083
+ inst. op . matrix ( inst. params_view ( ) ) . unwrap ( )
1084
+ } else {
1085
+ unreachable ! ( "Can only have op nodes here" )
1086
+ }
1029
1087
} )
1030
- } )
1031
- . collect ( )
1088
+ . fold (
1089
+ [
1090
+ [ Complex64 :: new ( 1. , 0. ) , Complex64 :: new ( 0. , 0. ) ] ,
1091
+ [ Complex64 :: new ( 0. , 0. ) , Complex64 :: new ( 1. , 0. ) ] ,
1092
+ ] ,
1093
+ |mut operator, node| {
1094
+ matmul_1q ( & mut operator, node) ;
1095
+ operator
1096
+ } ,
1097
+ ) ;
1098
+
1099
+ let old_error = if target. is_some ( ) {
1100
+ ( 1. - error, raw_run. len ( ) )
1101
+ } else {
1102
+ ( error, raw_run. len ( ) )
1103
+ } ;
1104
+ let sequence = unitary_to_gate_sequence_inner (
1105
+ aview2 ( & operator) ,
1106
+ & target_basis_vec,
1107
+ qubit. 0 as usize ,
1108
+ None ,
1109
+ true ,
1110
+ None ,
1111
+ ) ;
1112
+ let sequence = match sequence {
1113
+ Some ( seq) => seq,
1114
+ None => continue ,
1115
+ } ;
1116
+ let new_error = compute_error_from_target_one_qubit_sequence ( & sequence, qubit, target) ;
1117
+
1118
+ let mut outside_basis = false ;
1119
+ if let Some ( basis) = basis_gates {
1120
+ for node in & raw_run {
1121
+ if let NodeType :: Operation ( inst) = & dag. dag [ * node] {
1122
+ if !basis. contains ( inst. op . name ( ) ) {
1123
+ outside_basis = true ;
1124
+ break ;
1125
+ }
1126
+ }
1127
+ }
1128
+ } else {
1129
+ outside_basis = false ;
1130
+ }
1131
+ if outside_basis
1132
+ || new_error < old_error
1133
+ || new_error. 0 . abs ( ) < 1e-9 && old_error. 0 . abs ( ) >= 1e-9
1134
+ {
1135
+ for gate in sequence. gates {
1136
+ dag. insert_1q_on_incoming_qubit ( ( gate. 0 , & gate. 1 ) , raw_run[ 0 ] ) ;
1137
+ }
1138
+ dag. add_global_phase ( py, & Param :: Float ( sequence. global_phase ) ) ?;
1139
+ dag. remove_1q_sequence ( & raw_run) ;
1140
+ }
1141
+ }
1142
+ Ok ( ( ) )
1032
1143
}
1033
1144
1145
+ static EULER_BASIS_MAP : [ & [ & str ] ; 12 ] = [
1146
+ & [ "u3" ] ,
1147
+ & [ "u3" , "u2" , "u1" ] ,
1148
+ & [ "u" ] ,
1149
+ & [ "p" , "sx" ] ,
1150
+ & [ "u1" , "rx" ] ,
1151
+ & [ "r" ] ,
1152
+ & [ "rz" , "ry" ] ,
1153
+ & [ "rz" , "rx" ] ,
1154
+ & [ "rz" , "rx" ] ,
1155
+ & [ "rx" , "ry" ] ,
1156
+ & [ "rz" , "sx" , "x" ] ,
1157
+ & [ "rz" , "sx" ] ,
1158
+ ] ;
1159
+ static EULER_BASIS_NAMES : [ EulerBasis ; 12 ] = [
1160
+ EulerBasis :: U3 ,
1161
+ EulerBasis :: U321 ,
1162
+ EulerBasis :: U ,
1163
+ EulerBasis :: PSX ,
1164
+ EulerBasis :: U1X ,
1165
+ EulerBasis :: RR ,
1166
+ EulerBasis :: ZYZ ,
1167
+ EulerBasis :: ZXZ ,
1168
+ EulerBasis :: XZX ,
1169
+ EulerBasis :: XYX ,
1170
+ EulerBasis :: ZSXX ,
1171
+ EulerBasis :: ZSX ,
1172
+ ] ;
1173
+
1034
1174
fn matmul_1q ( operator : & mut [ [ Complex64 ; 2 ] ; 2 ] , other : Array2 < Complex64 > ) {
1035
1175
* operator = [
1036
1176
[
@@ -1054,7 +1194,6 @@ pub fn euler_one_qubit_decomposer(m: &Bound<PyModule>) -> PyResult<()> {
1054
1194
m. add_wrapped ( wrap_pyfunction ! ( generate_circuit) ) ?;
1055
1195
m. add_wrapped ( wrap_pyfunction ! ( unitary_to_gate_sequence) ) ?;
1056
1196
m. add_wrapped ( wrap_pyfunction ! ( unitary_to_circuit) ) ?;
1057
- m. add_wrapped ( wrap_pyfunction ! ( compute_error_one_qubit_sequence) ) ?;
1058
1197
m. add_wrapped ( wrap_pyfunction ! ( compute_error_list) ) ?;
1059
1198
m. add_wrapped ( wrap_pyfunction ! ( optimize_1q_gates_decomposition) ) ?;
1060
1199
m. add_class :: < OneQubitGateSequence > ( ) ?;
0 commit comments