tequila-basic 1.9.5__py3-none-any.whl → 1.9.7__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.
@@ -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
@@ -113,7 +112,11 @@ class QuantumChemistryBase:
113
112
  h1 = molecule.integral_manager.one_body_integrals
114
113
  h2 = molecule.integral_manager.two_body_integrals
115
114
  S = molecule.integral_manager.overlap_integrals
116
- active_orbitals = [o.idx_total for o in molecule.integral_manager.active_orbitals]
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")
117
120
  if transformation is None:
118
121
  transformation = molecule.transformation
119
122
  parameters = molecule.parameters
@@ -125,7 +128,8 @@ class QuantumChemistryBase:
125
128
  active_orbitals= active_orbitals,
126
129
  transformation=transformation,
127
130
  orbital_type=molecule.integral_manager._orbital_type,
128
- parameters=parameters, *args, **kwargs)
131
+ parameters=parameters,
132
+ reference_orbitals= molecule.integral_manager.active_space.reference_orbitals,*args, **kwargs)
129
133
 
130
134
  def supports_ucc(self):
131
135
  """
@@ -441,10 +445,14 @@ class QuantumChemistryBase:
441
445
 
442
446
  generator = self.make_excitation_generator(indices=indices, remove_constant_term=control is None)
443
447
  p0 = self.make_excitation_generator(indices=indices, form="P0", remove_constant_term=control is None)
444
-
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
445
453
  return QCircuit.wrap_gate(
446
454
  FermionicGateImpl(angle=angle, generator=generator, p0=p0,
447
- transformation=type(self.transformation).__name__.lower(), indices=indices,
455
+ transformation=type(self.transformation).__name__.lower(), indices=idx,
448
456
  assume_real=assume_real,
449
457
  control=control, **kwargs))
450
458
 
@@ -1239,7 +1247,13 @@ class QuantumChemistryBase:
1239
1247
  indices = self.make_upccgsd_indices(key=name)
1240
1248
 
1241
1249
  # check if the used qubit encoding has a hcb transformation
1242
- have_hcb_trafo = self.transformation.hcb_to_me() is not None
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
+
1243
1257
 
1244
1258
  # consistency checks for optimization
1245
1259
  if have_hcb_trafo and hcb_optimization is None and include_reference:
@@ -1487,7 +1501,8 @@ class QuantumChemistryBase:
1487
1501
  factor = 1.0 / trotter_steps
1488
1502
  for step in range(trotter_steps):
1489
1503
  for idx, angle in indices.items():
1490
- UCCSD += self.make_excitation_gate(indices=idx, angle=factor * angle)
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)
1491
1506
  if hasattr(initial_amplitudes,
1492
1507
  "lower") and initial_amplitudes.lower() == "mp2" and parametrized and add_singles:
1493
1508
  # mp2 has no singles, need to initialize them here (if not parametrized initializling as 0.0 makes no sense though)
@@ -1721,7 +1736,8 @@ class QuantumChemistryBase:
1721
1736
  return None
1722
1737
 
1723
1738
  def compute_rdms(self, U: QCircuit = None, variables: Variables = None, spin_free: bool = True,
1724
- 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, evaluate=True):
1725
1741
  """
1726
1742
  Computes the one- and two-particle reduced density matrices (rdm1 and rdm2) given
1727
1743
  a unitary U. This method uses the standard ordering in physics as denoted below.
@@ -1749,7 +1765,13 @@ class QuantumChemistryBase:
1749
1765
  get_rdm1, get_rdm2 :
1750
1766
  Set whether either one or both rdm1, rdm2 should be computed. If both are needed at some point,
1751
1767
  it is recommended to compute them at once.
1752
-
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.
1771
+ evaluate :
1772
+ if true, the tequila expectation values are evaluated directly via the tq.simulate command.
1773
+ the protocol is optimized to avoid repetation of wavefunction simulation
1774
+ if false, the rdms are returned as tq.QTensors
1753
1775
  Returns
1754
1776
  -------
1755
1777
  """
@@ -1872,13 +1894,14 @@ class QuantumChemistryBase:
1872
1894
  ops += [op]
1873
1895
  return ops
1874
1896
 
1875
- def _assemble_rdm1(evals) -> numpy.ndarray:
1897
+ def _assemble_rdm1(evals, rdm1=None) -> numpy.ndarray:
1876
1898
  """
