Skip to content

Commit b39c78e

Browse files
kottmanjohuettenhoferdavibincoMikel-MaJdelArco98
authored
V1.9.9 merge (#383)
* Add initial state support for expectation values (#373) * Improve circuit compilation performance (#375) * Wave function compact visualization (#376) * wfn compact visualization * minor edit * Update src/tequila/wavefunction/qubit_wavefunction.py Co-authored-by: Oliver Hüttenhofer <[email protected]> * Update src/tequila/wavefunction/qubit_wavefunction.py Co-authored-by: Oliver Hüttenhofer <[email protected]> * Update test_chemistry.py for h2 hcb is a bit slower (overhead due to changes in orbopt function, not dramatic) --------- Co-authored-by: Oliver Hüttenhofer <[email protected]> * spex backend (#379) * adding spex as a supported backend * simulator_spex.py * simple test case * using ExpPauliTerm in circuit * cashing using a circuit hash * using SPEX and OMP_NUM_THREADS for parallelisation if available * using a threshold to eliminate elements with negligible amplitudes * compress_qubit_indicies and moving thresholding to c++ * deleting test_spex_simulator * Update ci_backends.yml * num_qubits param for spex backend (from LSB to MSB) * minor fixup spex backend (#381) * import error in spex backend * spex-backend: error in hadamard compiling * test_gradients: samplers/simulators missmatch * Bug in orbital_optimizer.py to vqe_solver (#377) * Bug in orbital_optimizer.py to vqe_solver * Remove Qiskit noise feature and transpile circuit (#378) * geometry_to_xyz (#382) * Update version.py * Update version.py --------- Co-authored-by: Oliver Hüttenhofer <[email protected]> Co-authored-by: davibinco <[email protected]> Co-authored-by: Mikel-Ma <[email protected]> Co-authored-by: JdelArco98 <[email protected]>
1 parent eb36f43 commit b39c78e

17 files changed

+766
-69
lines changed

Diff for: .github/workflows/ci_backends.yml

+11-9
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,27 @@ jobs:
1616
runs-on: ubuntu-latest
1717
strategy:
1818
matrix:
19-
python-version: ['3.9']
20-
19+
python-version: ['3.10']
20+
include:
21+
- os: ubuntu-latest
22+
cxx: /usr/bin/g++-14
2123
steps:
2224
- uses: actions/checkout@v2
2325
- name: Set up Python ${{ matrix.python-version }}
2426
uses: actions/setup-python@v1
2527
with:
2628
python-version: ${{ matrix.python-version }}
29+
- name: Install spex
30+
run: |
31+
python -m pip install --upgrade pip
32+
pip install git+https://github.com/Mikel-Ma/spex@devel
2733
- name: Install basic dependencies
2834
run: |
2935
python -m pip install --upgrade pip
3036
pip install -r requirements.txt
31-
if [ $ver -eq 7 ]; then
32-
# myqlm does not work in python3.7
33-
pip install "cirq" "qiskit>=0.30" "qulacs" "qibo==0.1.1"
34-
elif [ $ver -eq 8 ]; then
35-
pip install "cirq" "qiskit" "qulacs" "qibo==0.1.1" "myqlm" "cirq-google"
36-
fi
37-
pip install -e .
37+
pip install "qiskit" "qulacs"
38+
# pip install cirq # installs too much stuff currently
39+
pip install -e .
3840
- name: Lint with flake8
3941
run: |
4042
pip install flake8

Diff for: src/tequila/circuit/circuit.py

+20-15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from .qpic import export_to
1111

12+
1213
class QCircuit():
1314
"""
1415
Fundamental class representing an abstract circuit.
@@ -254,8 +255,8 @@ def replace_gates(self, positions: list, circuits: list, replace: list = None):
254255
the gates to add at the corresponding positions
255256
replace: list of bool: (Default value: None)
256257
Default is None which corresponds to all true
257-
decide if gates shall be replaces or if the new parts shall be inserted without replacement
258-
if replace[i] = true: gate at position [i] will be replaces by gates[i]
258+
decide if gates shall be replaced or if the new parts shall be inserted without replacement
259+
if replace[i] = true: gate at position [i] will be replaced by gates[i]
259260
if replace[i] = false: gates[i] circuit will be inserted at position [i] (beaming before gate previously at position [i])
260261
Returns
261262
-------
@@ -271,8 +272,9 @@ def replace_gates(self, positions: list, circuits: list, replace: list = None):
271272
dataset = zip(positions, circuits, replace)
272273
dataset = sorted(dataset, key=lambda x: x[0])
273274

274-
offset = 0
275-
new_gatelist = self.gates
275+
new_gatelist = []
276+
last_idx = -1
277+
276278
for idx, circuit, do_replace in dataset:
277279

278280
# failsafe
@@ -283,13 +285,14 @@ def replace_gates(self, positions: list, circuits: list, replace: list = None):
283285
else:
284286
gatelist = [circuit]
285287

286-
pos = idx + offset
287-
if do_replace:
288-
new_gatelist = new_gatelist[:pos] + gatelist + new_gatelist[pos + 1:]
289-
offset += len(gatelist) - 1
290-
else:
291-
new_gatelist = new_gatelist[:pos] + gatelist + new_gatelist[pos:]
292-
offset += len(gatelist)
288+
new_gatelist += self.gates[last_idx + 1:idx]
289+
new_gatelist += gatelist
290+
if not do_replace:
291+
new_gatelist.append(self.gates[idx])
292+
293+
last_idx = idx
294+
295+
new_gatelist += self.gates[last_idx + 1:]
293296

294297
result = QCircuit(gates=new_gatelist)
295298
result.n_qubits = max(result.n_qubits, self.n_qubits)
@@ -386,7 +389,7 @@ def __iadd__(self, other):
386389
for k, v in other._parameter_map.items():
387390
self._parameter_map[k] += [(x[0] + offset, x[1]) for x in v]
388391

389-
self._gates += copy.deepcopy(other.gates)
392+
self._gates += other.gates
390393
self._min_n_qubits = max(self._min_n_qubits, other._min_n_qubits)
391394

392395
return self
@@ -593,7 +596,7 @@ def _inpl_control_circ(self, control) -> None:
593596
594597
This is an in-place method, so it mutates self and doesn't return any value.
595598
596-
Raise TequilaWarning if there any qubits in common between self and control.
599+
Raise TequilaWarning if there are any qubits in common between self and control.
597600
"""
598601
gates = self.gates
599602
control = list_assignment(control)
@@ -610,8 +613,10 @@ def _inpl_control_circ(self, control) -> None:
610613
if len(control_lst) < len(gate.control) + len(control):
611614
# warnings.warn("Some of the controls {} were already included in the control "
612615
# "of a gate {}.".format(control, gate), TequilaWarning)
613-
raise TequilaWarning(f'Some of the controls {control} were already included '
614-
f'in the control of a gate {gate}.')
616+
raise TequilaWarning(f"Some of the controls {control} were already included "
617+
f"in the control of a gate {gate}. "
618+
f"This might be because the same instance of a gate is used in multiple places, "
619+
f"e.g. because the same circuit was appended twice.")
615620
else:
616621
control_lst, not_control = list(control), list()
617622

Diff for: src/tequila/objective/objective.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import typing, copy, numbers
2+
from typing import Union
23
from tequila.grouping.compile_groups import compile_commuting_parts
34
from tequila import TequilaException
45
from tequila.utils import JoinedTransformation
@@ -545,14 +546,16 @@ def __str__(self):
545546
"variables = {}\n" \
546547
"types = {}".format(unique, measurements, variables, types)
547548

548-
def __call__(self, variables=None, *args, **kwargs):
549+
def __call__(self, variables=None, initial_state = 0, *args, **kwargs):
549550
"""
550551
Return the output of the calculation the objective represents.
551552
552553
Parameters
553554
----------
554555
variables: dict:
555556
dictionary instantiating all variables that may appear within the objective.
557+
initial_state: int or QubitWaveFunction:
558+
the initial state of the circuit
556559
args
557560
kwargs
558561
@@ -579,7 +582,7 @@ def __call__(self, variables=None, *args, **kwargs):
579582
ev_array = []
580583
for E in self.args:
581584
if E not in evaluated: #
582-
expval_result = E(variables=variables, *args, **kwargs)
585+
expval_result = E(variables=variables, initial_state=initial_state, *args, **kwargs)
583586
evaluated[E] = expval_result
584587
else:
585588
expval_result = evaluated[E]

Diff for: src/tequila/quantumchemistry/chemistry_tools.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,14 @@ def read_xyz_from_file(filename):
442442
for i in range(natoms):
443443
coord += content[2 + i]
444444
return coord, comment
445-
445+
def get_xyz(self)->str:
446+
geom = self.parameters.get_geometry()
447+
f = ''
448+
f += f'{len(geom)}\n'
449+
f += f'{self.parameters.name}\n'
450+
for at in geom:
451+
f += f'{at[0]} {at[1][0]} {at[1][1]} {at[1][2]}\n'
452+
return f
446453

447454
@dataclass
448455
class ClosedShellAmplitudes:

Diff for: src/tequila/quantumchemistry/orbital_optimizer.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ def kernel(self, h1, h2, *args, **kwargs):
217217
vqe_solver_arguments = self.vqe_solver_arguments
218218
result = self.vqe_solver(H=H, circuit=self.circuit, molecule=molecule, **vqe_solver_arguments)
219219
if hasattr(self.vqe_solver, "compute_rdms"):
220-
rdm1, rdm2 = self.vqe_solver.compute_rdms(U=self.circuit, variables=result.variables, molecule=molecule, use_hcb=restrict_to_hcb)
220+
rdm1,rdm2 = self.vqe_solver.compute_rdms(U=self.circuit, variables=result.variables, molecule=molecule, use_hcb=restrict_to_hcb)
221+
rdm2 = self.reorder(rdm2, 'dirac', 'mulliken')
221222
elif self.circuit is None:
222223
raise Exception("Orbital Optimizer: Either provide a callable vqe_solver or a circuit")
223224
else:

Diff for: src/tequila/simulators/simulator_api.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
from tequila.circuit.noise import NoiseModel
1212
from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction
1313

14-
SUPPORTED_BACKENDS = ["qulacs", "qulacs_gpu", "qibo", "qiskit", "qiskit_gpu", "cirq", "pyquil", "symbolic", "qlm"]
15-
SUPPORTED_NOISE_BACKENDS = ["qiskit", "qiskit_gpu", "cirq", "pyquil"] # qulacs removed in v.1.9
14+
SUPPORTED_BACKENDS = ["qulacs", "qulacs_gpu", "qibo", "qiskit", "qiskit_gpu", "cirq", "pyquil", "symbolic", "qlm", "spex"]
15+
# TODO: Reenable noise for Qiskit
16+
SUPPORTED_NOISE_BACKENDS = ["cirq", "pyquil"] # qulacs removed in v.1.9
1617
BackendTypes = namedtuple('BackendTypes', 'CircType ExpValueType')
1718
INSTALLED_SIMULATORS = {}
1819
INSTALLED_SAMPLERS = {}
@@ -30,6 +31,15 @@
3031
"""
3132

3233

34+
HAS_SPEX = True
35+
try:
36+
from tequila.simulators.simulator_spex import BackendCircuitSpex, BackendExpectationValueSpex
37+
38+
INSTALLED_SIMULATORS["spex"] = BackendTypes(BackendCircuitSpex, BackendExpectationValueSpex)
39+
except ImportError:
40+
HAS_SPEX = False
41+
42+
3343
HAS_QISKIT = True
3444
try:
3545
from tequila.simulators.simulator_qiskit import BackendCircuitQiskit, BackendExpectationValueQiskit

Diff for: src/tequila/simulators/simulator_base.py

+23-16
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,8 @@ def sample(self, variables, samples, read_out_qubits=None, circuit=None, initial
431431
return self.do_sample(samples=samples, circuit=circuit, read_out_qubits=read_out_qubits,
432432
initial_state=initial_state, *args, **kwargs)
433433

434-
def sample_all_z_hamiltonian(self, samples: int, hamiltonian, variables, *args, **kwargs):
434+
def sample_all_z_hamiltonian(self, samples: int, hamiltonian, variables,
435+
initial_state: Union[int, QubitWaveFunction] = 0, *args, **kwargs):
435436
"""
436437
Sample from a Hamiltonian which only consists of Pauli-Z and unit operators
437438
Parameters
@@ -440,6 +441,8 @@ def sample_all_z_hamiltonian(self, samples: int, hamiltonian, variables, *args,
440441
number of samples to take
441442
hamiltonian
442443
the tequila hamiltonian
444+
initial_state
445+
the initial state of the circuit
443446
args
444447
arguments for do_sample
445448
kwargs
@@ -458,7 +461,7 @@ def sample_all_z_hamiltonian(self, samples: int, hamiltonian, variables, *args,
458461
self.n_qubits))
459462

460463
# run simulators
461-
counts = self.sample(samples=samples, read_out_qubits=abstract_qubits_H, variables=variables, *args, **kwargs)
464+
counts = self.sample(samples=samples, read_out_qubits=abstract_qubits_H, variables=variables, initial_state=initial_state, *args, **kwargs)
462465
read_out_map = {q: i for i, q in enumerate(abstract_qubits_H)}
463466

464467
# compute energy
@@ -481,8 +484,8 @@ def sample_all_z_hamiltonian(self, samples: int, hamiltonian, variables, *args,
481484
assert n_samples == samples
482485
return E
483486

484-
def sample_paulistring(self, samples: int, paulistring, variables, *args,
485-
**kwargs) -> numbers.Real:
487+
def sample_paulistring(self, samples: int, paulistring, variables, initial_state: Union[int, QubitWaveFunction] = 0,
488+
*args, **kwargs) -> numbers.Real:
486489
"""
487490
Sample an individual pauli word (pauli string) and return the average result thereof.
488491
Parameters
@@ -520,8 +523,8 @@ def sample_paulistring(self, samples: int, paulistring, variables, *args,
520523
# on construction: tq.ExpectationValue(H=H, U=U, optimize_measurements=True)
521524
circuit = self.create_circuit(circuit=copy.deepcopy(self.circuit), abstract_circuit=basis_change)
522525
# run simulators
523-
counts = self.sample(samples=samples, circuit=circuit, read_out_qubits=qubits, variables=variables, *args,
524-
**kwargs)
526+
counts = self.sample(samples=samples, circuit=circuit, read_out_qubits=qubits, variables=variables,
527+
initial_state=initial_state, *args, **kwargs)
525528
# compute energy
526529
E = 0.0
527530
n_samples = 0
@@ -792,7 +795,7 @@ def __copy__(self):
792795
def __deepcopy__(self, memodict={}):
793796
return type(self)(self.abstract_expectationvalue, **self._input_args)
794797

795-
def __call__(self, variables, samples: int = None, *args, **kwargs):
798+
def __call__(self, variables, samples: int = None, initial_state: Union[int, QubitWaveFunction] = 0, *args, **kwargs):
796799

797800
variables = format_variable_dictionary(variables=variables)
798801
if self._variables is not None and len(self._variables) > 0:
@@ -802,9 +805,9 @@ def __call__(self, variables, samples: int = None, *args, **kwargs):
802805
self._variables, variables))
803806

804807
if samples is None:
805-
data = self.simulate(variables=variables, *args, **kwargs)
808+
data = self.simulate(variables=variables, initial_state=initial_state, *args, **kwargs)
806809
else:
807-
data = self.sample(variables=variables, samples=samples, *args, **kwargs)
810+
data = self.sample(variables=variables, samples=samples, initial_state=initial_state, *args, **kwargs)
808811

809812
if self._shape is None and self._contraction is None:
810813
# this is the default
@@ -852,7 +855,7 @@ def update_variables(self, variables):
852855
"""wrapper over circuit update_variables"""
853856
self._U.update_variables(variables=variables)
854857

855-
def sample(self, variables, samples, *args, **kwargs) -> numpy.array:
858+
def sample(self, variables, samples, initial_state: Union[int, QubitWaveFunction] = 0, *args, **kwargs) -> numpy.array:
856859
"""
857860
sample the expectationvalue.
858861
@@ -862,6 +865,8 @@ def sample(self, variables, samples, *args, **kwargs) -> numpy.array:
862865
variables to supply to the unitary.
863866
samples: int:
864867
number of samples to perform.
868+
initial_state: int or QubitWaveFunction:
869+
the initial state of the circuit
865870
args
866871
kwargs
867872
@@ -891,23 +896,25 @@ def sample(self, variables, samples, *args, **kwargs) -> numpy.array:
891896
if len(H.qubits) == 0:
892897
E = sum([ps.coeff for ps in H.paulistrings])
893898
elif H.is_all_z():
894-
E = self.U.sample_all_z_hamiltonian(samples=samples, hamiltonian=H, variables=variables, *args,
895-
**kwargs)
899+
E = self.U.sample_all_z_hamiltonian(samples=samples, hamiltonian=H, variables=variables, initial_state=initial_state,
900+
*args, **kwargs)
896901
else:
897902
for ps in H.paulistrings:
898-
E += self.U.sample_paulistring(samples=samples, paulistring=ps, variables=variables, *args,
899-
**kwargs)
903+
E += self.U.sample_paulistring(samples=samples, paulistring=ps, variables=variables, initial_state=initial_state,
904+
*args, **kwargs)
900905
result.append(to_float(E))
901906
return numpy.asarray(result)
902907

903-
def simulate(self, variables, *args, **kwargs):
908+
def simulate(self, variables, initial_state: Union[int, QubitWaveFunction], *args, **kwargs):
904909
"""
905910
Simulate the expectationvalue.
906911
907912
Parameters
908913
----------
909914
variables:
910915
variables to supply to the unitary.
916+
initial_state: int or QubitWaveFunction:
917+
the initial state of the circuit
911918
args
912919
kwargs
913920
@@ -922,7 +929,7 @@ def simulate(self, variables, *args, **kwargs):
922929
final_E = 0.0
923930
# TODO inefficient,
924931
# Always better to overwrite this function
925-
wfn = self.U.simulate(variables=variables, *args, **kwargs)
932+
wfn = self.U.simulate(variables=variables, initial_state=initial_state, *args, **kwargs)
926933
final_E += wfn.compute_expectationvalue(operator=H)
927934
result.append(to_float(final_E))
928935
return numpy.asarray(result)

Diff for: src/tequila/simulators/simulator_qiskit.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import warnings
1010
import numpy as np
1111
import qiskit, qiskit_aer, qiskit.providers.fake_provider
12-
from qiskit import QuantumCircuit
12+
from qiskit import QuantumCircuit, transpile
1313

1414
HAS_NOISE = True
1515
try:
@@ -316,11 +316,9 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF
316316
optimization_level = kwargs['optimization_level']
317317

318318
circuit = self.circuit.assign_parameters(self.resolver)
319-
320319
circuit = self.add_state_init(circuit, initial_state)
321-
322320
circuit.save_statevector()
323-
321+
circuit = transpile(circuit, qiskit_backend)
324322
backend_result = qiskit_backend.run(circuit, optimization_level=optimization_level).result()
325323

326324
return QubitWaveFunction.from_array(array=backend_result.get_statevector(circuit).data, numbering=self.numbering)
@@ -349,11 +347,11 @@ def do_sample(self, circuit: qiskit.QuantumCircuit, samples: int, read_out_qubit
349347
if 'optimization_level' in kwargs:
350348
optimization_level = kwargs['optimization_level']
351349
if self.device is None:
352-
qiskit_backend = self.retrieve_device('aer_simulator')
350+
qiskit_backend = self.retrieve_device(self.STATEVECTOR_DEVICE_NAME)
353351
else:
354352
qiskit_backend = self.retrieve_device(self.device)
355353

356-
if isinstance(qiskit_backend, IBMBackend):
354+
if HAS_IBMQ and isinstance(qiskit_backend, IBMBackend):
357355
if self.noise_model is not None:
358356
raise TequilaException(
359357
'Cannot combine backend {} with custom noise models.'.format(str(qiskit_backend)))
@@ -370,7 +368,7 @@ def do_sample(self, circuit: qiskit.QuantumCircuit, samples: int, read_out_qubit
370368
if self.noise_model is not None:
371369
from_back = self.noise_model
372370
basis = from_back.basis_gates
373-
use_backend = self.retrieve_device('aer_simulator')
371+
use_backend = self.retrieve_device(self.STATEVECTOR_DEVICE_NAME)
374372
use_backend.set_options(noise_model=from_back)
375373
circuit = qiskit.transpile(circuit, backend=use_backend,
376374
basis_gates=basis,

0 commit comments

Comments
 (0)