tequila-basic 1.9.4__py3-none-any.whl → 1.9.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tequila/circuit/gates.py +1 -1
- tequila/circuit/qasm.py +25 -11
- tequila/circuit/qpic.py +1 -1
- tequila/grouping/binary_rep.py +1 -1
- tequila/quantumchemistry/__init__.py +2 -0
- tequila/quantumchemistry/chemistry_tools.py +146 -19
- tequila/quantumchemistry/encodings.py +141 -47
- tequila/quantumchemistry/madness_interface.py +4 -1
- tequila/quantumchemistry/orbital_optimizer.py +10 -4
- tequila/quantumchemistry/pyscf_interface.py +1 -0
- tequila/quantumchemistry/qc_base.py +325 -24
- tequila/simulators/simulator_api.py +6 -1
- tequila/simulators/simulator_qiskit.py +14 -6
- tequila/tools/random_generators.py +17 -0
- tequila/version.py +1 -1
- {tequila_basic-1.9.4.dist-info → tequila_basic-1.9.6.dist-info}/METADATA +12 -10
- {tequila_basic-1.9.4.dist-info → tequila_basic-1.9.6.dist-info}/RECORD +20 -20
- {tequila_basic-1.9.4.dist-info → tequila_basic-1.9.6.dist-info}/WHEEL +1 -1
- {tequila_basic-1.9.4.dist-info → tequila_basic-1.9.6.dist-info}/LICENSE +0 -0
- {tequila_basic-1.9.4.dist-info → tequila_basic-1.9.6.dist-info}/top_level.txt +0 -0
@@ -420,7 +420,10 @@ class QuantumChemistryMadness(QuantumChemistryBase):
|
|
420
420
|
|
421
421
|
"""
|
422
422
|
# check if the used qubit encoding has a hcb transformation
|
423
|
-
|
423
|
+
try:
|
424
|
+
have_hcb_trafo = self.transformation.hcb_to_me() is not None
|
425
|
+
except:
|
426
|
+
have_hcb_trafo = False
|
424
427
|
name = name.upper()
|
425
428
|
|
426
429
|
# Default Method
|
@@ -37,7 +37,7 @@ class OptimizeOrbitalsResult:
|
|
37
37
|
self.iterations += 1
|
38
38
|
|
39
39
|
def optimize_orbitals(molecule, circuit=None, vqe_solver=None, pyscf_arguments=None, silent=False,
|
40
|
-
vqe_solver_arguments=None, initial_guess=None, return_mcscf=False, use_hcb=False, molecule_factory=None, molecule_arguments=None, *args, **kwargs):
|
40
|
+
vqe_solver_arguments=None, initial_guess=None, return_mcscf=False, use_hcb=False, molecule_factory=None, molecule_arguments=None, restrict_to_active_space=True, *args, **kwargs):
|
41
41
|
"""
|
42
42
|
|
43
43
|
Parameters
|
@@ -78,7 +78,12 @@ def optimize_orbitals(molecule, circuit=None, vqe_solver=None, pyscf_arguments=N
|
|
78
78
|
if pyscf_arguments is None:
|
79
79
|
pyscf_arguments = {"max_cycle_macro": 10, "max_cycle_micro": 3}
|
80
80
|
no = molecule.n_orbitals
|
81
|
-
|
81
|
+
|
82
|
+
if not isinstance(molecule, QuantumChemistryPySCF):
|
83
|
+
pyscf_molecule = QuantumChemistryPySCF.from_tequila(molecule=molecule, transformation=molecule.transformation)
|
84
|
+
else:
|
85
|
+
pyscf_molecule = molecule
|
86
|
+
|
82
87
|
mf = pyscf_molecule._get_hf()
|
83
88
|
result=OptimizeOrbitalsResult()
|
84
89
|
mc = mcscf.CASSCF(mf, pyscf_molecule.n_orbitals, pyscf_molecule.n_electrons)
|
@@ -140,10 +145,11 @@ def optimize_orbitals(molecule, circuit=None, vqe_solver=None, pyscf_arguments=N
|
|
140
145
|
mc.kernel()
|
141
146
|
# make new molecule
|
142
147
|
|
143
|
-
|
148
|
+
mo_coeff = mc.mo_coeff
|
149
|
+
transformed_molecule = pyscf_molecule.transform_orbitals(orbital_coefficients=mo_coeff, name="optimized")
|
144
150
|
result.molecule=transformed_molecule
|
145
151
|
result.old_molecule=molecule
|
146
|
-
result.mo_coeff=
|
152
|
+
result.mo_coeff=mo_coeff
|
147
153
|
result.energy=mc.e_tot
|
148
154
|
|
149
155
|
if return_mcscf:
|
@@ -75,6 +75,7 @@ class QuantumChemistryPySCF(QuantumChemistryBase):
|
|
75
75
|
kwargs["two_body_integrals"] = g_ao
|
76
76
|
kwargs["one_body_integrals"] = h_ao
|
77
77
|
kwargs["orbital_coefficients"] = mo_coeff
|
78
|
+
kwargs["orbital_type"] = "hf"
|
78
79
|
|
79
80
|
if "nuclear_repulsion" not in kwargs:
|
80
81
|
kwargs["nuclear_repulsion"] = mol.energy_nuc()
|
@@ -17,7 +17,7 @@ from .encodings import known_encodings
|
|
17
17
|
|
18
18
|
import typing, numpy, numbers
|
19
19
|
from itertools import product
|
20
|
-
|
20
|
+
import tequila.grouping.fermionic_functions as ff
|
21
21
|
|
22
22
|
|
23
23
|
try:
|
@@ -32,8 +32,7 @@ except:
|
|
32
32
|
except Exception as E:
|
33
33
|
raise Exception("{}\nIssue with Tequila Chemistry: Please update openfermion".format(str(E)))
|
34
34
|
import warnings
|
35
|
-
|
36
|
-
|
35
|
+
OPTIMIZED_ORDERING = "Optimized"
|
37
36
|
class QuantumChemistryBase:
|
38
37
|
"""
|
39
38
|
Base Class for tequila chemistry functionality
|
@@ -94,7 +93,7 @@ class QuantumChemistryBase:
|
|
94
93
|
else:
|
95
94
|
self.integral_manager = self.initialize_integral_manager(active_orbitals=active_orbitals,
|
96
95
|
reference_orbitals=reference_orbitals,
|
97
|
-
orbitals=orbitals, frozen_orbitals=frozen_orbitals, orbital_type=orbital_type, *args,
|
96
|
+
orbitals=orbitals, frozen_orbitals=frozen_orbitals, orbital_type=orbital_type, basis_name=self.parameters.basis_set, *args,
|
98
97
|
**kwargs)
|
99
98
|
|
100
99
|
if orbital_type is not None and orbital_type.lower() == "native":
|
@@ -109,15 +108,28 @@ class QuantumChemistryBase:
|
|
109
108
|
|
110
109
|
@classmethod
|
111
110
|
def from_tequila(cls, molecule, transformation=None, *args, **kwargs):
|
112
|
-
c
|
111
|
+
c = molecule.integral_manager.constant_term
|
112
|
+
h1 = molecule.integral_manager.one_body_integrals
|
113
|
+
h2 = molecule.integral_manager.two_body_integrals
|
114
|
+
S = molecule.integral_manager.overlap_integrals
|
115
|
+
if "active_orbitals" not in kwargs:
|
116
|
+
active_orbitals = [o.idx_total for o in molecule.integral_manager.active_orbitals]
|
117
|
+
else:
|
118
|
+
active_orbitals = kwargs["active_orbitals"]
|
119
|
+
kwargs.pop("active_orbitals")
|
113
120
|
if transformation is None:
|
114
121
|
transformation = molecule.transformation
|
122
|
+
parameters = molecule.parameters
|
115
123
|
return cls(nuclear_repulsion=c,
|
116
124
|
one_body_integrals=h1,
|
117
125
|
two_body_integrals=h2,
|
118
|
-
|
126
|
+
overlap_integrals = S,
|
127
|
+
orbital_coefficients = molecule.integral_manager.orbital_coefficients,
|
128
|
+
active_orbitals= active_orbitals,
|
119
129
|
transformation=transformation,
|
120
|
-
|
130
|
+
orbital_type=molecule.integral_manager._orbital_type,
|
131
|
+
parameters=parameters,
|
132
|
+
reference_orbitals= molecule.integral_manager.active_space.reference_orbitals,*args, **kwargs)
|
121
133
|
|
122
134
|
def supports_ucc(self):
|
123
135
|
"""
|
@@ -433,10 +445,14 @@ class QuantumChemistryBase:
|
|
433
445
|
|
434
446
|
generator = self.make_excitation_generator(indices=indices, remove_constant_term=control is None)
|
435
447
|
p0 = self.make_excitation_generator(indices=indices, form="P0", remove_constant_term=control is None)
|
436
|
-
|
448
|
+
if self.transformation.up_then_down:
|
449
|
+
idx = []
|
450
|
+
for pair in indices:
|
451
|
+
idx.append((pair[0]//2+(pair[0]%2)*self.n_orbitals,pair[1]//2+(pair[1]%2)*self.n_orbitals))
|
452
|
+
else:idx = indices
|
437
453
|
return QCircuit.wrap_gate(
|
438
454
|
FermionicGateImpl(angle=angle, generator=generator, p0=p0,
|
439
|
-
transformation=type(self.transformation).__name__.lower(), indices=
|
455
|
+
transformation=type(self.transformation).__name__.lower(), indices=idx,
|
440
456
|
assume_real=assume_real,
|
441
457
|
control=control, **kwargs))
|
442
458
|
|
@@ -543,11 +559,13 @@ class QuantumChemistryBase:
|
|
543
559
|
|
544
560
|
return manager
|
545
561
|
|
546
|
-
def transform_orbitals(self, orbital_coefficients, *args, **kwargs):
|
562
|
+
def transform_orbitals(self, orbital_coefficients, ignore_active_space=False, name=None, *args, **kwargs):
|
547
563
|
"""
|
548
564
|
Parameters
|
549
565
|
----------
|
550
|
-
orbital_coefficients: second index is new orbital indes, first is old orbital index (summed over)
|
566
|
+
orbital_coefficients: second index is new orbital indes, first is old orbital index (summed over), indices are assumed to be defined on the active space
|
567
|
+
ignore_active_space: if true orbital_coefficients are not assumed to be given in the active space
|
568
|
+
name: str, name the new orbitals
|
551
569
|
args
|
552
570
|
kwargs
|
553
571
|
|
@@ -556,9 +574,20 @@ class QuantumChemistryBase:
|
|
556
574
|
New molecule with transformed orbitals
|
557
575
|
"""
|
558
576
|
|
577
|
+
U = numpy.eye(self.integral_manager.orbital_coefficients.shape[0])
|
578
|
+
# mo_coeff by default only acts on the active space
|
579
|
+
active_indices = [o.idx_total for o in self.integral_manager.active_orbitals]
|
580
|
+
|
581
|
+
if ignore_active_space:
|
582
|
+
U = orbital_coefficients
|
583
|
+
else:
|
584
|
+
for kk,k in enumerate(active_indices):
|
585
|
+
for ll,l in enumerate(active_indices):
|
586
|
+
U[k][l] = orbital_coefficients[kk][ll]
|
587
|
+
|
559
588
|
# can not be an instance of a specific backend (otherwise we get inconsistencies with classical methods in the backend)
|
560
589
|
integral_manager = copy.deepcopy(self.integral_manager)
|
561
|
-
integral_manager.transform_orbitals(U=
|
590
|
+
integral_manager.transform_orbitals(U=U, name=name)
|
562
591
|
result = QuantumChemistryBase(parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation)
|
563
592
|
return result
|
564
593
|
|
@@ -583,7 +612,7 @@ class QuantumChemistryBase:
|
|
583
612
|
else:
|
584
613
|
integral_manager = copy.deepcopy(self.integral_manager)
|
585
614
|
integral_manager.transform_to_native_orbitals()
|
586
|
-
result = QuantumChemistryBase(parameters=self.parameters, integral_manager=integral_manager,
|
615
|
+
result = QuantumChemistryBase(parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation)
|
587
616
|
return result
|
588
617
|
|
589
618
|
|
@@ -645,6 +674,68 @@ class QuantumChemistryBase:
|
|
645
674
|
"""
|
646
675
|
return 2 * len(self.integral_manager.active_reference_orbitals)
|
647
676
|
|
677
|
+
def make_annihilation_op(self, orbital, coefficient=1.0):
|
678
|
+
"""
|
679
|
+
Compute annihilation operator on spin-orbital in qubit representation
|
680
|
+
Spin-orbital order is always (up,down,up,down,...)
|
681
|
+
"""
|
682
|
+
assert orbital<=self.n_orbitals*2
|
683
|
+
aop = openfermion.ops.FermionOperator(f'{orbital}', coefficient)
|
684
|
+
return self.transformation(aop)
|
685
|
+
|
686
|
+
def make_creation_op(self, orbital, coefficient=1.0):
|
687
|
+
"""
|
688
|
+
Compute creation operator on spin-orbital in qubit representation
|
689
|
+
Spin-orbital order is always (up,down,up,down,...)
|
690
|
+
"""
|
691
|
+
assert orbital<=self.n_orbitals*2
|
692
|
+
cop = openfermion.ops.FermionOperator(f'{orbital}^', coefficient)
|
693
|
+
return self.transformation(cop)
|
694
|
+
|
695
|
+
def make_number_op(self, orbital):
|
696
|
+
"""
|
697
|
+
Compute number operator on spin-orbital in qubit representation
|
698
|
+
Spin-orbital order is always (up,down,up,down,...)
|
699
|
+
"""
|
700
|
+
num_op = self.make_creation_op(orbital) * self.make_annihilation_op(orbital)
|
701
|
+
return num_op
|
702
|
+
|
703
|
+
def make_sz_op(self):
|
704
|
+
"""
|
705
|
+
Compute the spin_z operator of the molecule in qubit representation
|
706
|
+
"""
|
707
|
+
sz = QubitHamiltonian()
|
708
|
+
for i in range(0, self.n_orbitals * 2, 2):
|
709
|
+
one = 0.5 * self.make_creation_op(i) * self.make_annihilation_op(i)
|
710
|
+
two = 0.5 * self.make_creation_op(i+1) * self.make_annihilation_op(i+1)
|
711
|
+
sz += (one - two)
|
712
|
+
return sz
|
713
|
+
|
714
|
+
def make_sp_op(self):
|
715
|
+
"""
|
716
|
+
Compute the spin+ operator of the molecule in qubit representation
|
717
|
+
"""
|
718
|
+
sp = QubitHamiltonian()
|
719
|
+
for i in range(self.n_orbitals):
|
720
|
+
sp += self.make_creation_op(i*2) * self.make_annihilation_op(i*2 + 1)
|
721
|
+
return sp
|
722
|
+
|
723
|
+
def make_sm_op(self):
|
724
|
+
"""
|
725
|
+
Compute the spin- operator of the molecule in qubit representation
|
726
|
+
"""
|
727
|
+
sm = QubitHamiltonian()
|
728
|
+
for i in range(self.n_orbitals):
|
729
|
+
sm += self.make_creation_op(i*2 + 1) * self.make_annihilation_op(i*2)
|
730
|
+
return sm
|
731
|
+
|
732
|
+
def make_s2_op(self):
|
733
|
+
"""
|
734
|
+
Compute the spin^2 operator of the molecule in qubit representation
|
735
|
+
"""
|
736
|
+
s2_op = self.make_sm_op() * self.make_sp_op() + self.make_sz_op() * (self.make_sz_op() + 1)
|
737
|
+
return s2_op
|
738
|
+
|
648
739
|
def make_hamiltonian(self, *args, **kwargs) -> QubitHamiltonian:
|
649
740
|
"""
|
650
741
|
Parameters
|
@@ -805,13 +896,13 @@ class QuantumChemistryBase:
|
|
805
896
|
"""
|
806
897
|
if U is None:
|
807
898
|
U = QCircuit()
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
899
|
+
else:
|
900
|
+
ups = [self.transformation.up(i.idx) for i in self.orbitals]
|
901
|
+
consistency = [x in ups for x in U.qubits]
|
902
|
+
if not all(consistency):
|
903
|
+
warnings.warn(
|
904
|
+
"hcb_to_me: given circuit is not defined on all first {} qubits. Is this a HCB circuit?".format(
|
905
|
+
self.n_orbitals))
|
815
906
|
|
816
907
|
# map to alpha qubits
|
817
908
|
if condensed:
|
@@ -1156,7 +1247,13 @@ class QuantumChemistryBase:
|
|
1156
1247
|
indices = self.make_upccgsd_indices(key=name)
|
1157
1248
|
|
1158
1249
|
# check if the used qubit encoding has a hcb transformation
|
1159
|
-
have_hcb_trafo =
|
1250
|
+
have_hcb_trafo = True
|
1251
|
+
try:
|
1252
|
+
if self.transformation.hcb_to_me() is None:
|
1253
|
+
have_hcb_trafo = False
|
1254
|
+
except:
|
1255
|
+
have_hcb_trafo = False
|
1256
|
+
|
1160
1257
|
|
1161
1258
|
# consistency checks for optimization
|
1162
1259
|
if have_hcb_trafo and hcb_optimization is None and include_reference:
|
@@ -1404,7 +1501,8 @@ class QuantumChemistryBase:
|
|
1404
1501
|
factor = 1.0 / trotter_steps
|
1405
1502
|
for step in range(trotter_steps):
|
1406
1503
|
for idx, angle in indices.items():
|
1407
|
-
|
1504
|
+
converted = [(idx[2 * i], idx[2 * i + 1]) for i in range(len(idx) // 2)]
|
1505
|
+
UCCSD += self.make_excitation_gate(indices=converted, angle=factor * angle)
|
1408
1506
|
if hasattr(initial_amplitudes,
|
1409
1507
|
"lower") and initial_amplitudes.lower() == "mp2" and parametrized and add_singles:
|
1410
1508
|
# mp2 has no singles, need to initialize them here (if not parametrized initializling as 0.0 makes no sense though)
|
@@ -1638,7 +1736,8 @@ class QuantumChemistryBase:
|
|
1638
1736
|
return None
|
1639
1737
|
|
1640
1738
|
def compute_rdms(self, U: QCircuit = None, variables: Variables = None, spin_free: bool = True,
|
1641
|
-
get_rdm1: bool = True, get_rdm2: bool = True, ordering="dirac", use_hcb: bool = False
|
1739
|
+
get_rdm1: bool = True, get_rdm2: bool = True, ordering="dirac", use_hcb: bool = False,
|
1740
|
+
rdm_trafo: QubitHamiltonian = None, decompose=None):
|
1642
1741
|
"""
|
1643
1742
|
Computes the one- and two-particle reduced density matrices (rdm1 and rdm2) given
|
1644
1743
|
a unitary U. This method uses the standard ordering in physics as denoted below.
|
@@ -1666,6 +1765,9 @@ class QuantumChemistryBase:
|
|
1666
1765
|
get_rdm1, get_rdm2 :
|
1667
1766
|
Set whether either one or both rdm1, rdm2 should be computed. If both are needed at some point,
|
1668
1767
|
it is recommended to compute them at once.
|
1768
|
+
rdm_trafo :
|
1769
|
+
The rdm operators can be transformed, e.g., a^dagger_i a_j -> U^dagger a^dagger_i a_j U,
|
1770
|
+
where U represents the transformation. The default is set to None, implying that U equas the identity.
|
1669
1771
|
|
1670
1772
|
Returns
|
1671
1773
|
-------
|
@@ -1910,8 +2012,18 @@ class QuantumChemistryBase:
|
|
1910
2012
|
# Transform operator lists to QubitHamiltonians
|
1911
2013
|
if (not use_hcb):
|
1912
2014
|
qops = [_get_qop_hermitian(op) for op in qops]
|
2015
|
+
|
1913
2016
|
# Compute expected values
|
1914
|
-
|
2017
|
+
if rdm_trafo is None:
|
2018
|
+
if decompose is not None:
|
2019
|
+
print("MANIPULATED")
|
2020
|
+
X = decompose(H=qops, U=U)
|
2021
|
+
evals = simulate(X, variables=variables)
|
2022
|
+
else:
|
2023
|
+
evals = simulate(ExpectationValue(H=qops, U=U, shape=[len(qops)]), variables=variables)
|
2024
|
+
else:
|
2025
|
+
qops = [rdm_trafo.dagger()*qops[i]*rdm_trafo for i in range(len(qops))]
|
2026
|
+
evals = simulate(ExpectationValue(H=qops, U=U, shape=[len(qops)]), variables=variables)
|
1915
2027
|
|
1916
2028
|
# Assemble density matrices
|
1917
2029
|
# If self._rdm1, self._rdm2 exist, reset them if they are of the other spin-type
|
@@ -2044,6 +2156,58 @@ class QuantumChemistryBase:
|
|
2044
2156
|
n_ri=n_ri, external_info=external_info, **kwargs)
|
2045
2157
|
return correction.compute()
|
2046
2158
|
|
2159
|
+
def n_rotation(self, i, phi):
|
2160
|
+
'''
|
2161
|
+
Creates a quantum circuit that applies a phase rotation based on phi to both components (up and down) of a given qubit.
|
2162
|
+
|
2163
|
+
Parameters:
|
2164
|
+
- i (int): The index of the qubit to which the rotation will be applied.
|
2165
|
+
- phi (float): The rotation angle. The actual rotation applied will be multiplied with -2 for both components.
|
2166
|
+
|
2167
|
+
Returns:
|
2168
|
+
- QCircuit: A quantum circuit object containing the sequence of rotations applied to the up and down components of the specified qubit.
|
2169
|
+
'''
|
2170
|
+
|
2171
|
+
# Generate number operators for the up and down components of the qubit.
|
2172
|
+
n_up = self.make_number_op(2*i)
|
2173
|
+
n_down = self.make_number_op(2*i+1)
|
2174
|
+
|
2175
|
+
# Start a new circuit and apply rotations to each component.
|
2176
|
+
circuit = gates.GeneralizedRotation(generator = n_up, angle=-2*phi)
|
2177
|
+
circuit += gates.GeneralizedRotation(generator = n_down, angle=-2*phi)
|
2178
|
+
return circuit
|
2179
|
+
|
2180
|
+
def get_givens_circuit(self, unitary, tol = 1e-12, ordering = OPTIMIZED_ORDERING):
|
2181
|
+
'''
|
2182
|
+
Constructs a quantum circuit from a given real unitary matrix using Givens rotations.
|
2183
|
+
|
2184
|
+
This method decomposes a unitary matrix into a series of Givens and Rz (phase) rotations,
|
2185
|
+
then constructs and returns a quantum circuit that implements this sequence of rotations.
|
2186
|
+
|
2187
|
+
Parameters:
|
2188
|
+
- unitary (numpy.array): A real unitary matrix representing the transformation to implement.
|
2189
|
+
- tol (float): A tolerance threshold below which matrix elements are considered zero.
|
2190
|
+
- ordering (list of tuples or 'Optimized'): Custom ordering of indices for Givens rotations or 'Optimized' to generate them automatically.
|
2191
|
+
|
2192
|
+
Returns:
|
2193
|
+
- QCircuit: A quantum circuit implementing the series of rotations decomposed from the unitary.
|
2194
|
+
'''
|
2195
|
+
# Decompose the unitary matrix into Givens and phase (Rz) rotations.
|
2196
|
+
theta_list, phi_list = get_givens_decomposition(unitary, tol, ordering)
|
2197
|
+
|
2198
|
+
# Initialize an empty quantum circuit.
|
2199
|
+
circuit = QCircuit()
|
2200
|
+
|
2201
|
+
# Add all Rz (phase) rotations to the circuit.
|
2202
|
+
for phi in phi_list:
|
2203
|
+
circuit += self.n_rotation(phi[1], phi[0])
|
2204
|
+
|
2205
|
+
# Add all Givens rotations to the circuit.
|
2206
|
+
for theta in reversed(theta_list):
|
2207
|
+
circuit += self.UR(theta[1], theta[2], theta[0]*2)
|
2208
|
+
|
2209
|
+
return circuit
|
2210
|
+
|
2047
2211
|
|
2048
2212
|
def print_basis_info(self):
|
2049
2213
|
return self.integral_manager.print_basis_info()
|
@@ -2064,3 +2228,140 @@ class QuantumChemistryBase:
|
|
2064
2228
|
result += "\nmore information with: self.print_basis_info()\n"
|
2065
2229
|
|
2066
2230
|
return result
|
2231
|
+
|
2232
|
+
def givens_matrix(n, p, q, theta):
|
2233
|
+
'''
|
2234
|
+
Construct a complex Givens rotation matrix of dimension n by theta between rows/columns p and q.
|
2235
|
+
'''
|
2236
|
+
'''
|
2237
|
+
Generates a Givens rotation matrix of size n x n to rotate by angle theta in the (p, q) plane. This matrix can be complex
|
2238
|
+
|
2239
|
+
Parameters:
|
2240
|
+
- n (int): The size of the Givens rotation matrix.
|
2241
|
+
- p (int): The first index for the rotation plane.
|
2242
|
+
- q (int): The second index for the rotation plane.
|
2243
|
+
- theta (float): The rotation angle.
|
2244
|
+
|
2245
|
+
Returns:
|
2246
|
+
- numpy.array: The Givens rotation matrix.
|
2247
|
+
'''
|
2248
|
+
matrix = numpy.eye(n) # Matrix to hold complex numbers
|
2249
|
+
cos_theta = numpy.cos(theta)
|
2250
|
+
sin_theta = numpy.sin(theta)
|
2251
|
+
|
2252
|
+
# Directly assign cosine and sine without complex phase adjustment
|
2253
|
+
matrix[p, p] = cos_theta
|
2254
|
+
matrix[q, q] = cos_theta
|
2255
|
+
matrix[p, q] = sin_theta
|
2256
|
+
matrix[q, p] = -sin_theta
|
2257
|
+
|
2258
|
+
return matrix
|
2259
|
+
|
2260
|
+
def get_givens_decomposition(unitary, tol = 1e-12, ordering = OPTIMIZED_ORDERING, return_diagonal = False):
|
2261
|
+
'''
|
2262
|
+
Decomposes a real unitary matrix into Givens rotations (theta) and Rz rotations (phi).
|
2263
|
+
|
2264
|
+
Parameters:
|
2265
|
+
- unitary (numpy.array): A real unitary matrix to decompose. It cannot be complex.
|
2266
|
+
- tol (float): Tolerance for considering matrix elements as zero. Elements with absolute value less than tol are treated as zero.
|
2267
|
+
- ordering (list of tuples or 'Optimized'): Custom ordering of indices for Givens rotations or 'Optimized' to generate them automatically.
|
2268
|
+
- return_diagonal (bool): If True, the function also returns the diagonal matrix as part of the output.
|
2269
|
+
|
2270
|
+
Returns:
|
2271
|
+
- list: A list of tuples, each representing a Givens rotation. Each tuple contains the rotation angle theta and indices (i,j) of the rotation.
|
2272
|
+
- list: A list of tuples, each representing an Rz rotation. Each tuple contains the rotation angle phi and the index (i) of the rotation.
|
2273
|
+
- numpy.array (optional): The diagonal matrix after applying all Givens rotations, returned if return_diagonal is True.
|
2274
|
+
'''
|
2275
|
+
U = unitary # no need to copy as we don't modify the original
|
2276
|
+
U[abs(U) < tol] = 0 # Zeroing out the small elements as per the tolerance level.
|
2277
|
+
n = U.shape[0]
|
2278
|
+
|
2279
|
+
# Determine optimized ordering if specified.
|
2280
|
+
if ordering == OPTIMIZED_ORDERING:
|
2281
|
+
ordering = ff.depth_eff_order_mf(n)
|
2282
|
+
|
2283
|
+
theta_list = []
|
2284
|
+
phi_list = []
|
2285
|
+
|
2286
|
+
def calcTheta(U, c, r):
|
2287
|
+
'''Calculate and apply the Givens rotation for a specific matrix element.'''
|
2288
|
+
t = numpy.arctan2(-U[r,c], U[r-1,c])
|
2289
|
+
theta_list.append((t, r, r-1))
|
2290
|
+
g = givens_matrix(n,r,r-1,t)
|
2291
|
+
U = numpy.dot(g, U)
|
2292
|
+
|
2293
|
+
return U
|
2294
|
+
|
2295
|
+
# Apply and store Givens rotations as per the given or computed ordering.
|
2296
|
+
if ordering is None:
|
2297
|
+
for c in range(n):
|
2298
|
+
for r in range(n-1, c, -1):
|
2299
|
+
U = calcTheta(U, c, r)
|
2300
|
+
else:
|
2301
|
+
for r, c in ordering:
|
2302
|
+
U = calcTheta(U, c, r)
|
2303
|
+
|
2304
|
+
# Calculating the Rz rotations based on the phases of the diagonal elements.
|
2305
|
+
# For real elements this means a 180 degree shift, i.e. a sign change.
|
2306
|
+
for i in range(n):
|
2307
|
+
ph = numpy.angle(U[i,i])
|
2308
|
+
phi_list.append((ph, i))
|
2309
|
+
|
2310
|
+
# Filtering out rotations without significance.
|
2311
|
+
theta_list_new = []
|
2312
|
+
for i, theta in enumerate(theta_list):
|
2313
|
+
if abs(theta[0] % (2*numpy.pi)) > tol:
|
2314
|
+
theta_list_new.append(theta)
|
2315
|
+
|
2316
|
+
phi_list_new = []
|
2317
|
+
for i, phi in enumerate(phi_list):
|
2318
|
+
if abs(phi[0]) > tol:
|
2319
|
+
phi_list_new.append(phi)
|
2320
|
+
|
2321
|
+
if return_diagonal:
|
2322
|
+
# Optionally return the resulting diagonal
|
2323
|
+
return theta_list_new, phi_list_new, U
|
2324
|
+
else:
|
2325
|
+
return theta_list_new, phi_list_new
|
2326
|
+
|
2327
|
+
def reconstruct_matrix_from_givens(n, theta_list, phi_list, to_real_if_possible = True, tol = 1e-12):
|
2328
|
+
'''
|
2329
|
+
Reconstructs a matrix from given Givens rotations and Rz diagonal rotations.
|
2330
|
+
This function is effectively an inverse of get_givens_decomposition, and therefore only works with data in the same format as its output.
|
2331
|
+
|
2332
|
+
Parameters:
|
2333
|
+
- n (int): The size of the unitary matrix to be reconstructed.
|
2334
|
+
- theta_list (list of tuples): Each tuple contains (angle, i, j) representing a Givens rotation of `angle` radians, applied to rows/columns `i` and `j`.
|
2335
|
+
- phi_list (list of tuples): Each tuple contains (angle, i), representing an Rz rotation by `angle` radians applied to the `i`th diagonal element.
|
2336
|
+
- to_real_if_possible (bool): If True, converts the matrix to real if its imaginary part is effectively zero.
|
2337
|
+
- tol (float): The tolerance whether to swap a complex rotation for a sign change.
|
2338
|
+
|
2339
|
+
Returns:
|
2340
|
+
- numpy.ndarray: The reconstructed complex or real matrix, depending on the `to_real_if_possible` flag and matrix composition.
|
2341
|
+
'''
|
2342
|
+
# Start with an identity matrix
|
2343
|
+
reconstructed = numpy.eye(n, dtype=complex)
|
2344
|
+
|
2345
|
+
# Apply Rz rotations for diagonal elements
|
2346
|
+
for phi in phi_list:
|
2347
|
+
angle, i = phi
|
2348
|
+
# Directly apply a sign flip if the rotation angle is π
|
2349
|
+
if numpy.isclose(angle, numpy.pi, atol=tol):
|
2350
|
+
reconstructed[i, i] *= -1
|
2351
|
+
else:
|
2352
|
+
reconstructed[i, i] *= numpy.exp(1j * angle)
|
2353
|
+
|
2354
|
+
# Apply Givens rotations in reverse order
|
2355
|
+
for theta in reversed(theta_list):
|
2356
|
+
angle, i, j = theta
|
2357
|
+
g = givens_matrix(n, i, j, angle)
|
2358
|
+
reconstructed = numpy.dot(g.conj().T, reconstructed) # Transpose of Givens matrix applied to the left
|
2359
|
+
|
2360
|
+
# Convert matrix to real if its imaginary part is negligible unless disabled via to_real_if_possible
|
2361
|
+
if to_real_if_possible:
|
2362
|
+
# Directly apply a sign flip if the rotation angle is π
|
2363
|
+
if numpy.all(reconstructed.imag == 0):
|
2364
|
+
# Convert to real by taking the real part
|
2365
|
+
reconstructed = reconstructed.real
|
2366
|
+
|
2367
|
+
return reconstructed
|
@@ -36,9 +36,12 @@ try:
|
|
36
36
|
HAS_QISKIT = True
|
37
37
|
INSTALLED_SIMULATORS["qiskit"] = BackendTypes(BackendCircuitQiskit, BackendExpectationValueQiskit)
|
38
38
|
INSTALLED_SAMPLERS["qiskit"] = BackendTypes(BackendCircuitQiskit, BackendExpectationValueQiskit)
|
39
|
-
|
39
|
+
from tequila.simulators.simulator_qiskit import HAS_NOISE as HAS_QISKIT_NOISE
|
40
|
+
if HAS_QISKIT_NOISE:
|
41
|
+
INSTALLED_NOISE_SAMPLERS["qiskit"] = BackendTypes(BackendCircuitQiskit, BackendExpectationValueQiskit)
|
40
42
|
except ImportError:
|
41
43
|
HAS_QISKIT = False
|
44
|
+
HAS_QISKIT_NOISE = False
|
42
45
|
|
43
46
|
HAS_QIBO = True
|
44
47
|
try:
|
@@ -133,6 +136,8 @@ def show_available_simulators():
|
|
133
136
|
str(k in INSTALLED_SAMPLERS),
|
134
137
|
str(k in INSTALLED_NOISE_SAMPLERS),
|
135
138
|
str(k in INSTALLED_BACKENDS)))
|
139
|
+
if HAS_QISKIT and not HAS_QISKIT_NOISE:
|
140
|
+
print("missing qiskit_aer: no noisy simulation")
|
136
141
|
|
137
142
|
|
138
143
|
def pick_backend(backend: str = None, samples: int = None, noise: NoiseModel = None, device=None,
|
@@ -3,12 +3,20 @@ from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction
|
|
3
3
|
from tequila import TequilaException, TequilaWarning
|
4
4
|
from tequila import BitString, BitNumbering, BitStringLSB
|
5
5
|
from tequila.utils.keymap import KeyMapRegisterToSubregister
|
6
|
-
import qiskit, numpy, warnings
|
7
|
-
import qiskit.providers.aer.noise as qiskitnoise
|
8
6
|
from tequila.utils import to_float
|
9
|
-
import qiskit
|
10
|
-
from qiskit.providers.ibmq import IBMQBackend
|
7
|
+
import qiskit, numpy, warnings
|
11
8
|
|
9
|
+
HAS_NOISE=True
|
10
|
+
try:
|
11
|
+
from qiskit_aer import noise as qiskitnoise
|
12
|
+
except:
|
13
|
+
HAS_NOISE = False
|
14
|
+
|
15
|
+
HAS_IBMQ=True
|
16
|
+
try:
|
17
|
+
from qiskit.providers.ibmq import IBMQBackend
|
18
|
+
except:
|
19
|
+
HAS_IBMQ=False
|
12
20
|
|
13
21
|
def get_bit_flip(p):
|
14
22
|
"""
|
@@ -137,7 +145,7 @@ class BackendCircuitQiskit(BackendCircuit):
|
|
137
145
|
}
|
138
146
|
|
139
147
|
numbering = BitNumbering.LSB
|
140
|
-
|
148
|
+
|
141
149
|
def __init__(self, abstract_circuit: QCircuit, variables, qubit_map=None, noise=None,
|
142
150
|
device=None, *args, **kwargs):
|
143
151
|
"""
|
@@ -169,7 +177,7 @@ class BackendCircuitQiskit(BackendCircuit):
|
|
169
177
|
'Rx': (lambda c: c.rx, lambda c: c.mcrx),
|
170
178
|
'Ry': (lambda c: c.ry, lambda c: c.mcry),
|
171
179
|
'Rz': (lambda c: c.rz, lambda c: c.mcrz),
|
172
|
-
'Phase': (lambda c: c.
|
180
|
+
'Phase': (lambda c: c.p, lambda c: c.cp),
|
173
181
|
'SWAP': (lambda c: c.swap, lambda c: c.cswap),
|
174
182
|
}
|
175
183
|
|
@@ -2,6 +2,7 @@ import numpy as np
|
|
2
2
|
from tequila.circuit import gates
|
3
3
|
from tequila.circuit.circuit import QCircuit
|
4
4
|
from tequila.hamiltonian.qubit_hamiltonian import QubitHamiltonian
|
5
|
+
from scipy.stats import unitary_group, ortho_group
|
5
6
|
|
6
7
|
def make_random_circuit(n_qubits: int, rotation_gates: list=['rx', 'ry', 'rz'], n_rotations: int=None,
|
7
8
|
enable_controls: bool=None) -> QCircuit:
|
@@ -75,3 +76,19 @@ def make_random_hamiltonian(n_qubits: int , paulis: list=['X','Y','Z'], n_ps: in
|
|
75
76
|
|
76
77
|
H = QubitHamiltonian(ham)
|
77
78
|
return H
|
79
|
+
|
80
|
+
def generate_random_unitary(size, complex = False):
|
81
|
+
'''
|
82
|
+
Generates a random unitary (or furthermore orthogonal if complex is False) matrix of a specified size.
|
83
|
+
|
84
|
+
Parameters:
|
85
|
+
- size (int): The size of the unitary matrix to be generated.
|
86
|
+
- complex (bool, optional): Whether the unitary should be complex.
|
87
|
+
|
88
|
+
Returns:
|
89
|
+
- numpy.ndarray: A randomly generated unitary matrix.
|
90
|
+
'''
|
91
|
+
if complex:
|
92
|
+
return unitary_group.rvs(size)
|
93
|
+
else:
|
94
|
+
return ortho_group.rvs(size)
|
tequila/version.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
__version__ = "1.9.
|
1
|
+
__version__ = "1.9.6"
|
2
2
|
__author__ = "Tequila Developers "
|