1877
1899
  Returns spin-ful or spin-free one-particle RDM built by symmetry conditions
1878
1900
  Same symmetry with or without spin, so we can use the same function
1879
1901
  """
1880
1902
  N = n_MOs if spin_free else n_SOs
1881
- rdm1 = numpy.zeros([N, N])
1903
+ if rdm1 is None:
1904
+ rdm1 = numpy.zeros([N, N])
1882
1905
  ctr: int = 0
1883
1906
  for p in range(N):
1884
1907
  for q in range(p + 1):
@@ -1889,10 +1912,11 @@ class QuantumChemistryBase:
1889
1912
 
1890
1913
  return rdm1
1891
1914
 
1892
- def _assemble_rdm2_spinful(evals) -> numpy.ndarray:
1915
+ def _assemble_rdm2_spinful(evals, rdm2=None) -> numpy.ndarray:
1893
1916
  """ Returns spin-ful two-particle RDM built by symmetry conditions """
1894
1917
  ctr: int = 0
1895
- rdm2 = numpy.zeros([n_SOs, n_SOs, n_SOs, n_SOs])
1918
+ if rdm2 is None:
1919
+ rdm2 = numpy.zeros([n_SOs, n_SOs, n_SOs, n_SOs])
1896
1920
  for p in range(n_SOs):
1897
1921
  for q in range(p):
1898
1922
  for r in range(n_SOs):
@@ -1914,10 +1938,11 @@ class QuantumChemistryBase:
1914
1938
 
1915
1939
  return rdm2
1916
1940
 
1917
- def _assemble_rdm2_spinfree(evals) -> numpy.ndarray:
1941
+ def _assemble_rdm2_spinfree(evals, rdm2=None) -> numpy.ndarray:
1918
1942
  """ Returns spin-free two-particle RDM built by symmetry conditions """
1919
1943
  ctr: int = 0
1920
- rdm2 = numpy.zeros([n_MOs, n_MOs, n_MOs, n_MOs])
1944
+ if rdm2 is None:
1945
+ rdm2 = numpy.zeros([n_MOs, n_MOs, n_MOs, n_MOs])
1921
1946
  for p, q, r, s in product(range(n_MOs), repeat=4):
1922
1947
  if p * n_MOs + q >= r * n_MOs + s and (p >= q or r >= s):
1923
1948
  rdm2[p, q, r, s] = evals[ctr]
@@ -1993,8 +2018,25 @@ class QuantumChemistryBase:
1993
2018
  # Transform operator lists to QubitHamiltonians
1994
2019
  if (not use_hcb):
1995
2020
  qops = [_get_qop_hermitian(op) for op in qops]
2021
+
1996
2022
  # Compute expected values
1997
- evals = simulate(ExpectationValue(H=qops, U=U, shape=[len(qops)]), variables=variables)
2023
+ rdm1 = None
2024
+ rdm2 = None
2025
+ from tequila import QTensor
2026
+ if evaluate:
2027
+ if rdm_trafo is None:
2028
+ evals = simulate(ExpectationValue(H=qops, U=U, shape=[len(qops)]), variables=variables)
2029
+ else:
2030
+ qops = [rdm_trafo.dagger()*qops[i]*rdm_trafo for i in range(len(qops))]
2031
+ evals = simulate(ExpectationValue(H=qops, U=U, shape=[len(qops)]), variables=variables)
2032
+ else:
2033
+ if rdm_trafo is None:
2034
+ evals = [ExpectationValue(H=x, U=U) for x in qops]
2035
+ N = n_MOs if spin_free else n_SOs
2036
+ rdm1 = QTensor(shape=[N,N])
2037
+ rdm2 = QTensor(shape=[N, N, N, N])
2038
+ else:
2039
+ raise TequilaException("compute_rdms: rdm_trafo was set but evaluate flag is False (not supported)")
1998
2040
 
1999
2041
  # Assemble density matrices
2000
2042
  # If self._rdm1, self._rdm2 exist, reset them if they are of the other spin-type
@@ -2015,11 +2057,11 @@ class QuantumChemistryBase:
2015
2057
  len_1 = 0
2016
2058
  evals_1, evals_2 = evals[:len_1], evals[len_1:]
2017
2059
  # Build matrices using the expectation values
2018
- self._rdm1 = _assemble_rdm1(evals_1) if get_rdm1 else self._rdm1
2060
+ self._rdm1 = _assemble_rdm1(evals_1, rdm1=rdm1) if get_rdm1 else self._rdm1
2019
2061
  if spin_free or use_hcb:
2020
- self._rdm2 = _assemble_rdm2_spinfree(evals_2) if get_rdm2 else self._rdm2
2062
+ self._rdm2 = _assemble_rdm2_spinfree(evals_2, rdm2=rdm2) if get_rdm2 else self._rdm2
2021
2063
  else:
2022
- self._rdm2 = _assemble_rdm2_spinful(evals_2) if get_rdm2 else self._rdm2
2064
+ self._rdm2 = _assemble_rdm2_spinful(evals_2, rdm2=rdm2) if get_rdm2 else self._rdm2
2023
2065
 
2024
2066
  if get_rdm2:
2025
2067
  rdm2 = NBodyTensor(elems=self.rdm2, ordering="dirac", verify=False)
@@ -2127,6 +2169,58 @@ class QuantumChemistryBase:
2127
2169
  n_ri=n_ri, external_info=external_info, **kwargs)
2128
2170
  return correction.compute()
2129
2171
 
2172
+ def n_rotation(self, i, phi):
2173
+ '''
2174
+ Creates a quantum circuit that applies a phase rotation based on phi to both components (up and down) of a given qubit.
2175
+
2176
+ Parameters:
2177
+ - i (int): The index of the qubit to which the rotation will be applied.
2178
+ - phi (float): The rotation angle. The actual rotation applied will be multiplied with -2 for both components.
2179
+
2180
+ Returns:
2181
+ - QCircuit: A quantum circuit object containing the sequence of rotations applied to the up and down components of the specified qubit.
2182
+ '''
2183
+
2184
+ # Generate number operators for the up and down components of the qubit.
2185
+ n_up = self.make_number_op(2*i)
2186
+ n_down = self.make_number_op(2*i+1)
2187
+
2188
+ # Start a new circuit and apply rotations to each component.
2189
+ circuit = gates.GeneralizedRotation(generator = n_up, angle=-2*phi)
2190
+ circuit += gates.GeneralizedRotation(generator = n_down, angle=-2*phi)
2191
+ return circuit
2192
+
2193
+ def get_givens_circuit(self, unitary, tol = 1e-12, ordering = OPTIMIZED_ORDERING):
2194
+ '''
2195
+ Constructs a quantum circuit from a given real unitary matrix using Givens rotations.
2196
+
2197
+ This method decomposes a unitary matrix into a series of Givens and Rz (phase) rotations,
2198
+ then constructs and returns a quantum circuit that implements this sequence of rotations.
2199
+
2200
+ Parameters:
2201
+ - unitary (numpy.array): A real unitary matrix representing the transformation to implement.
2202
+ - tol (float): A tolerance threshold below which matrix elements are considered zero.
2203
+ - ordering (list of tuples or 'Optimized'): Custom ordering of indices for Givens rotations or 'Optimized' to generate them automatically.
2204
+
2205
+ Returns:
2206
+ - QCircuit: A quantum circuit implementing the series of rotations decomposed from the unitary.
2207
+ '''
2208
+ # Decompose the unitary matrix into Givens and phase (Rz) rotations.
2209
+ theta_list, phi_list = get_givens_decomposition(unitary, tol, ordering)
2210
+
2211
+ # Initialize an empty quantum circuit.
2212
+ circuit = QCircuit()
2213
+
2214
+ # Add all Rz (phase) rotations to the circuit.
2215
+ for phi in phi_list:
2216
+ circuit += self.n_rotation(phi[1], phi[0])
2217
+
2218
+ # Add all Givens rotations to the circuit.
2219
+ for theta in reversed(theta_list):
2220
+ circuit += self.UR(theta[1], theta[2], theta[0]*2)
2221
+
2222
+ return circuit
2223
+
2130
2224
 
2131
2225
  def print_basis_info(self):
2132
2226
  return self.integral_manager.print_basis_info()
@@ -2147,3 +2241,140 @@ class QuantumChemistryBase:
2147
2241
  result += "\nmore information with: self.print_basis_info()\n"
2148
2242
 
2149
2243
  return result
2244
+
2245
+ def givens_matrix(n, p, q, theta):
2246
+ '''
2247
+ Construct a complex Givens rotation matrix of dimension n by theta between rows/columns p and q.
2248
+ '''
2249
+ '''
2250
+ 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
2251
+
2252
+ Parameters:
2253
+ - n (int): The size of the Givens rotation matrix.
2254
+ - p (int): The first index for the rotation plane.
2255
+ - q (int): The second index for the rotation plane.
2256
+ - theta (float): The rotation angle.
2257
+
2258
+ Returns:
2259
+ - numpy.array: The Givens rotation matrix.
2260
+ '''
2261
+ matrix = numpy.eye(n) # Matrix to hold complex numbers
2262
+ cos_theta = numpy.cos(theta)
2263
+ sin_theta = numpy.sin(theta)
2264
+
2265
+ # Directly assign cosine and sine without complex phase adjustment
2266
+ matrix[p, p] = cos_theta
2267
+ matrix[q, q] = cos_theta
2268
+ matrix[p, q] = sin_theta
2269
+ matrix[q, p] = -sin_theta
2270
+
2271
+ return matrix
2272
+
2273
+ def get_givens_decomposition(unitary, tol = 1e-12, ordering = OPTIMIZED_ORDERING, return_diagonal = False):
2274
+ '''
2275
+ Decomposes a real unitary matrix into Givens rotations (theta) and Rz rotations (phi).
2276
+
2277
+ Parameters:
2278
+ - unitary (numpy.array): A real unitary matrix to decompose. It cannot be complex.
2279
+ - tol (float): Tolerance for considering matrix elements as zero. Elements with absolute value less than tol are treated as zero.
2280
+ - ordering (list of tuples or 'Optimized'): Custom ordering of indices for Givens rotations or 'Optimized' to generate them automatically.
2281
+ - return_diagonal (bool): If True, the function also returns the diagonal matrix as part of the output.
2282
+
2283
+ Returns:
2284
+ - list: A list of tuples, each representing a Givens rotation. Each tuple contains the rotation angle theta and indices (i,j) of the rotation.
2285
+ - list: A list of tuples, each representing an Rz rotation. Each tuple contains the rotation angle phi and the index (i) of the rotation.
2286
+ - numpy.array (optional): The diagonal matrix after applying all Givens rotations, returned if return_diagonal is True.
2287
+ '''
2288
+ U = unitary # no need to copy as we don't modify the original
2289
+ U[abs(U) < tol] = 0 # Zeroing out the small elements as per the tolerance level.
2290
+ n = U.shape[0]
2291
+
2292
+ # Determine optimized ordering if specified.
2293
+ if ordering == OPTIMIZED_ORDERING:
2294
+ ordering = ff.depth_eff_order_mf(n)
2295
+
2296
+ theta_list = []
2297
+ phi_list = []
2298
+
2299
+ def calcTheta(U, c, r):
2300
+ '''Calculate and apply the Givens rotation for a specific matrix element.'''
2301
+ t = numpy.arctan2(-U[r,c], U[r-1,c])
2302
+ theta_list.append((t, r, r-1))
2303
+ g = givens_matrix(n,r,r-1,t)
2304
+ U = numpy.dot(g, U)
2305
+
2306
+ return U
2307
+
2308
+ # Apply and store Givens rotations as per the given or computed ordering.
2309
+ if ordering is None:
2310
+ for c in range(n):
2311
+ for r in range(n-1, c, -1):
2312
+ U = calcTheta(U, c, r)
2313
+ else:
2314
+ for r, c in ordering:
2315
+ U = calcTheta(U, c, r)
2316
+
2317
+ # Calculating the Rz rotations based on the phases of the diagonal elements.
2318
+ # For real elements this means a 180 degree shift, i.e. a sign change.
2319
+ for i in range(n):
2320
+ ph = numpy.angle(U[i,i])
2321
+ phi_list.append((ph, i))
2322
+
2323
+ # Filtering out rotations without significance.
2324
+ theta_list_new = []
2325
+ for i, theta in enumerate(theta_list):
2326
+ if abs(theta[0] % (2*numpy.pi)) > tol:
2327
+ theta_list_new.append(theta)
2328
+
2329
+ phi_list_new = []
2330
+ for i, phi in enumerate(phi_list):
2331
+ if abs(phi[0]) > tol:
2332
+ phi_list_new.append(phi)
2333
+
2334
+ if return_diagonal:
2335
+ # Optionally return the resulting diagonal
2336
+ return theta_list_new, phi_list_new, U
2337
+ else:
2338
+ return theta_list_new, phi_list_new
2339
+
2340
+ def reconstruct_matrix_from_givens(n, theta_list, phi_list, to_real_if_possible = True, tol = 1e-12):
2341
+ '''
2342
+ Reconstructs a matrix from given Givens rotations and Rz diagonal rotations.
2343
+ This function is effectively an inverse of get_givens_decomposition, and therefore only works with data in the same format as its output.
2344
+
2345
+ Parameters:
2346
+ - n (int): The size of the unitary matrix to be reconstructed.
2347
+ - 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`.
2348
+ - phi_list (list of tuples): Each tuple contains (angle, i), representing an Rz rotation by `angle` radians applied to the `i`th diagonal element.
2349
+ - to_real_if_possible (bool): If True, converts the matrix to real if its imaginary part is effectively zero.
2350
+ - tol (float): The tolerance whether to swap a complex rotation for a sign change.
2351
+
2352
+ Returns:
2353
+ - numpy.ndarray: The reconstructed complex or real matrix, depending on the `to_real_if_possible` flag and matrix composition.
2354
+ '''
2355
+ # Start with an identity matrix
2356
+ reconstructed = numpy.eye(n, dtype=complex)
2357
+
2358
+ # Apply Rz rotations for diagonal elements
2359
+ for phi in phi_list:
2360
+ angle, i = phi
2361
+ # Directly apply a sign flip if the rotation angle is π
2362
+ if numpy.isclose(angle, numpy.pi, atol=tol):
2363
+ reconstructed[i, i] *= -1
2364
+ else:
2365
+ reconstructed[i, i] *= numpy.exp(1j * angle)
2366
+
2367
+ # Apply Givens rotations in reverse order
2368
+ for theta in reversed(theta_list):
2369
+ angle, i, j = theta
2370
+ g = givens_matrix(n, i, j, angle)
2371
+ reconstructed = numpy.dot(g.conj().T, reconstructed) # Transpose of Givens matrix applied to the left
2372
+
2373
+ # Convert matrix to real if its imaginary part is negligible unless disabled via to_real_if_possible
2374
+ if to_real_if_possible:
2375
+ # Directly apply a sign flip if the rotation angle is π
2376
+ if numpy.all(reconstructed.imag == 0):
2377
+ # Convert to real by taking the real part
2378
+ reconstructed = reconstructed.real
2379
+
2380
+ 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
- INSTALLED_NOISE_SAMPLERS["qiskit"] = BackendTypes(BackendCircuitQiskit, BackendExpectationValueQiskit)
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,
@@ -356,15 +356,23 @@ class BackendCircuit():
356
356
  raise TequilaException("only product states as initial states accepted")
357
357
  initial_state = list(initial_state.keys())[0].integer
358
358
 
359
- all_qubits = [i for i in range(self.abstract_circuit.n_qubits)]
359
+ all_qubits = list(range(self.abstract_circuit.n_qubits))
360
360
  active_qubits = self.qubit_map.keys()
361
361
 
362
- # maps from reduced register to full register
363
- keymap = KeyMapSubregisterToRegister(subregister=active_qubits, register=all_qubits)
362
+ # Keymap is only necessary if not all qubits are active
363
+ keymap_required = sorted(active_qubits) != all_qubits
364
364
 
365
- result = self.do_simulate(variables=variables, initial_state=keymap.inverted(initial_state).integer, *args,
365
+ if keymap_required:
366
+ # maps from reduced register to full register
367
+ keymap = KeyMapSubregisterToRegister(subregister=active_qubits, register=all_qubits)
368
+
369
+ mapped_initial_state = keymap.inverted(initial_state).integer if keymap_required else int(initial_state)
370
+ result = self.do_simulate(variables=variables, initial_state=mapped_initial_state, *args,
366
371
  **kwargs)
367
- result.apply_keymap(keymap=keymap, initial_state=initial_state)
372
+
373
+ if keymap_required:
374
+ result.apply_keymap(keymap=keymap, initial_state=initial_state)
375
+
368
376
  return result
369
377
 
370
378
  def sample(self, variables, samples, read_out_qubits=None, circuit=None, *args, **kwargs):
@@ -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.test.mock.backends
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.u1, lambda c: c.cu1),
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)
@@ -1,6 +1,7 @@
1
1
  from enum import Enum
2
2
  from typing import List
3
3
  from functools import total_ordering
4
+ from math import ceil, log2
4
5
 
5
6
 
6
7
  class BitNumbering(Enum):
@@ -35,7 +36,7 @@ class BitString:
35
36
 
36
37
  def update_nbits(self):
37
38
  current = self.nbits
38
- min_needed = len(format(self._value, 'b'))
39
+ min_needed = ceil(log2(self._value + 1))
39
40
  self._nbits = max(current, min_needed)
40
41
  return self
41
42
 
@@ -177,15 +178,23 @@ class BitStringLSB(BitString):
177
178
  return BitNumbering.LSB
178
179
 
179
180
 
181
+ def _reverse_int_bits(x: int, nbits: int) -> int:
182
+ if nbits is None:
183
+ nbits = x.bit_length()
184
+ assert nbits <= 32
185
+
186
+ x = ((x & 0x55555555) << 1) | ((x & 0xAAAAAAAA) >> 1)
187
+ x = ((x & 0x33333333) << 2) | ((x & 0xCCCCCCCC) >> 2)
188
+ x = ((x & 0x0F0F0F0F) << 4) | ((x & 0xF0F0F0F0) >> 4)
189
+ x = ((x & 0x00FF00FF) << 8) | ((x & 0xFF00FF00) >> 8)
190
+ x = ((x & 0x0000FFFF) << 16) | ((x & 0xFFFF0000) >> 16)
191
+ return x >> (32 - nbits)
192
+
193
+
180
194
  def initialize_bitstring(integer: int, nbits: int = None, numbering_in: BitNumbering = BitNumbering.MSB,
181
195
  numbering_out: BitNumbering = BitNumbering.MSB):
182
- if numbering_in == BitNumbering.MSB:
183
- if numbering_out == BitNumbering.MSB:
184
- return BitString.from_int(integer=integer, nbits=nbits)
185
- else:
186
- return BitString.from_binary(binary=BitStringLSB.from_int(integer=integer, nbits=nbits).binary, nbits=nbits)
196
+ integer = _reverse_int_bits(integer, nbits) if numbering_in != numbering_out else integer
197
+ if numbering_out == BitNumbering.MSB:
198
+ return BitString.from_int(integer=integer, nbits=nbits)
187
199
  else:
188
- if numbering_out == BitNumbering.LSB:
189
- return BitStringLSB.from_int(integer=integer, nbits=nbits)
190
- else:
191
- return BitStringLSB.from_binary(binary=BitString.from_int(integer=integer, nbits=nbits).binary, nbits=nbits)
200
+ return BitStringLSB.from_int(integer=integer, nbits=nbits)
tequila/version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "1.9.5"
1
+ __version__ = "1.9.7"
2
2
  __author__ = "Tequila Developers "
@@ -145,18 +145,12 @@ class QubitWaveFunction:
145
145
  maxkey = len(arr) - 1
146
146
  maxbit = initialize_bitstring(integer=maxkey, numbering_in=numbering, numbering_out=cls.numbering).nbits
147
147
  for ii, v in enumerate(arr):
148
- i = initialize_bitstring(integer=ii, nbits=maxbit, numbering_in=numbering, numbering_out=cls.numbering)
149
- if not numpy.isclose(abs(v), 0.0, atol=threshold):
148
+ if abs(v) > threshold:
149
+ i = initialize_bitstring(integer=ii, nbits=maxbit, numbering_in=numbering, numbering_out=cls.numbering)
150
150
  key = i if keymap is None else keymap(i)
151
151
  state[key] = v
152
152
  result = QubitWaveFunction(state, n_qubits=n_qubits)
153
153
 
154
- if cls.numbering != numbering:
155
- if cls.numbering == BitNumbering.MSB:
156
- result.apply_keymap(keymap=KeyMapLSB2MSB())
157
- else:
158
- result.apply_keymap(keymap=KeyMapMSB2LSB())
159
-
160
154
  return result
161
155
 
162
156
  @classmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tequila-basic
3
- Version: 1.9.5
3
+ Version: 1.9.7
4
4
  Summary: A High-Level Abstraction Framework for Quantum Algorithms
5
5
  Home-page: https://github.com/tequilahub/tequila
6
6
  Author: Tequila Developers
@@ -8,13 +8,13 @@ Author-email: jakob.kottmann@gmail.com
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: numpy
11
- Requires-Dist: scipy <1.11
11
+ Requires-Dist: scipy
12
12
  Requires-Dist: sympy
13
13
  Requires-Dist: autograd
14
14
  Requires-Dist: setuptools
15
15
  Requires-Dist: pytest
16
- Requires-Dist: openfermion ~=1.0
17
- Requires-Dist: dataclasses ; python_version < "3.7"
16
+ Requires-Dist: openfermion~=1.0
17
+ Requires-Dist: dataclasses; python_version < "3.7"
18
18
 
19
19
  [![License: MIT](https://img.shields.io/badge/License-MIT-lightgrey.svg)](LICENCE) [![DOI](https://zenodo.org/badge/259718912.svg)](https://zenodo.org/badge/latestdoi/259718912) [![PyPI version](https://badge.fury.io/py/tequila-basic.svg)](https://badge.fury.io/py/tequila-basic) ![CI](https://github.com/tequilahub/tequila/actions/workflows/ci_basic.yml/badge.svg)
20
20
 
@@ -24,15 +24,18 @@ Tequila is an abstraction framework for (variational) quantum algorithms.
24
24
  It operates on abstract data structures allowing the formulation, combination, automatic differentiation and optimization of generalized objectives.
25
25
  Tequila can execute the underlying quantum expectation values on state of the art simulators as well as on real quantum devices.
26
26
 
27
+ # Getting Started
28
+
29
+ Get started with our collection of
30
+ - *[Tutorials](https://thuytr18.github.io/tequila-tutorials-beta/tutorials.html)*
31
+
32
+ Further sources:
27
33
  - [overview article](https://arxiv.org/abs/2011.03057)
28
34
  - [tequila in a nutshell](https://kottmanj.github.io/tequila-in-a-nutshell/#/)
29
- - [getting started](https://jakobkottmann.com/posts/tq-get-started/)
30
- - [circuits in tequila](https://jakobkottmann.com/posts/tq-circuits/)
31
- - [notebook collection](https://github.com/tequilahub/tequila-tutorials)
32
35
  - [talks and slides](https://kottmanj.github.io/talks_and_material/)
33
36
 
34
37
  # Installation
35
- Recommended Python version is 3.9 - 3.10.
38
+ Recommended Python version is 3.9 (3.10).
36
39
  Tequila supports linux, osx and windows. However, not all optional dependencies are supported on windows.
37
40
 
38
41
  ## Install from PyPi
@@ -269,11 +272,7 @@ Quantum Equation of Motion with Orbital Optimization for Computing Molecular Pro
269
272
  Let us know, if you want your research project and/or tutorial to be included in this list!
270
273
 
271
274
  # Dependencies
272
- Support for additional optimizers or quantum backends can be activated by intalling them in your environment.
273
- Tequila will then detect them automatically.
274
- Currently those are: [Phoenics](https://github.com/aspuru-guzik-group/phoenics)
275
- and [GPyOpt](https://sheffieldml.github.io/GPyOpt/).
276
- Quantum backends are treated in the same way.
275
+ Support for specific backends (quantum simulators, optimizers, quantum chemistry) can be activated by intalling them in your environment.
277
276
 
278
277
  ## Quantum Backends
279
278
  Currently supported
@@ -407,5 +406,3 @@ Tequila runs on Mac OSX.
407
406
  You might get in trouble with installing qulacs since it currently does not work with Apple's clang compiler.
408
407
  You need to install latest GNU compile (at least gcc-7 and g++7) and set them as default before installing qulacs over pip.
409
408
 
410
- ## Qibo and GPyOpt
411
- Currently you can't use Qibo and GPyOpt within the same environment.