From b730ed94a430685b25b78609807018f4e9bfe3b6 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:57:27 +0100 Subject: [PATCH 01/32] adding spex as a supported backend --- src/tequila/simulators/simulator_api.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/tequila/simulators/simulator_api.py b/src/tequila/simulators/simulator_api.py index 8db872a9..54db5a11 100755 --- a/src/tequila/simulators/simulator_api.py +++ b/src/tequila/simulators/simulator_api.py @@ -11,7 +11,7 @@ from tequila.circuit.noise import NoiseModel from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction -SUPPORTED_BACKENDS = ["qulacs", "qulacs_gpu", "qibo", "qiskit", "qiskit_gpu", "cirq", "pyquil", "symbolic", "qlm"] +SUPPORTED_BACKENDS = ["qulacs", "qulacs_gpu", "qibo", "qiskit", "qiskit_gpu", "cirq", "pyquil", "symbolic", "qlm", "spex"] SUPPORTED_NOISE_BACKENDS = ["qiskit", "qiskit_gpu", "cirq", "pyquil"] # qulacs removed in v.1.9 BackendTypes = namedtuple('BackendTypes', 'CircType ExpValueType') INSTALLED_SIMULATORS = {} @@ -30,6 +30,18 @@ """ +HAS_SPEX = True +try: + from tequila.simulators.simulator_spex import BackendCircuitSpex, BackendExpectationValueSpex + + INSTALLED_SIMULATORS["spex"] = BackendTypes(BackendCircuitSpex, BackendExpectationValueSpex) + INSTALLED_SAMPLERS["spex"] = BackendTypes(BackendCircuitSpex, BackendExpectationValueSpex) + +except ImportError: + print("Import Spex Error") + HAS_SPEX = False + + HAS_QISKIT = True try: from tequila.simulators.simulator_qiskit import BackendCircuitQiskit, BackendExpectationValueQiskit From a2e3b724a6bde7c7d4b62bc2874a755ee50b78d1 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:57:56 +0100 Subject: [PATCH 02/32] simulator_spex.py --- src/tequila/simulators/simulator_spex.py | 203 +++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 src/tequila/simulators/simulator_spex.py diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py new file mode 100644 index 00000000..8282c19f --- /dev/null +++ b/src/tequila/simulators/simulator_spex.py @@ -0,0 +1,203 @@ +from tequila.simulators.simulator_base import BackendExpectationValue, BackendCircuit +from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction +from tequila.utils import TequilaException +from tequila.hamiltonian import paulis, PauliString +from tequila.circuit._gates_impl import ExponentialPauliGateImpl + +import numpy +import spex_tequila + +class TequilaSpexException(TequilaException): + pass + +class BackendCircuitSpex(BackendCircuit): + compiler_arguments = { + "trotterized": False, + "swap": False, + "multitarget": False, + "controlled_rotation": False, + "generalized_rotation": False, + "exponential_pauli": True, + "controlled_exponential_pauli": True, + "phase": False, + "power": False, + "hadamard_power": False, + "controlled_power": False, + "controlled_phase": False, + "toffoli": False, + "phase_to_z": True, + "cc_max": False + } + + + def initialize_circuit(self, *args, **kwargs): + return [] + + def add_basic_gate(self, gate, circuit, *args, **kwargs): + """ + Adds a basic Exponential Pauli gate to the circuit. + + Args: + gate: The gate object to be added. + circuit: The list representing the circuit. + """ + + ps = gate.paulistring + angle = gate.parameter + + + # Ensure that ps is a PauliString + if isinstance(ps, PauliString): + pauli_dict = dict(ps.items()) + else: + raise TequilaSpexException(f"Unexpected paulistring type in add_basic_gate: {type(ps)}") + + # Create the spex_pauli_string in the correct format + spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) + + # Add the gate to the circuit + circuit.append((spex_pauli_string, angle)) + + + + def add_parametrized_gate(self, gate, circuit, *args, **kwargs): + """ + Adds a parametrized Exponential Pauli gate to the circuit. + + Args: + gate: The gate object to be added. + circuit: The list representing the circuit. + """ + + # Check if gate is an ExponentialPauliGateImpl + if isinstance(gate, ExponentialPauliGateImpl): + ps = gate.paulistring + angle = gate.parameter + + # Ensure that ps is a PauliString + if isinstance(ps, PauliString): + pauli_dict = dict(ps.items()) + elif isinstance(ps, dict): + pauli_dict = ps + else: + raise TequilaSpexException(f"Unexpected paulistring type: {type(ps)}") + + # Create the spex_pauli_string in the correct format + spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) + + # Add the gate to the circuit + circuit.append((spex_pauli_string, angle)) + else: + # If the gate is not an Exponential Pauli gate, raise an error + raise TequilaSpexException(f"Unsupported gate type: {type(gate)}. Only Exponential Pauli gates are allowed.") + + + def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveFunction: + """ + Simulates the circuit and returns the final qubit state. + + Args: + variables: Variables to adjust the circuit (not used for fixed gates). + initial_state: The initial state of the qubit. + + Returns: + QubitWaveFunction: The final state after applying the circuit. + """ + n_qubits = self.n_qubits + # Prepare the initial state + if isinstance(initial_state, int): + if initial_state == 0: + state = spex_tequila.initialize_zero_state(n_qubits) + else: + state = {initial_state: 1.0 + 0j} + else: + # initial_state is already a QubitWaveFunction + state = initial_state.to_dictionary() + + # Apply the Exponential Pauli Term gates + U_terms = [] + for pauli_str, angle in self.circuit: + U_terms.append(spex_tequila.ExpPauliTerm(pauli_str, angle)) + + final_state = spex_tequila.apply_U(U_terms, state) + + # Create the QubitWaveFunction from the final state + wfn = QubitWaveFunction.from_dictionary(final_state, n_qubits=n_qubits, numbering=self.numbering) + return wfn + + +class BackendExpectationValueSpex(BackendExpectationValue): + """ + Backend for computing expectation values using the spex_tequila C++ module. + """ + + BackendCircuitType = BackendCircuitSpex + + def initialize_hamiltonian(self, hamiltonians): + """ + Initializes the Hamiltonian terms for the simulation. + + Args: + hamiltonians: A list of Hamiltonian objects. + + Returns: + tuple: A converted list of (pauli_string, coefficient) tuples. + """ + # Convert Tequila Hamiltonians into a list of (pauli_string, coeff) tuples for spex_tequila. + converted = [] + for H in hamiltonians: + terms = [] + for ps in H.paulistrings: + # Construct Pauli string like "X(0)Y(1)" + spex_ps = "".join([f"{p.upper()}({q})" for q, p in dict(ps.items()).items()]) + terms.append((spex_ps, ps.coeff)) + converted.append(terms) + return tuple(converted) + + + + def simulate(self, variables, initial_state=0, *args, **kwargs): + """ + Computes the expectation value by simulating the circuit U and evaluating ⟨ψ|H|ψ⟩. + + Args: + variables: Variables to adjust the circuit (not used for fixed gates). + initial_state: The initial state of the qubit. + + Returns: + numpy.ndarray: The computed expectation values for the Hamiltonian terms. + """ + self.update_variables(variables) + n_qubits = self.U.n_qubits + + # Prepare the initial state + if isinstance(initial_state, int): + if initial_state == 0: + state = spex_tequila.initialize_zero_state(n_qubits) + else: + state = {initial_state: 1.0 + 0j} + else: + # initial_state is a QubitWaveFunction + state = initial_state.to_dictionary() + + # Convert circuit gates into Exponential Pauli Terms + U_terms = [] + for pauli_str, angle in self.U.circuit: + U_terms.append(spex_tequila.ExpPauliTerm(pauli_str, angle)) + + # Apply U to the initial state + final_state = spex_tequila.apply_U(U_terms, state) + + # Calculate the expectation value for each Hamiltonian + results = [] + for H_terms in self.H: + val = spex_tequila.expectation_value(final_state, final_state, H_terms) + results.append(val.real) + return numpy.array(results) + + + def sample(self, variables, samples, initial_state=0, *args, **kwargs): + return super().sample(variables=variables, samples=samples, initial_state=initial_state, *args, **kwargs) + + def sample_paulistring(self, samples: int, paulistring, variables, initial_state=0, *args, **kwargs): + return super().sample_paulistring(samples, paulistring, variables, initial_state=initial_state, *args, **kwargs) From f987cb918818547098774c8678348993815b1b45 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:58:08 +0100 Subject: [PATCH 03/32] simple test case --- tests/test_spex_simulator.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/test_spex_simulator.py diff --git a/tests/test_spex_simulator.py b/tests/test_spex_simulator.py new file mode 100644 index 00000000..e8689fe0 --- /dev/null +++ b/tests/test_spex_simulator.py @@ -0,0 +1,12 @@ +import tequila as tq +import numpy as np +from tequila.hamiltonian import PauliString + +U = tq.gates.ExpPauli(paulistring=PauliString({0: "X", 1: "Z"}), angle=np.pi / 2) + +H = tq.QubitHamiltonian("Z(0)") + +E = tq.ExpectationValue(U=U, H=H) + +print("\nspex:", tq.simulate(E, backend='spex')) +print("qulacs:", tq.simulate(E, backend='qulacs')) From ded9d31f6f048bb803bc3f9f24680c77cbe74dda Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Thu, 12 Dec 2024 15:37:42 +0100 Subject: [PATCH 04/32] compiler_arguments fix --- src/tequila/simulators/simulator_spex.py | 6 +++--- tests/test_spex_simulator.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 8282c19f..ec373638 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -17,15 +17,15 @@ class BackendCircuitSpex(BackendCircuit): "multitarget": False, "controlled_rotation": False, "generalized_rotation": False, - "exponential_pauli": True, - "controlled_exponential_pauli": True, + "exponential_pauli": False, + "controlled_exponential_pauli": False, "phase": False, "power": False, "hadamard_power": False, "controlled_power": False, "controlled_phase": False, "toffoli": False, - "phase_to_z": True, + "phase_to_z": False, "cc_max": False } diff --git a/tests/test_spex_simulator.py b/tests/test_spex_simulator.py index e8689fe0..ef0d6edc 100644 --- a/tests/test_spex_simulator.py +++ b/tests/test_spex_simulator.py @@ -8,5 +8,5 @@ E = tq.ExpectationValue(U=U, H=H) -print("\nspex:", tq.simulate(E, backend='spex')) +print("spex:", tq.simulate(E, backend='spex')) print("qulacs:", tq.simulate(E, backend='qulacs')) From 6f359e1f1eac7db46dd07c82f5e76063656c12da Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Wed, 18 Dec 2024 20:07:30 +0100 Subject: [PATCH 05/32] using make_generator for QGateImpl --- src/tequila/simulators/simulator_api.py | 2 - src/tequila/simulators/simulator_spex.py | 161 ++++++++++++++--------- tests/test_spex_simulator.py | 80 +++++++++++ 3 files changed, 178 insertions(+), 65 deletions(-) diff --git a/src/tequila/simulators/simulator_api.py b/src/tequila/simulators/simulator_api.py index 54db5a11..4d36764f 100755 --- a/src/tequila/simulators/simulator_api.py +++ b/src/tequila/simulators/simulator_api.py @@ -35,8 +35,6 @@ from tequila.simulators.simulator_spex import BackendCircuitSpex, BackendExpectationValueSpex INSTALLED_SIMULATORS["spex"] = BackendTypes(BackendCircuitSpex, BackendExpectationValueSpex) - INSTALLED_SAMPLERS["spex"] = BackendTypes(BackendCircuitSpex, BackendExpectationValueSpex) - except ImportError: print("Import Spex Error") HAS_SPEX = False diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index ec373638..2fcb7ab4 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -2,94 +2,124 @@ from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction from tequila.utils import TequilaException from tequila.hamiltonian import paulis, PauliString -from tequila.circuit._gates_impl import ExponentialPauliGateImpl +from tequila.circuit._gates_impl import ExponentialPauliGateImpl, QGateImpl, RotationGateImpl, QubitHamiltonian +from tequila import BitNumbering +from tequila.circuit import compile_circuit import numpy import spex_tequila +numbering = BitNumbering.LSB + class TequilaSpexException(TequilaException): pass class BackendCircuitSpex(BackendCircuit): compiler_arguments = { - "trotterized": False, - "swap": False, - "multitarget": False, - "controlled_rotation": False, - "generalized_rotation": False, + "multitarget": True, + "multicontrol": True, + "trotterized": True, + "generalized_rotation": True, "exponential_pauli": False, - "controlled_exponential_pauli": False, - "phase": False, - "power": False, - "hadamard_power": False, - "controlled_power": False, - "controlled_phase": False, - "toffoli": False, - "phase_to_z": False, - "cc_max": False + "controlled_exponential_pauli": True, + "hadamard_power": True, + "controlled_power": True, + "power": True, + "toffoli": True, + "controlled_phase": True, + "phase": True, + "phase_to_z": True, + "controlled_rotation": True, + "swap": True, + "cc_max": True, + "ry_gate": True, + "y_gate": True, + "ch_gate": True } def initialize_circuit(self, *args, **kwargs): return [] - - def add_basic_gate(self, gate, circuit, *args, **kwargs): - """ - Adds a basic Exponential Pauli gate to the circuit. - - Args: - gate: The gate object to be added. - circuit: The list representing the circuit. - """ - - ps = gate.paulistring - angle = gate.parameter - - - # Ensure that ps is a PauliString + + + def extract_pauli_dict(self, ps): + # Extract a dict {qubit: 'X'/'Y'/'Z'} from ps. + # ps is PauliString or QubitHamiltonian if isinstance(ps, PauliString): - pauli_dict = dict(ps.items()) + return dict(ps.items()) + elif isinstance(ps, QubitHamiltonian): + if len(ps.paulistrings) == 1: + return dict(ps.paulistrings[0].items()) + else: + raise TequilaSpexException("Rotation gate generator with multiple PauliStrings is not supported.") else: - raise TequilaSpexException(f"Unexpected paulistring type in add_basic_gate: {type(ps)}") + raise TequilaSpexException(f"Unexpected generator type: {type(ps)}") - # Create the spex_pauli_string in the correct format - spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) - # Add the gate to the circuit - circuit.append((spex_pauli_string, angle)) + def add_basic_gate(self, gate, circuit, *args, **kwargs): + if isinstance(gate, ExponentialPauliGateImpl): + ps = gate.paulistring + angle = gate.parameter + pauli_dict = self.extract_pauli_dict(ps) + spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) + if not spex_pauli_string: + spex_pauli_string = "I" + circuit.append((spex_pauli_string, angle)) + elif isinstance(gate, RotationGateImpl): + ps = gate.generator + angle = gate.parameter + pauli_dict = self.extract_pauli_dict(ps) + spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) + if not spex_pauli_string: + spex_pauli_string = "I" + circuit.append((spex_pauli_string, angle)) + elif isinstance(gate, QGateImpl): + for ps in gate.make_generator(include_controls=True).paulistrings: + angle = numpy.pi * ps.coeff + pauli_dict = self.extract_pauli_dict(ps) + spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) + if not spex_pauli_string: + spex_pauli_string = "I" + circuit.append((spex_pauli_string, angle)) - def add_parametrized_gate(self, gate, circuit, *args, **kwargs): - """ - Adds a parametrized Exponential Pauli gate to the circuit. + else: + raise TequilaSpexException(f"Unsupported gate object type: {type(gate)}. " + "All gates should be compiled to exponential pauli or rotation gates.") - Args: - gate: The gate object to be added. - circuit: The list representing the circuit. - """ - # Check if gate is an ExponentialPauliGateImpl + def add_parametrized_gate(self, gate, circuit, *args, **kwargs): if isinstance(gate, ExponentialPauliGateImpl): ps = gate.paulistring angle = gate.parameter - - # Ensure that ps is a PauliString - if isinstance(ps, PauliString): - pauli_dict = dict(ps.items()) - elif isinstance(ps, dict): - pauli_dict = ps - else: - raise TequilaSpexException(f"Unexpected paulistring type: {type(ps)}") - - # Create the spex_pauli_string in the correct format + pauli_dict = self.extract_pauli_dict(ps) spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) + if not spex_pauli_string: + spex_pauli_string = "I" + circuit.append((spex_pauli_string, angle)) - # Add the gate to the circuit + elif isinstance(gate, RotationGateImpl): + ps = gate.generator + angle = gate.parameter + pauli_dict = self.extract_pauli_dict(ps) + spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) + if not spex_pauli_string: + spex_pauli_string = "I" circuit.append((spex_pauli_string, angle)) + + elif isinstance(gate, QGateImpl): + for ps in gate.make_generator(include_controls=True).paulistrings: + angle = gate.parameter + pauli_dict = self.extract_pauli_dict(ps) + spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) + if not spex_pauli_string: + spex_pauli_string = "I" + circuit.append((spex_pauli_string, angle)) + else: - # If the gate is not an Exponential Pauli gate, raise an error - raise TequilaSpexException(f"Unsupported gate type: {type(gate)}. Only Exponential Pauli gates are allowed.") + raise TequilaSpexException(f"Unsupported gate type: {type(gate)}. " + "Only Exponential Pauli and Rotation gates are allowed after compilation.") def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveFunction: @@ -121,8 +151,9 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF final_state = spex_tequila.apply_U(U_terms, state) - # Create the QubitWaveFunction from the final state - wfn = QubitWaveFunction.from_dictionary(final_state, n_qubits=n_qubits, numbering=self.numbering) + wfn = QubitWaveFunction(n_qubits=n_qubits, numbering=numbering) + for state, amplitude in final_state.items(): + wfn[state] = amplitude return wfn @@ -150,12 +181,13 @@ def initialize_hamiltonian(self, hamiltonians): for ps in H.paulistrings: # Construct Pauli string like "X(0)Y(1)" spex_ps = "".join([f"{p.upper()}({q})" for q, p in dict(ps.items()).items()]) - terms.append((spex_ps, ps.coeff)) + if not spex_ps: + spex_ps = "I" + terms.append((spex_ps, ps.coeff)) converted.append(terms) return tuple(converted) - def simulate(self, variables, initial_state=0, *args, **kwargs): """ Computes the expectation value by simulating the circuit U and evaluating ⟨ψ|H|ψ⟩. @@ -167,6 +199,9 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): Returns: numpy.ndarray: The computed expectation values for the Hamiltonian terms. """ + + # variables as dict, variable map for var to gates + self.update_variables(variables) n_qubits = self.U.n_qubits @@ -191,7 +226,7 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): # Calculate the expectation value for each Hamiltonian results = [] for H_terms in self.H: - val = spex_tequila.expectation_value(final_state, final_state, H_terms) + val = spex_tequila.expectation_value_parallel(final_state, final_state, H_terms) results.append(val.real) return numpy.array(results) @@ -200,4 +235,4 @@ def sample(self, variables, samples, initial_state=0, *args, **kwargs): return super().sample(variables=variables, samples=samples, initial_state=initial_state, *args, **kwargs) def sample_paulistring(self, samples: int, paulistring, variables, initial_state=0, *args, **kwargs): - return super().sample_paulistring(samples, paulistring, variables, initial_state=initial_state, *args, **kwargs) + return super().sample_paulistring(samples, paulistring, variables, initial_state=initial_state, *args, **kwargs) \ No newline at end of file diff --git a/tests/test_spex_simulator.py b/tests/test_spex_simulator.py index ef0d6edc..b1d857c0 100644 --- a/tests/test_spex_simulator.py +++ b/tests/test_spex_simulator.py @@ -1,12 +1,92 @@ import tequila as tq import numpy as np +import time from tequila.hamiltonian import PauliString +print("\nTest: 1") U = tq.gates.ExpPauli(paulistring=PauliString({0: "X", 1: "Z"}), angle=np.pi / 2) +H = tq.QubitHamiltonian("Z(0)") +E = tq.ExpectationValue(U=U, H=H) + +print(U) +print("spex-U:", tq.simulate(U, backend='spex')) +print("qulacs-U:", tq.simulate(U, backend='qulacs')) +print("spex:", tq.simulate(E, backend='spex')) +print("qulacs:", tq.simulate(E, backend='qulacs')) + + +print("\nTest: 2") +H = tq.QubitHamiltonian("Z(0)X(1)") + +U = tq.gates.Rx(angle=np.pi / 4, target=(0,)) +U += tq.gates.Ry(angle=np.pi / 3, target=(1,)) +E = tq.ExpectationValue(U=U, H=H) +print(U) +print("spex-U:", tq.simulate(U, backend='spex')) +print("qulacs-U:", tq.simulate(U, backend='qulacs')) +print("spex:", tq.simulate(E, backend='spex')) +print("qulacs:", tq.simulate(E, backend='qulacs')) + + +print("\nTest: 3") H = tq.QubitHamiltonian("Z(0)") +U = tq.gates.X(target=(0,)) +U += tq.gates.Y(target=(1,)) E = tq.ExpectationValue(U=U, H=H) +print(U) +print("spex-U:", tq.simulate(U, backend='spex')) +print("qulacs-U:", tq.simulate(U, backend='qulacs')) +print("spex:", tq.simulate(E, backend='spex')) +print("qulacs:", tq.simulate(E, backend='qulacs')) + + +print("\nTest: 4") +U = ( + tq.gates.Rx(angle=np.pi / 2, target=(0,)) + + tq.gates.Rz(angle=np.pi / 2, target=(1,)) + + tq.gates.Ry(angle=np.pi / 3, target=(2,)) + + tq.gates.ExpPauli(paulistring=PauliString({0: "X", 1: "X", 2: "X"}), angle=np.pi / 4) +) + +H = tq.QubitHamiltonian("X(0)X(1)X(2) + Z(0) + Z(1) + Z(2)") +E = tq.ExpectationValue(U=U, H=H) + +print(U) +print("spex-U:", tq.simulate(U, backend='spex')) +print("qulacs-U:", tq.simulate(U, backend='qulacs')) +print("spex:", tq.simulate(E, backend='spex')) +print("qulacs:", tq.simulate(E, backend='qulacs')) + + +print("\nTest: 5") +n = 2 # <--- Variabel, qubits sind am Ende 4n + +R = 1.5 +geom = "" +for k in range(2*n): + geom += "h 0.0 0.0 {}\n".format(R*k) + +edges = [(2*i, 2*i+1) for i in range(n)] + +# --> pip install pyscf <-- +mol = tq.Molecule(geometry=geom, basis_set="sto-3g") +U = mol.make_ansatz(name="SPA", edges=edges) + +# SPA -> HCB-SPA + +U = U.map_variables({k:1.0 for k in U.extract_variables()}) + +H = mol.make_hamiltonian() + +# make_hamiltonian -> make_hardcore_boson_hamiltonian + +E = tq.ExpectationValue(H=H, U=U) + +print(U) +print("spex-U:", tq.simulate(U, backend='spex')) +print("qulacs-U:", tq.simulate(U, backend='qulacs')) print("spex:", tq.simulate(E, backend='spex')) print("qulacs:", tq.simulate(E, backend='qulacs')) From 03a45256148fd160cfa5cc98b762612eb0477f9d Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:37:38 +0100 Subject: [PATCH 06/32] switch from paulistrings to paulimaps --- src/tequila/simulators/simulator_spex.py | 106 ++++++++++------------- 1 file changed, 48 insertions(+), 58 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 2fcb7ab4..16cd43c8 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -14,6 +14,19 @@ class TequilaSpexException(TequilaException): pass +def extract_pauli_dict(ps): + # Extract a dict {qubit: 'X'/'Y'/'Z'} from ps. + # ps is PauliString or QubitHamiltonian + if isinstance(ps, PauliString): + return dict(ps.items()) + elif isinstance(ps, QubitHamiltonian): + if len(ps.paulistrings) == 1: + return dict(ps.paulistrings[0].items()) + else: + raise TequilaSpexException("Rotation gate generator with multiple PauliStrings is not supported.") + else: + raise TequilaSpexException(f"Unexpected generator type: {type(ps)}") + class BackendCircuitSpex(BackendCircuit): compiler_arguments = { "multitarget": True, @@ -40,49 +53,24 @@ class BackendCircuitSpex(BackendCircuit): def initialize_circuit(self, *args, **kwargs): return [] - - - def extract_pauli_dict(self, ps): - # Extract a dict {qubit: 'X'/'Y'/'Z'} from ps. - # ps is PauliString or QubitHamiltonian - if isinstance(ps, PauliString): - return dict(ps.items()) - elif isinstance(ps, QubitHamiltonian): - if len(ps.paulistrings) == 1: - return dict(ps.paulistrings[0].items()) - else: - raise TequilaSpexException("Rotation gate generator with multiple PauliStrings is not supported.") - else: - raise TequilaSpexException(f"Unexpected generator type: {type(ps)}") def add_basic_gate(self, gate, circuit, *args, **kwargs): if isinstance(gate, ExponentialPauliGateImpl): - ps = gate.paulistring + pauli_map = extract_pauli_dict(gate.paulistring) angle = gate.parameter - pauli_dict = self.extract_pauli_dict(ps) - spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) - if not spex_pauli_string: - spex_pauli_string = "I" - circuit.append((spex_pauli_string, angle)) + circuit.append({"pauli_map": pauli_map, "angle": angle}) elif isinstance(gate, RotationGateImpl): - ps = gate.generator + pauli_map = extract_pauli_dict(gate.generator) angle = gate.parameter - pauli_dict = self.extract_pauli_dict(ps) - spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) - if not spex_pauli_string: - spex_pauli_string = "I" - circuit.append((spex_pauli_string, angle)) + circuit.append({"pauli_map": pauli_map, "angle": angle}) elif isinstance(gate, QGateImpl): for ps in gate.make_generator(include_controls=True).paulistrings: angle = numpy.pi * ps.coeff - pauli_dict = self.extract_pauli_dict(ps) - spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) - if not spex_pauli_string: - spex_pauli_string = "I" - circuit.append((spex_pauli_string, angle)) + pauli_map = dict(ps.items()) + circuit.append({"pauli_map": pauli_map, "angle": angle}) else: raise TequilaSpexException(f"Unsupported gate object type: {type(gate)}. " @@ -91,31 +79,20 @@ def add_basic_gate(self, gate, circuit, *args, **kwargs): def add_parametrized_gate(self, gate, circuit, *args, **kwargs): if isinstance(gate, ExponentialPauliGateImpl): - ps = gate.paulistring + pauli_map = extract_pauli_dict(gate.paulistring) angle = gate.parameter - pauli_dict = self.extract_pauli_dict(ps) - spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) - if not spex_pauli_string: - spex_pauli_string = "I" - circuit.append((spex_pauli_string, angle)) + circuit.append({"pauli_map": pauli_map, "angle": angle}) elif isinstance(gate, RotationGateImpl): - ps = gate.generator + pauli_map = extract_pauli_dict(gate.generator) angle = gate.parameter - pauli_dict = self.extract_pauli_dict(ps) - spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) - if not spex_pauli_string: - spex_pauli_string = "I" - circuit.append((spex_pauli_string, angle)) + circuit.append({"pauli_map": pauli_map, "angle": angle}) elif isinstance(gate, QGateImpl): for ps in gate.make_generator(include_controls=True).paulistrings: angle = gate.parameter - pauli_dict = self.extract_pauli_dict(ps) - spex_pauli_string = "".join([f"{p.upper()}({q})" for q, p in pauli_dict.items()]) - if not spex_pauli_string: - spex_pauli_string = "I" - circuit.append((spex_pauli_string, angle)) + pauli_map = dict(ps.items()) + circuit.append({"pauli_map": pauli_map, "angle": angle}) else: raise TequilaSpexException(f"Unsupported gate type: {type(gate)}. " @@ -146,8 +123,15 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF # Apply the Exponential Pauli Term gates U_terms = [] - for pauli_str, angle in self.circuit: - U_terms.append(spex_tequila.ExpPauliTerm(pauli_str, angle)) + for item in self.circuit: + pauli_map = item["pauli_map"] + angle = item["angle"] + + term = spex_tequila.ExpPauliTerm() + term.pauli_map = pauli_map + term.angle = float(angle) + + U_terms.append(term) final_state = spex_tequila.apply_U(U_terms, state) @@ -180,10 +164,10 @@ def initialize_hamiltonian(self, hamiltonians): terms = [] for ps in H.paulistrings: # Construct Pauli string like "X(0)Y(1)" - spex_ps = "".join([f"{p.upper()}({q})" for q, p in dict(ps.items()).items()]) - if not spex_ps: - spex_ps = "I" - terms.append((spex_ps, ps.coeff)) + pauli_map = dict(ps.items()) + term = spex_tequila.ExpPauliTerm() + term.pauli_map = pauli_map + terms.append((term, ps.coeff)) converted.append(terms) return tuple(converted) @@ -215,12 +199,18 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): # initial_state is a QubitWaveFunction state = initial_state.to_dictionary() - # Convert circuit gates into Exponential Pauli Terms + # Apply U to the initial state U_terms = [] - for pauli_str, angle in self.U.circuit: - U_terms.append(spex_tequila.ExpPauliTerm(pauli_str, angle)) + for item in self.U.circuit: + pauli_map = item["pauli_map"] + angle = item["angle"] + + term = spex_tequila.ExpPauliTerm() + term.pauli_map = pauli_map + term.angle = float(angle) + + U_terms.append(term) - # Apply U to the initial state final_state = spex_tequila.apply_U(U_terms, state) # Calculate the expectation value for each Hamiltonian From 3b1ff99af828d01c62714d1b989065a6afcb0369 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:49:33 +0100 Subject: [PATCH 07/32] using ExpPauliTerm in circuit --- src/tequila/simulators/simulator_spex.py | 68 +++++++++--------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 16cd43c8..794e5a80 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -56,21 +56,23 @@ def initialize_circuit(self, *args, **kwargs): def add_basic_gate(self, gate, circuit, *args, **kwargs): + exp_term = spex_tequila.ExpPauliTerm() if isinstance(gate, ExponentialPauliGateImpl): - pauli_map = extract_pauli_dict(gate.paulistring) - angle = gate.parameter - circuit.append({"pauli_map": pauli_map, "angle": angle}) + exp_term.pauli_map = extract_pauli_dict(gate.paulistring) + exp_term.angle = gate.parameter + circuit.append(exp_term) elif isinstance(gate, RotationGateImpl): - pauli_map = extract_pauli_dict(gate.generator) - angle = gate.parameter - circuit.append({"pauli_map": pauli_map, "angle": angle}) + exp_term.pauli_map = extract_pauli_dict(gate.generator) + exp_term.angle = gate.parameter + circuit.append(exp_term) elif isinstance(gate, QGateImpl): for ps in gate.make_generator(include_controls=True).paulistrings: - angle = numpy.pi * ps.coeff - pauli_map = dict(ps.items()) - circuit.append({"pauli_map": pauli_map, "angle": angle}) + exp_term = spex_tequila.ExpPauliTerm() + exp_term.pauli_map = dict(ps.items()) + exp_term.angle = numpy.pi * ps.coeff + circuit.append(exp_term) else: raise TequilaSpexException(f"Unsupported gate object type: {type(gate)}. " @@ -78,21 +80,23 @@ def add_basic_gate(self, gate, circuit, *args, **kwargs): def add_parametrized_gate(self, gate, circuit, *args, **kwargs): + exp_term = spex_tequila.ExpPauliTerm() if isinstance(gate, ExponentialPauliGateImpl): - pauli_map = extract_pauli_dict(gate.paulistring) - angle = gate.parameter - circuit.append({"pauli_map": pauli_map, "angle": angle}) + exp_term.pauli_map = extract_pauli_dict(gate.paulistring) + exp_term.angle = gate.parameter + circuit.append(exp_term) elif isinstance(gate, RotationGateImpl): - pauli_map = extract_pauli_dict(gate.generator) - angle = gate.parameter - circuit.append({"pauli_map": pauli_map, "angle": angle}) + exp_term.pauli_map = extract_pauli_dict(gate.generator) + exp_term.angle = gate.parameter + circuit.append(exp_term) elif isinstance(gate, QGateImpl): for ps in gate.make_generator(include_controls=True).paulistrings: - angle = gate.parameter - pauli_map = dict(ps.items()) - circuit.append({"pauli_map": pauli_map, "angle": angle}) + exp_term = spex_tequila.ExpPauliTerm() + exp_term.pauli_map = dict(ps.items()) + exp_term.angle = gate.parameter + circuit.append(exp_term) else: raise TequilaSpexException(f"Unsupported gate type: {type(gate)}. " @@ -121,19 +125,7 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF # initial_state is already a QubitWaveFunction state = initial_state.to_dictionary() - # Apply the Exponential Pauli Term gates - U_terms = [] - for item in self.circuit: - pauli_map = item["pauli_map"] - angle = item["angle"] - - term = spex_tequila.ExpPauliTerm() - term.pauli_map = pauli_map - term.angle = float(angle) - - U_terms.append(term) - - final_state = spex_tequila.apply_U(U_terms, state) + final_state = spex_tequila.apply_U(self.circuit, state) wfn = QubitWaveFunction(n_qubits=n_qubits, numbering=numbering) for state, amplitude in final_state.items(): @@ -199,19 +191,7 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): # initial_state is a QubitWaveFunction state = initial_state.to_dictionary() - # Apply U to the initial state - U_terms = [] - for item in self.U.circuit: - pauli_map = item["pauli_map"] - angle = item["angle"] - - term = spex_tequila.ExpPauliTerm() - term.pauli_map = pauli_map - term.angle = float(angle) - - U_terms.append(term) - - final_state = spex_tequila.apply_U(U_terms, state) + final_state = spex_tequila.apply_U(self.U.circuit, state) # Calculate the expectation value for each Hamiltonian results = [] From 816c0f3390c2b826bd09648de4c9343c78f3e211 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Fri, 3 Jan 2025 23:22:21 +0100 Subject: [PATCH 08/32] cashing using a circuit hash --- src/tequila/simulators/simulator_spex.py | 39 ++++++++++++++++++++++-- tests/test_spex_simulator.py | 25 ++++++++++----- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 794e5a80..e4192e14 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -6,6 +6,7 @@ from tequila import BitNumbering from tequila.circuit import compile_circuit +import hashlib import numpy import spex_tequila @@ -26,7 +27,16 @@ def extract_pauli_dict(ps): raise TequilaSpexException("Rotation gate generator with multiple PauliStrings is not supported.") else: raise TequilaSpexException(f"Unexpected generator type: {type(ps)}") - + +def circuit_hash(abstract_circuit): + sha = hashlib.md5() + if abstract_circuit is None: + return None + for g in abstract_circuit.gates: + gate_str = f"{type(g).__name__}:{g.name}:{g.target}:{g.control}:{g.generator}\n" + sha.update(gate_str.encode('utf-8')) + return sha.hexdigest() + class BackendCircuitSpex(BackendCircuit): compiler_arguments = { "multitarget": True, @@ -51,8 +61,31 @@ class BackendCircuitSpex(BackendCircuit): } + def __init__(self, abstract_circuit=None, variables=None, *args, **kwargs): + self._cached_circuit_hash = None + self._cached_circuit = [] + + super().__init__(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs) + + def initialize_circuit(self, *args, **kwargs): return [] + + + def create_circuit(self, abstract_circuit=None, variables=None, *args, **kwargs): + if abstract_circuit is None: + abstract_circuit = self.abstract_circuit + + new_hash = circuit_hash(abstract_circuit) + + if (new_hash is not None) and (new_hash == self._cached_circuit_hash): + return self._cached_circuit + + circuit = super().create_circuit(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs) + + self._cached_circuit_key = abstract_circuit + self._cached_circuit = circuit + return circuit def add_basic_gate(self, gate, circuit, *args, **kwargs): @@ -158,7 +191,7 @@ def initialize_hamiltonian(self, hamiltonians): # Construct Pauli string like "X(0)Y(1)" pauli_map = dict(ps.items()) term = spex_tequila.ExpPauliTerm() - term.pauli_map = pauli_map + term.pauli_map = pauli_map terms.append((term, ps.coeff)) converted.append(terms) return tuple(converted) @@ -196,7 +229,7 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): # Calculate the expectation value for each Hamiltonian results = [] for H_terms in self.H: - val = spex_tequila.expectation_value_parallel(final_state, final_state, H_terms) + val = spex_tequila.expectation_value_parallel(final_state, final_state, H_terms, num_threads=-1) results.append(val.real) return numpy.array(results) diff --git a/tests/test_spex_simulator.py b/tests/test_spex_simulator.py index b1d857c0..16293558 100644 --- a/tests/test_spex_simulator.py +++ b/tests/test_spex_simulator.py @@ -2,7 +2,7 @@ import numpy as np import time from tequila.hamiltonian import PauliString - +""" print("\nTest: 1") U = tq.gates.ExpPauli(paulistring=PauliString({0: "X", 1: "Z"}), angle=np.pi / 2) H = tq.QubitHamiltonian("Z(0)") @@ -59,10 +59,10 @@ print("qulacs-U:", tq.simulate(U, backend='qulacs')) print("spex:", tq.simulate(E, backend='spex')) print("qulacs:", tq.simulate(E, backend='qulacs')) - +""" print("\nTest: 5") -n = 2 # <--- Variabel, qubits sind am Ende 4n +n = 9 # <--- Variabel, qubits sind am Ende 4n R = 1.5 geom = "" @@ -73,20 +73,29 @@ # --> pip install pyscf <-- mol = tq.Molecule(geometry=geom, basis_set="sto-3g") -U = mol.make_ansatz(name="SPA", edges=edges) +U = mol.make_ansatz(name="HCB-SPA", edges=edges) # SPA -> HCB-SPA U = U.map_variables({k:1.0 for k in U.extract_variables()}) -H = mol.make_hamiltonian() +H = mol.make_hardcore_boson_hamiltonian() # make_hamiltonian -> make_hardcore_boson_hamiltonian + E = tq.ExpectationValue(H=H, U=U) -print(U) -print("spex-U:", tq.simulate(U, backend='spex')) -print("qulacs-U:", tq.simulate(U, backend='qulacs')) + +#print(U) +#print("spex-U:", tq.simulate(U, backend='spex')) +#print("qulacs-U:", tq.simulate(U, backend='qulacs')) +time_start = time.time() print("spex:", tq.simulate(E, backend='spex')) +time_stop = time.time() +print("spex time:", time_stop - time_start, "\n") + +time_start = time.time() print("qulacs:", tq.simulate(E, backend='qulacs')) +time_stop = time.time() +print("qulacs time:", time_stop - time_start) \ No newline at end of file From c310e0f0667d7eaa0d645b57dc8ffdd07eb42b5c Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Sun, 19 Jan 2025 12:31:14 +0100 Subject: [PATCH 09/32] using SPEX and OMP_NUM_THREADS for parallelisation if available --- src/tequila/simulators/simulator_spex.py | 11 +++++++++++ tests/test_spex_simulator.py | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index e4192e14..b112dcdc 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -8,6 +8,7 @@ import hashlib import numpy +import os import spex_tequila numbering = BitNumbering.LSB @@ -226,6 +227,16 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): final_state = spex_tequila.apply_U(self.U.circuit, state) + num_threads = kwargs.get("num_threads", None) + if num_threads is None: + if "SPEX_NUM_THREADS" in os.environ: + num_threads = int(os.environ["SPEX_NUM_THREADS"]) + elif "OMP_NUM_THREADS" in os.environ: + num_threads = int(os.environ["OMP_NUM_THREADS"]) + else: + """default: uses all availabel threads""" + num_threads = -1 + # Calculate the expectation value for each Hamiltonian results = [] for H_terms in self.H: diff --git a/tests/test_spex_simulator.py b/tests/test_spex_simulator.py index 16293558..6f922ad9 100644 --- a/tests/test_spex_simulator.py +++ b/tests/test_spex_simulator.py @@ -62,7 +62,7 @@ """ print("\nTest: 5") -n = 9 # <--- Variabel, qubits sind am Ende 4n +n = 8 # <--- Variabel, qubits sind am Ende 4n R = 1.5 geom = "" @@ -91,7 +91,7 @@ #print("spex-U:", tq.simulate(U, backend='spex')) #print("qulacs-U:", tq.simulate(U, backend='qulacs')) time_start = time.time() -print("spex:", tq.simulate(E, backend='spex')) +print("spex:", tq.simulate(E, backend='spex', num_threads = -1)) time_stop = time.time() print("spex time:", time_stop - time_start, "\n") From c712cd8a44ebdb7659d9d6e482cdd076dc8f9295 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Mon, 20 Jan 2025 20:34:41 +0100 Subject: [PATCH 10/32] using a threshold to eliminate elements with negligible amplitudes --- src/tequila/simulators/simulator_spex.py | 74 ++++++++++++++++++++---- tests/test_spex_simulator.py | 4 +- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index b112dcdc..c923cb91 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -62,10 +62,21 @@ class BackendCircuitSpex(BackendCircuit): } - def __init__(self, abstract_circuit=None, variables=None, *args, **kwargs): + def __init__(self, + abstract_circuit=None, + variables=None, + num_threads=-1, + amplitude_threshold=1e-14, + angle_threshold=1e-14, + *args, **kwargs): + self._cached_circuit_hash = None self._cached_circuit = [] + self.num_threads = num_threads + self.amplitude_threshold = amplitude_threshold + self.angle_threshold = angle_threshold + super().__init__(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs) @@ -86,26 +97,37 @@ def create_circuit(self, abstract_circuit=None, variables=None, *args, **kwargs) self._cached_circuit_key = abstract_circuit self._cached_circuit = circuit + self._cached_circuit_hash = new_hash return circuit def add_basic_gate(self, gate, circuit, *args, **kwargs): exp_term = spex_tequila.ExpPauliTerm() if isinstance(gate, ExponentialPauliGateImpl): + if abs(gate.parameter) < self.angle_threshold: + print("used") + return exp_term.pauli_map = extract_pauli_dict(gate.paulistring) exp_term.angle = gate.parameter circuit.append(exp_term) elif isinstance(gate, RotationGateImpl): + if abs(gate.parameter) < self.angle_threshold: + print("used") + return exp_term.pauli_map = extract_pauli_dict(gate.generator) exp_term.angle = gate.parameter circuit.append(exp_term) elif isinstance(gate, QGateImpl): for ps in gate.make_generator(include_controls=True).paulistrings: + angle = numpy.pi * ps.coeff + if abs(angle) < self.angle_threshold: + print("used") + continue exp_term = spex_tequila.ExpPauliTerm() exp_term.pauli_map = dict(ps.items()) - exp_term.angle = numpy.pi * ps.coeff + exp_term.angle = angle circuit.append(exp_term) else: @@ -113,20 +135,30 @@ def add_basic_gate(self, gate, circuit, *args, **kwargs): "All gates should be compiled to exponential pauli or rotation gates.") + def add_parametrized_gate(self, gate, circuit, *args, **kwargs): exp_term = spex_tequila.ExpPauliTerm() if isinstance(gate, ExponentialPauliGateImpl): + if abs(gate.parameter) < self.angle_threshold: + print("used") + return exp_term.pauli_map = extract_pauli_dict(gate.paulistring) exp_term.angle = gate.parameter circuit.append(exp_term) elif isinstance(gate, RotationGateImpl): + if abs(gate.parameter) < self.angle_threshold: + print("used") + return exp_term.pauli_map = extract_pauli_dict(gate.generator) exp_term.angle = gate.parameter circuit.append(exp_term) elif isinstance(gate, QGateImpl): for ps in gate.make_generator(include_controls=True).paulistrings: + if abs(gate.parameter) < self.angle_threshold: + print("used") + continue exp_term = spex_tequila.ExpPauliTerm() exp_term.pauli_map = dict(ps.items()) exp_term.angle = gate.parameter @@ -161,6 +193,10 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF final_state = spex_tequila.apply_U(self.circuit, state) + for basis, amplitude in list(final_state.items()): + if abs(amplitude) < self.amplitude_threshold: + del final_state[basis] + wfn = QubitWaveFunction(n_qubits=n_qubits, numbering=numbering) for state, amplitude in final_state.items(): wfn[state] = amplitude @@ -171,9 +207,24 @@ class BackendExpectationValueSpex(BackendExpectationValue): """ Backend for computing expectation values using the spex_tequila C++ module. """ - BackendCircuitType = BackendCircuitSpex + def __init__(self, *args, + num_threads=-1, + amplitude_threshold=1e-14, + angle_threshold=1e-14, + **kwargs): + super().__init__(*args, **kwargs) + + self.num_threads = num_threads + self.amplitude_threshold = amplitude_threshold + self.angle_threshold = angle_threshold + + if isinstance(self.U, BackendCircuitSpex): + self.U.num_threads = num_threads + self.U.amplitude_threshold = amplitude_threshold + self.U.angle_threshold = angle_threshold + def initialize_hamiltonian(self, hamiltonians): """ Initializes the Hamiltonian terms for the simulation. @@ -227,15 +278,14 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): final_state = spex_tequila.apply_U(self.U.circuit, state) - num_threads = kwargs.get("num_threads", None) - if num_threads is None: - if "SPEX_NUM_THREADS" in os.environ: - num_threads = int(os.environ["SPEX_NUM_THREADS"]) - elif "OMP_NUM_THREADS" in os.environ: - num_threads = int(os.environ["OMP_NUM_THREADS"]) - else: - """default: uses all availabel threads""" - num_threads = -1 + for basis, amplitude in list(final_state.items()): + if abs(amplitude) < self.amplitude_threshold: + del final_state[basis] + + if "SPEX_NUM_THREADS" in os.environ: + self.num_threads = int(os.environ["SPEX_NUM_THREADS"]) + elif "OMP_NUM_THREADS" in os.environ: + self.num_threads = int(os.environ["OMP_NUM_THREADS"]) # Calculate the expectation value for each Hamiltonian results = [] diff --git a/tests/test_spex_simulator.py b/tests/test_spex_simulator.py index 6f922ad9..db9337bf 100644 --- a/tests/test_spex_simulator.py +++ b/tests/test_spex_simulator.py @@ -62,7 +62,7 @@ """ print("\nTest: 5") -n = 8 # <--- Variabel, qubits sind am Ende 4n +n = 10 # <--- Variabel, qubits sind am Ende 4n R = 1.5 geom = "" @@ -91,7 +91,7 @@ #print("spex-U:", tq.simulate(U, backend='spex')) #print("qulacs-U:", tq.simulate(U, backend='qulacs')) time_start = time.time() -print("spex:", tq.simulate(E, backend='spex', num_threads = -1)) +print("spex:", tq.simulate(E, backend='spex', num_threads=-1)) time_stop = time.time() print("spex time:", time_stop - time_start, "\n") From 04108b79903008e0c9e021a1470e236897872b54 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Thu, 23 Jan 2025 13:11:17 +0100 Subject: [PATCH 11/32] disabling thresholds by setting them None --- src/tequila/simulators/simulator_spex.py | 31 +++++++++++------------- tests/test_spex_simulator.py | 2 +- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index c923cb91..1730b185 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -104,16 +104,14 @@ def create_circuit(self, abstract_circuit=None, variables=None, *args, **kwargs) def add_basic_gate(self, gate, circuit, *args, **kwargs): exp_term = spex_tequila.ExpPauliTerm() if isinstance(gate, ExponentialPauliGateImpl): - if abs(gate.parameter) < self.angle_threshold: - print("used") + if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: return exp_term.pauli_map = extract_pauli_dict(gate.paulistring) exp_term.angle = gate.parameter circuit.append(exp_term) elif isinstance(gate, RotationGateImpl): - if abs(gate.parameter) < self.angle_threshold: - print("used") + if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: return exp_term.pauli_map = extract_pauli_dict(gate.generator) exp_term.angle = gate.parameter @@ -122,8 +120,7 @@ def add_basic_gate(self, gate, circuit, *args, **kwargs): elif isinstance(gate, QGateImpl): for ps in gate.make_generator(include_controls=True).paulistrings: angle = numpy.pi * ps.coeff - if abs(angle) < self.angle_threshold: - print("used") + if self.angle_threshold != None and abs(angle) < self.angle_threshold: continue exp_term = spex_tequila.ExpPauliTerm() exp_term.pauli_map = dict(ps.items()) @@ -139,16 +136,14 @@ def add_basic_gate(self, gate, circuit, *args, **kwargs): def add_parametrized_gate(self, gate, circuit, *args, **kwargs): exp_term = spex_tequila.ExpPauliTerm() if isinstance(gate, ExponentialPauliGateImpl): - if abs(gate.parameter) < self.angle_threshold: - print("used") + if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: return exp_term.pauli_map = extract_pauli_dict(gate.paulistring) exp_term.angle = gate.parameter circuit.append(exp_term) elif isinstance(gate, RotationGateImpl): - if abs(gate.parameter) < self.angle_threshold: - print("used") + if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: return exp_term.pauli_map = extract_pauli_dict(gate.generator) exp_term.angle = gate.parameter @@ -156,7 +151,7 @@ def add_parametrized_gate(self, gate, circuit, *args, **kwargs): elif isinstance(gate, QGateImpl): for ps in gate.make_generator(include_controls=True).paulistrings: - if abs(gate.parameter) < self.angle_threshold: + if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: print("used") continue exp_term = spex_tequila.ExpPauliTerm() @@ -193,9 +188,10 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF final_state = spex_tequila.apply_U(self.circuit, state) - for basis, amplitude in list(final_state.items()): - if abs(amplitude) < self.amplitude_threshold: - del final_state[basis] + if self.amplitude_threshold != None: + for basis, amplitude in list(final_state.items()): + if abs(amplitude) < self.amplitude_threshold: + del final_state[basis] wfn = QubitWaveFunction(n_qubits=n_qubits, numbering=numbering) for state, amplitude in final_state.items(): @@ -278,9 +274,10 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): final_state = spex_tequila.apply_U(self.U.circuit, state) - for basis, amplitude in list(final_state.items()): - if abs(amplitude) < self.amplitude_threshold: - del final_state[basis] + if self.amplitude_threshold != None: + for basis, amplitude in list(final_state.items()): + if abs(amplitude) < self.amplitude_threshold: + del final_state[basis] if "SPEX_NUM_THREADS" in os.environ: self.num_threads = int(os.environ["SPEX_NUM_THREADS"]) diff --git a/tests/test_spex_simulator.py b/tests/test_spex_simulator.py index db9337bf..5b59931a 100644 --- a/tests/test_spex_simulator.py +++ b/tests/test_spex_simulator.py @@ -91,7 +91,7 @@ #print("spex-U:", tq.simulate(U, backend='spex')) #print("qulacs-U:", tq.simulate(U, backend='qulacs')) time_start = time.time() -print("spex:", tq.simulate(E, backend='spex', num_threads=-1)) +print("spex:", tq.simulate(E, backend='spex')) time_stop = time.time() print("spex time:", time_stop - time_start, "\n") From 1dd3b6131bcd8ff6f968ed33ba2e28b692f6c5bb Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:41:39 +0100 Subject: [PATCH 12/32] removing print --- src/tequila/simulators/simulator_api.py | 1 - src/tequila/simulators/simulator_spex.py | 2 +- tests/test_spex_simulator.py | 15 +++++++-------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/tequila/simulators/simulator_api.py b/src/tequila/simulators/simulator_api.py index 4d36764f..590595ad 100755 --- a/src/tequila/simulators/simulator_api.py +++ b/src/tequila/simulators/simulator_api.py @@ -36,7 +36,6 @@ INSTALLED_SIMULATORS["spex"] = BackendTypes(BackendCircuitSpex, BackendExpectationValueSpex) except ImportError: - print("Import Spex Error") HAS_SPEX = False diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 1730b185..0e37bd70 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -67,7 +67,7 @@ def __init__(self, variables=None, num_threads=-1, amplitude_threshold=1e-14, - angle_threshold=1e-14, + angle_threshold=1e-14, *args, **kwargs): self._cached_circuit_hash = None diff --git a/tests/test_spex_simulator.py b/tests/test_spex_simulator.py index 5b59931a..aa5399bb 100644 --- a/tests/test_spex_simulator.py +++ b/tests/test_spex_simulator.py @@ -62,7 +62,7 @@ """ print("\nTest: 5") -n = 10 # <--- Variabel, qubits sind am Ende 4n +n = 12 # <--- Variabel, qubits sind am Ende 4n R = 1.5 geom = "" @@ -72,7 +72,7 @@ edges = [(2*i, 2*i+1) for i in range(n)] # --> pip install pyscf <-- -mol = tq.Molecule(geometry=geom, basis_set="sto-3g") +mol = tq.Molecule(geometry=geom, basis_set="sto-3g", transformation='reordered-Jordan-Wigner') #why does this make a difference U = mol.make_ansatz(name="HCB-SPA", edges=edges) # SPA -> HCB-SPA @@ -86,16 +86,15 @@ E = tq.ExpectationValue(H=H, U=U) - #print(U) #print("spex-U:", tq.simulate(U, backend='spex')) #print("qulacs-U:", tq.simulate(U, backend='qulacs')) time_start = time.time() -print("spex:", tq.simulate(E, backend='spex')) +print("spex:", tq.simulate(E, backend='spex', num_threads=6)) time_stop = time.time() print("spex time:", time_stop - time_start, "\n") -time_start = time.time() -print("qulacs:", tq.simulate(E, backend='qulacs')) -time_stop = time.time() -print("qulacs time:", time_stop - time_start) \ No newline at end of file +#time_start = time.time() +#print("qulacs:", tq.simulate(E, backend='qulacs')) +#time_stop = time.time() +#print("qulacs time:", time_stop - time_start) \ No newline at end of file From 4f88e4e24c556f2bc61af786ade1cb7f424a0fb2 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Sat, 1 Feb 2025 03:31:47 +0100 Subject: [PATCH 13/32] compress_qubit_indicies and moving thresholding to c++ --- spex_qulacs_comparison.csv | 10 ++ src/tequila/simulators/simulator_spex.py | 185 +++++++++++++++-------- tests/test_spex_simulator.py | 90 ++++++++++- 3 files changed, 211 insertions(+), 74 deletions(-) create mode 100644 spex_qulacs_comparison.csv diff --git a/spex_qulacs_comparison.csv b/spex_qulacs_comparison.csv new file mode 100644 index 00000000..3f8f267f --- /dev/null +++ b/spex_qulacs_comparison.csv @@ -0,0 +1,10 @@ +n,total_measurements,E_spex,time_spex,E_qulacs,time_qulacs +1,6,-0.5990305727422252,0.0449521541595459,-0.5990305727422253,0.0007569789886474609 +2,23,-0.9873173630884557,0.05591607093811035,-0.9873173630884562,0.0013020038604736328 +3,52,-1.4076621306451274,0.05423903465270996,-1.4076621306451282,0.0019998550415039062 +4,93,-1.7718199057882529,0.06614303588867188,-1.7718199057882538,0.01813483238220215 +5,146,-2.2123580028460643,0.052806854248046875,-2.212358002846065,0.00481104850769043 +6,211,-2.5762135294669544,0.06645083427429199,-2.576213529466954,0.009060144424438477 +7,288,-3.0390338836248216,0.07832908630371094,-3.0390338836248194,0.037663936614990234 +8,377,-3.4039762325717065,0.08845996856689453,-3.4039762325717104,0.12373208999633789 +9,478,-3.8692889377607145,0.0891718864440918,-3.8692889377607136,0.405670166015625 diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 0e37bd70..603ae354 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -1,35 +1,43 @@ from tequila.simulators.simulator_base import BackendExpectationValue, BackendCircuit from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction from tequila.utils import TequilaException -from tequila.hamiltonian import paulis, PauliString +from tequila.hamiltonian import PauliString from tequila.circuit._gates_impl import ExponentialPauliGateImpl, QGateImpl, RotationGateImpl, QubitHamiltonian from tequila import BitNumbering -from tequila.circuit import compile_circuit + import hashlib import numpy import os import spex_tequila +import gc numbering = BitNumbering.LSB class TequilaSpexException(TequilaException): + """Custom exception for SPEX simulator errors""" pass def extract_pauli_dict(ps): - # Extract a dict {qubit: 'X'/'Y'/'Z'} from ps. - # ps is PauliString or QubitHamiltonian - if isinstance(ps, PauliString): - return dict(ps.items()) - elif isinstance(ps, QubitHamiltonian): - if len(ps.paulistrings) == 1: - return dict(ps.paulistrings[0].items()) - else: - raise TequilaSpexException("Rotation gate generator with multiple PauliStrings is not supported.") - else: - raise TequilaSpexException(f"Unexpected generator type: {type(ps)}") + """ + Extract qubit:operator mapping from PauliString/QubitHamiltonian + Args: + ps: PauliString or single-term QubitHamiltonian + Returns: + dict: {qubit: 'X'/'Y'/'Z'} + """ + + if isinstance(ps, PauliString): + return dict(ps.items()) + if isinstance(ps, QubitHamiltonian) and len(ps.paulistrings) == 1: + return dict(ps.paulistrings[0].items()) + raise TequilaSpexException("Unsupported generator type") def circuit_hash(abstract_circuit): + """ + Create MD5 hash for circuit caching + Uses gate types, targets, controls and generators for uniqueness + """ sha = hashlib.md5() if abstract_circuit is None: return None @@ -39,6 +47,9 @@ def circuit_hash(abstract_circuit): return sha.hexdigest() class BackendCircuitSpex(BackendCircuit): + """SPEX circuit implementation using sparse state representation""" + + # Circuit compilation configuration compiler_arguments = { "multitarget": True, "multicontrol": True, @@ -61,47 +72,93 @@ class BackendCircuitSpex(BackendCircuit): "ch_gate": True } - def __init__(self, abstract_circuit=None, variables=None, num_threads=-1, amplitude_threshold=1e-14, angle_threshold=1e-14, + compress_qubits=True, *args, **kwargs): - self._cached_circuit_hash = None - self._cached_circuit = [] + # Circuit chaching + self.cached_circuit_hash = None + self.cached_circuit = [] + # Performance parameters self.num_threads = num_threads self.amplitude_threshold = amplitude_threshold self.angle_threshold = angle_threshold + # State compression + self.n_qubits_compressed = None + self.hamiltonians = None + super().__init__(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs) + @property + def n_qubits(self): + """Get number of qubits after compression (if enabled)""" + if self.compress_qubits and (self.n_qubits_compressed is not None): + return self.n_qubits_compressed + return super().n_qubits def initialize_circuit(self, *args, **kwargs): return [] - def create_circuit(self, abstract_circuit=None, variables=None, *args, **kwargs): + """Compile circuit with caching using MD5 hash""" if abstract_circuit is None: abstract_circuit = self.abstract_circuit new_hash = circuit_hash(abstract_circuit) - if (new_hash is not None) and (new_hash == self._cached_circuit_hash): - return self._cached_circuit + if (new_hash is not None) and (new_hash == self.cached_circuit_hash): + return self.cached_circuit circuit = super().create_circuit(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs) - self._cached_circuit_key = abstract_circuit - self._cached_circuit = circuit - self._cached_circuit_hash = new_hash + self.cached_circuit_key = abstract_circuit + self.cached_circuit = circuit + self.cached_circuit_hash = new_hash + return circuit + + def compress_qubit_indices(self): + """ + Optimize qubit indices by mapping used qubits to contiguous range + Reduces memory usage by eliminating unused qubit dimensions + """ + if not self.compress_qubits or not self.cached_circuit: + return + + # Collect all qubits used in circuit and Hamiltonians + used_qubits = set() + for term in self.cached_circuit: + used_qubits.update(term.pauli_map.keys()) + for ham in self.hamiltonians: + for term, _ in ham: + used_qubits.update(term.pauli_map.keys()) + + if not used_qubits: + self._n_qubits_compressed = 0 + return + + # Create qubit mapping and remap all terms + qubit_map = {old: new for new, old in enumerate(sorted(used_qubits))} + + for term in self.cached_circuit: + term.pauli_map = {qubit_map[old]: op for old, op in term.pauli_map.items()} + + for ham in self.hamiltonians: + for term, _ in ham: + term.pauli_map = {qubit_map[old]: op for old, op in term.pauli_map.items()} + + self._n_qubits_compressed = len(used_qubits) def add_basic_gate(self, gate, circuit, *args, **kwargs): + """Convert Tequila gates to SPEX exponential Pauli terms""" exp_term = spex_tequila.ExpPauliTerm() if isinstance(gate, ExponentialPauliGateImpl): if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: @@ -118,6 +175,7 @@ def add_basic_gate(self, gate, circuit, *args, **kwargs): circuit.append(exp_term) elif isinstance(gate, QGateImpl): + # Convert standard gates to Pauli rotations for ps in gate.make_generator(include_controls=True).paulistrings: angle = numpy.pi * ps.coeff if self.angle_threshold != None and abs(angle) < self.angle_threshold: @@ -134,6 +192,7 @@ def add_basic_gate(self, gate, circuit, *args, **kwargs): def add_parametrized_gate(self, gate, circuit, *args, **kwargs): + """Convert Tequila parametrized gates to SPEX exponential Pauli terms""" exp_term = spex_tequila.ExpPauliTerm() if isinstance(gate, ExponentialPauliGateImpl): if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: @@ -166,49 +225,46 @@ def add_parametrized_gate(self, gate, circuit, *args, **kwargs): def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveFunction: """ - Simulates the circuit and returns the final qubit state. - + Simulate circuit and return final state Args: - variables: Variables to adjust the circuit (not used for fixed gates). - initial_state: The initial state of the qubit. - + initial_state: Starting state (int or QubitWaveFunction) Returns: - QubitWaveFunction: The final state after applying the circuit. + QubitWaveFunction: Sparse state representation """ - n_qubits = self.n_qubits - # Prepare the initial state + + # Initialize state if isinstance(initial_state, int): if initial_state == 0: - state = spex_tequila.initialize_zero_state(n_qubits) + state = spex_tequila.initialize_zero_state(self.n_qubits) else: state = {initial_state: 1.0 + 0j} else: # initial_state is already a QubitWaveFunction state = initial_state.to_dictionary() - final_state = spex_tequila.apply_U(self.circuit, state) - - if self.amplitude_threshold != None: - for basis, amplitude in list(final_state.items()): - if abs(amplitude) < self.amplitude_threshold: - del final_state[basis] + # Apply circuit with amplitude thresholding, -1.0 disables threshold in spex_tequila + threshold = self.amplitude_threshold if self.amplitude_threshold is not None else -1.0 + final_state = spex_tequila.apply_U(self.circuit, state, threshold) - wfn = QubitWaveFunction(n_qubits=n_qubits, numbering=numbering) + wfn = QubitWaveFunction(n_qubits=self.n_qubits, numbering=numbering) for state, amplitude in final_state.items(): wfn[state] = amplitude + + del final_state + gc.collect() + return wfn class BackendExpectationValueSpex(BackendExpectationValue): - """ - Backend for computing expectation values using the spex_tequila C++ module. - """ + """SPEX expectation value calculator using sparse simulations""" BackendCircuitType = BackendCircuitSpex def __init__(self, *args, num_threads=-1, amplitude_threshold=1e-14, angle_threshold=1e-14, + compress_qubits=True, **kwargs): super().__init__(*args, **kwargs) @@ -216,18 +272,18 @@ def __init__(self, *args, self.amplitude_threshold = amplitude_threshold self.angle_threshold = angle_threshold + # Configure circuit parameters if isinstance(self.U, BackendCircuitSpex): self.U.num_threads = num_threads self.U.amplitude_threshold = amplitude_threshold self.U.angle_threshold = angle_threshold + self.U.compress_qubits = compress_qubits def initialize_hamiltonian(self, hamiltonians): """ Initializes the Hamiltonian terms for the simulation. - Args: hamiltonians: A list of Hamiltonian objects. - Returns: tuple: A converted list of (pauli_string, coefficient) tuples. """ @@ -242,42 +298,40 @@ def initialize_hamiltonian(self, hamiltonians): term.pauli_map = pauli_map terms.append((term, ps.coeff)) converted.append(terms) + + if isinstance(self.U, BackendCircuitSpex): + self.U.hamiltonians = converted + return tuple(converted) def simulate(self, variables, initial_state=0, *args, **kwargs): """ - Computes the expectation value by simulating the circuit U and evaluating ⟨ψ|H|ψ⟩. - - Args: - variables: Variables to adjust the circuit (not used for fixed gates). - initial_state: The initial state of the qubit. - + Calculate expectation value through sparse simulation Returns: - numpy.ndarray: The computed expectation values for the Hamiltonian terms. + numpy.ndarray: Expectation values for each Hamiltonian term """ - # variables as dict, variable map for var to gates - + # Prepare simulation self.update_variables(variables) - n_qubits = self.U.n_qubits + if self.U.compress_qubits: + self.U.compress_qubit_indices() # Prepare the initial state if isinstance(initial_state, int): if initial_state == 0: - state = spex_tequila.initialize_zero_state(n_qubits) + state = spex_tequila.initialize_zero_state(self.n_qubits) else: state = {initial_state: 1.0 + 0j} else: # initial_state is a QubitWaveFunction state = initial_state.to_dictionary() - final_state = spex_tequila.apply_U(self.U.circuit, state) + self.U.circuit = [t for t in self.U.circuit if abs(t.angle) >= self.U.angle_threshold] - if self.amplitude_threshold != None: - for basis, amplitude in list(final_state.items()): - if abs(amplitude) < self.amplitude_threshold: - del final_state[basis] + threshold = self.amplitude_threshold if self.amplitude_threshold is not None else -1.0 + final_state = spex_tequila.apply_U(self.U.circuit, state, threshold) + del state if "SPEX_NUM_THREADS" in os.environ: self.num_threads = int(os.environ["SPEX_NUM_THREADS"]) @@ -289,11 +343,8 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): for H_terms in self.H: val = spex_tequila.expectation_value_parallel(final_state, final_state, H_terms, num_threads=-1) results.append(val.real) - return numpy.array(results) - - - def sample(self, variables, samples, initial_state=0, *args, **kwargs): - return super().sample(variables=variables, samples=samples, initial_state=initial_state, *args, **kwargs) - - def sample_paulistring(self, samples: int, paulistring, variables, initial_state=0, *args, **kwargs): - return super().sample_paulistring(samples, paulistring, variables, initial_state=initial_state, *args, **kwargs) \ No newline at end of file + + del final_state + gc.collect() + + return numpy.array(results) \ No newline at end of file diff --git a/tests/test_spex_simulator.py b/tests/test_spex_simulator.py index aa5399bb..e3237325 100644 --- a/tests/test_spex_simulator.py +++ b/tests/test_spex_simulator.py @@ -1,6 +1,8 @@ import tequila as tq import numpy as np import time +import os +import csv from tequila.hamiltonian import PauliString """ print("\nTest: 1") @@ -59,7 +61,6 @@ print("qulacs-U:", tq.simulate(U, backend='qulacs')) print("spex:", tq.simulate(E, backend='spex')) print("qulacs:", tq.simulate(E, backend='qulacs')) -""" print("\nTest: 5") n = 12 # <--- Variabel, qubits sind am Ende 4n @@ -72,7 +73,7 @@ edges = [(2*i, 2*i+1) for i in range(n)] # --> pip install pyscf <-- -mol = tq.Molecule(geometry=geom, basis_set="sto-3g", transformation='reordered-Jordan-Wigner') #why does this make a difference +mol = tq.Molecule(geometry=geom, basis_set="sto-3g")#, transformation='reordered-Jordan-Wigner') #why does this make a difference U = mol.make_ansatz(name="HCB-SPA", edges=edges) # SPA -> HCB-SPA @@ -90,11 +91,86 @@ #print("spex-U:", tq.simulate(U, backend='spex')) #print("qulacs-U:", tq.simulate(U, backend='qulacs')) time_start = time.time() -print("spex:", tq.simulate(E, backend='spex', num_threads=6)) +print("spex:", tq.simulate(E, backend='spex', num_threads=1)) time_stop = time.time() print("spex time:", time_stop - time_start, "\n") -#time_start = time.time() -#print("qulacs:", tq.simulate(E, backend='qulacs')) -#time_stop = time.time() -#print("qulacs time:", time_stop - time_start) \ No newline at end of file +time_start = time.time() +print("qulacs:", tq.simulate(E, backend='qulacs')) +time_stop = time.time() +print("qulacs time:", time_stop - time_start) +""" + +os.environ["OMP_NUM_THREADS"] = "6" + +results = [] + +for n in range(1, 10): + # 1) Geometrie aufbauen + geom = "" + for k in range(2*n): + geom += f"h 0.0 0.0 {1.5*k}\n" + + # 2) Molekül + Hamiltonian + mol = tq.Molecule(geometry=geom, basis_set="sto-3g") + H = mol.make_hardcore_boson_hamiltonian() + + # 3) Ansatz U + edges = [(2*i, 2*i+1) for i in range(n)] + U = mol.make_ansatz(name="HCB-SPA", edges=edges) + # Alle im Ansatz auftretenden Variablen auf 1.0 setzen + U = U.map_variables({var: 1.0 for var in U.extract_variables()}) + + # 4) Erwartungswert-Objekt + E_obj = tq.ExpectationValue(H=H, U=U) + + # -- SPEX-Berechnung -- + start_spex = time.time() + E_spex = tq.simulate(E_obj, backend='spex', num_threads=6) + end_spex = time.time() + time_spex = end_spex - start_spex + + # -- Qulacs-Berechnung -- + if n <= 13: + start_qulacs = time.time() + E_qulacs = tq.simulate(E_obj, backend='qulacs') + end_qulacs = time.time() + time_qulacs = end_qulacs - start_qulacs + + total_measurements = E_obj.count_measurements() + + # Speichern der Daten + results.append({ + 'n': n, + 'total_measurements' : total_measurements, + 'E_spex': E_spex, + 'time_spex': time_spex, + 'E_qulacs': E_qulacs, + 'time_qulacs': time_qulacs + }) + + if E_qulacs is not None: + print(f"n={n:2d} | total_measurements={total_measurements} | " + f"E_spex={E_spex:.6f} (dt={time_spex:.2f}s) | " + f"E_qulacs={E_qulacs:.6f} (dt={time_qulacs:.2f}s)") + else: + print(f"n={n:2d} | total_measurements={total_measurements} | " + f"E_spex={E_spex:.6f} (dt={time_spex:.2f}s) | " + f"E_qulacs=--- (dt=---) (für n>13 nicht berechnet)") + + with open("spex_qulacs_comparison.csv", mode="w", newline="") as f: + writer = csv.writer(f) + # Kopfzeile + writer.writerow(["n", "total_measurements", "E_spex", "time_spex", "E_qulacs", "time_qulacs"]) + # Datenzeilen + for entry in results: + writer.writerow([ + entry["n"], + entry["total_measurements"], + entry["E_spex"], + entry["time_spex"], + entry["E_qulacs"] if entry["E_qulacs"] is not None else "NA", + entry["time_qulacs"] if entry["time_qulacs"] is not None else "NA" + ]) + + E_qulacs = None \ No newline at end of file From c8cd8fbfb0b00b6b3b0dc30f400a11f304a7b0ec Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Sat, 1 Feb 2025 16:48:47 +0100 Subject: [PATCH 14/32] deleting test_spex_simulator --- spex_qulacs_comparison.csv | 10 -- src/tequila/simulators/simulator_spex.py | 10 +- tests/test_spex_simulator.py | 176 ----------------------- 3 files changed, 5 insertions(+), 191 deletions(-) delete mode 100644 spex_qulacs_comparison.csv delete mode 100644 tests/test_spex_simulator.py diff --git a/spex_qulacs_comparison.csv b/spex_qulacs_comparison.csv deleted file mode 100644 index 3f8f267f..00000000 --- a/spex_qulacs_comparison.csv +++ /dev/null @@ -1,10 +0,0 @@ -n,total_measurements,E_spex,time_spex,E_qulacs,time_qulacs -1,6,-0.5990305727422252,0.0449521541595459,-0.5990305727422253,0.0007569789886474609 -2,23,-0.9873173630884557,0.05591607093811035,-0.9873173630884562,0.0013020038604736328 -3,52,-1.4076621306451274,0.05423903465270996,-1.4076621306451282,0.0019998550415039062 -4,93,-1.7718199057882529,0.06614303588867188,-1.7718199057882538,0.01813483238220215 -5,146,-2.2123580028460643,0.052806854248046875,-2.212358002846065,0.00481104850769043 -6,211,-2.5762135294669544,0.06645083427429199,-2.576213529466954,0.009060144424438477 -7,288,-3.0390338836248216,0.07832908630371094,-3.0390338836248194,0.037663936614990234 -8,377,-3.4039762325717065,0.08845996856689453,-3.4039762325717104,0.12373208999633789 -9,478,-3.8692889377607145,0.0891718864440918,-3.8692889377607136,0.405670166015625 diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 603ae354..8cdcc551 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -118,7 +118,6 @@ def create_circuit(self, abstract_circuit=None, variables=None, *args, **kwargs) circuit = super().create_circuit(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs) - self.cached_circuit_key = abstract_circuit self.cached_circuit = circuit self.cached_circuit_hash = new_hash @@ -149,10 +148,11 @@ def compress_qubit_indices(self): for term in self.cached_circuit: term.pauli_map = {qubit_map[old]: op for old, op in term.pauli_map.items()} - - for ham in self.hamiltonians: - for term, _ in ham: - term.pauli_map = {qubit_map[old]: op for old, op in term.pauli_map.items()} + + if self.hamiltonians is not None: + for ham in self.hamiltonians: + for term, _ in ham: + term.pauli_map = {qubit_map[old]: op for old, op in term.pauli_map.items()} self._n_qubits_compressed = len(used_qubits) diff --git a/tests/test_spex_simulator.py b/tests/test_spex_simulator.py deleted file mode 100644 index e3237325..00000000 --- a/tests/test_spex_simulator.py +++ /dev/null @@ -1,176 +0,0 @@ -import tequila as tq -import numpy as np -import time -import os -import csv -from tequila.hamiltonian import PauliString -""" -print("\nTest: 1") -U = tq.gates.ExpPauli(paulistring=PauliString({0: "X", 1: "Z"}), angle=np.pi / 2) -H = tq.QubitHamiltonian("Z(0)") -E = tq.ExpectationValue(U=U, H=H) - -print(U) -print("spex-U:", tq.simulate(U, backend='spex')) -print("qulacs-U:", tq.simulate(U, backend='qulacs')) -print("spex:", tq.simulate(E, backend='spex')) -print("qulacs:", tq.simulate(E, backend='qulacs')) - - -print("\nTest: 2") -H = tq.QubitHamiltonian("Z(0)X(1)") - -U = tq.gates.Rx(angle=np.pi / 4, target=(0,)) -U += tq.gates.Ry(angle=np.pi / 3, target=(1,)) -E = tq.ExpectationValue(U=U, H=H) - -print(U) -print("spex-U:", tq.simulate(U, backend='spex')) -print("qulacs-U:", tq.simulate(U, backend='qulacs')) -print("spex:", tq.simulate(E, backend='spex')) -print("qulacs:", tq.simulate(E, backend='qulacs')) - - -print("\nTest: 3") -H = tq.QubitHamiltonian("Z(0)") - -U = tq.gates.X(target=(0,)) -U += tq.gates.Y(target=(1,)) -E = tq.ExpectationValue(U=U, H=H) - -print(U) -print("spex-U:", tq.simulate(U, backend='spex')) -print("qulacs-U:", tq.simulate(U, backend='qulacs')) -print("spex:", tq.simulate(E, backend='spex')) -print("qulacs:", tq.simulate(E, backend='qulacs')) - - -print("\nTest: 4") -U = ( - tq.gates.Rx(angle=np.pi / 2, target=(0,)) + - tq.gates.Rz(angle=np.pi / 2, target=(1,)) + - tq.gates.Ry(angle=np.pi / 3, target=(2,)) + - tq.gates.ExpPauli(paulistring=PauliString({0: "X", 1: "X", 2: "X"}), angle=np.pi / 4) -) - -H = tq.QubitHamiltonian("X(0)X(1)X(2) + Z(0) + Z(1) + Z(2)") -E = tq.ExpectationValue(U=U, H=H) - -print(U) -print("spex-U:", tq.simulate(U, backend='spex')) -print("qulacs-U:", tq.simulate(U, backend='qulacs')) -print("spex:", tq.simulate(E, backend='spex')) -print("qulacs:", tq.simulate(E, backend='qulacs')) - -print("\nTest: 5") -n = 12 # <--- Variabel, qubits sind am Ende 4n - -R = 1.5 -geom = "" -for k in range(2*n): - geom += "h 0.0 0.0 {}\n".format(R*k) - -edges = [(2*i, 2*i+1) for i in range(n)] - -# --> pip install pyscf <-- -mol = tq.Molecule(geometry=geom, basis_set="sto-3g")#, transformation='reordered-Jordan-Wigner') #why does this make a difference -U = mol.make_ansatz(name="HCB-SPA", edges=edges) - -# SPA -> HCB-SPA - -U = U.map_variables({k:1.0 for k in U.extract_variables()}) - -H = mol.make_hardcore_boson_hamiltonian() - -# make_hamiltonian -> make_hardcore_boson_hamiltonian - - -E = tq.ExpectationValue(H=H, U=U) - -#print(U) -#print("spex-U:", tq.simulate(U, backend='spex')) -#print("qulacs-U:", tq.simulate(U, backend='qulacs')) -time_start = time.time() -print("spex:", tq.simulate(E, backend='spex', num_threads=1)) -time_stop = time.time() -print("spex time:", time_stop - time_start, "\n") - -time_start = time.time() -print("qulacs:", tq.simulate(E, backend='qulacs')) -time_stop = time.time() -print("qulacs time:", time_stop - time_start) -""" - -os.environ["OMP_NUM_THREADS"] = "6" - -results = [] - -for n in range(1, 10): - # 1) Geometrie aufbauen - geom = "" - for k in range(2*n): - geom += f"h 0.0 0.0 {1.5*k}\n" - - # 2) Molekül + Hamiltonian - mol = tq.Molecule(geometry=geom, basis_set="sto-3g") - H = mol.make_hardcore_boson_hamiltonian() - - # 3) Ansatz U - edges = [(2*i, 2*i+1) for i in range(n)] - U = mol.make_ansatz(name="HCB-SPA", edges=edges) - # Alle im Ansatz auftretenden Variablen auf 1.0 setzen - U = U.map_variables({var: 1.0 for var in U.extract_variables()}) - - # 4) Erwartungswert-Objekt - E_obj = tq.ExpectationValue(H=H, U=U) - - # -- SPEX-Berechnung -- - start_spex = time.time() - E_spex = tq.simulate(E_obj, backend='spex', num_threads=6) - end_spex = time.time() - time_spex = end_spex - start_spex - - # -- Qulacs-Berechnung -- - if n <= 13: - start_qulacs = time.time() - E_qulacs = tq.simulate(E_obj, backend='qulacs') - end_qulacs = time.time() - time_qulacs = end_qulacs - start_qulacs - - total_measurements = E_obj.count_measurements() - - # Speichern der Daten - results.append({ - 'n': n, - 'total_measurements' : total_measurements, - 'E_spex': E_spex, - 'time_spex': time_spex, - 'E_qulacs': E_qulacs, - 'time_qulacs': time_qulacs - }) - - if E_qulacs is not None: - print(f"n={n:2d} | total_measurements={total_measurements} | " - f"E_spex={E_spex:.6f} (dt={time_spex:.2f}s) | " - f"E_qulacs={E_qulacs:.6f} (dt={time_qulacs:.2f}s)") - else: - print(f"n={n:2d} | total_measurements={total_measurements} | " - f"E_spex={E_spex:.6f} (dt={time_spex:.2f}s) | " - f"E_qulacs=--- (dt=---) (für n>13 nicht berechnet)") - - with open("spex_qulacs_comparison.csv", mode="w", newline="") as f: - writer = csv.writer(f) - # Kopfzeile - writer.writerow(["n", "total_measurements", "E_spex", "time_spex", "E_qulacs", "time_qulacs"]) - # Datenzeilen - for entry in results: - writer.writerow([ - entry["n"], - entry["total_measurements"], - entry["E_spex"], - entry["time_spex"], - entry["E_qulacs"] if entry["E_qulacs"] is not None else "NA", - entry["time_qulacs"] if entry["time_qulacs"] is not None else "NA" - ]) - - E_qulacs = None \ No newline at end of file From 241f7b4c9ef1b77dcfc7a6f2828e8dff0d7ea7cd Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Sat, 1 Feb 2025 22:35:02 +0100 Subject: [PATCH 15/32] Update ci_backends.yml --- .github/workflows/ci_backends.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/ci_backends.yml b/.github/workflows/ci_backends.yml index 79717dbe..33bcf84e 100644 --- a/.github/workflows/ci_backends.yml +++ b/.github/workflows/ci_backends.yml @@ -28,12 +28,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - if [ $ver -eq 7 ]; then - # myqlm does not work in python3.7 - pip install "cirq" "qiskit>=0.30" "qulacs" "qibo==0.1.1" - elif [ $ver -eq 8 ]; then - pip install "cirq" "qiskit" "qulacs" "qibo==0.1.1" "myqlm" "cirq-google" - fi + pip install "cirq" "qiskit" "qulacs" "spex-tequila pip install -e . - name: Lint with flake8 run: | From 1e5e21c4f3a9bec0062b1d9474a3f03683e17e84 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:28:55 +0100 Subject: [PATCH 16/32] adding missing " in ci_backends.yml --- .github/workflows/ci_backends.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_backends.yml b/.github/workflows/ci_backends.yml index 33bcf84e..2a9f41df 100644 --- a/.github/workflows/ci_backends.yml +++ b/.github/workflows/ci_backends.yml @@ -28,7 +28,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install "cirq" "qiskit" "qulacs" "spex-tequila + pip install "cirq" "qiskit" "qulacs" "spex-tequila" pip install -e . - name: Lint with flake8 run: | From 3cf0a4b8c0635e8fe4906d39c61b40ef8e0c3102 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:46:37 +0100 Subject: [PATCH 17/32] print in simulator_spex.py removed --- src/tequila/simulators/simulator_spex.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 8cdcc551..3940c8c4 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -211,7 +211,6 @@ def add_parametrized_gate(self, gate, circuit, *args, **kwargs): elif isinstance(gate, QGateImpl): for ps in gate.make_generator(include_controls=True).paulistrings: if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: - print("used") continue exp_term = spex_tequila.ExpPauliTerm() exp_term.pauli_map = dict(ps.items()) @@ -347,4 +346,4 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): del final_state gc.collect() - return numpy.array(results) \ No newline at end of file + return numpy.array(results) From bae3adf0bc9badfe48cd0f81a45e1b5fc8ca3076 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:10:44 +0100 Subject: [PATCH 18/32] assign_parameter --- src/tequila/simulators/simulator_spex.py | 27 +++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 3940c8c4..1dbac12b 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -46,6 +46,11 @@ def circuit_hash(abstract_circuit): sha.update(gate_str.encode('utf-8')) return sha.hexdigest() +def reverse_bits(x: int, n_bits: int) -> int: + bin_str = format(x, f'0{n_bits}b') + reversed_str = bin_str[::-1] + return int(reversed_str, 2) + class BackendCircuitSpex(BackendCircuit): """SPEX circuit implementation using sparse state representation""" @@ -91,9 +96,10 @@ def __init__(self, self.angle_threshold = angle_threshold # State compression + self.compress_qubits = compress_qubits self.n_qubits_compressed = None self.hamiltonians = None - + super().__init__(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs) @property @@ -156,6 +162,15 @@ def compress_qubit_indices(self): self._n_qubits_compressed = len(used_qubits) + def assign_parameter(self, param, variables): + if isinstance(param, (int, float, complex)): + return float(param) + if callable(param): + result = param(variables) + return float(result) + + raise TequilaSpexException(f"Can't assign parameter '{param}'.") + def add_basic_gate(self, gate, circuit, *args, **kwargs): """Convert Tequila gates to SPEX exponential Pauli terms""" @@ -195,17 +210,19 @@ def add_parametrized_gate(self, gate, circuit, *args, **kwargs): """Convert Tequila parametrized gates to SPEX exponential Pauli terms""" exp_term = spex_tequila.ExpPauliTerm() if isinstance(gate, ExponentialPauliGateImpl): - if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: + angle = self.assign_parameter(gate.parameter, kwargs.get("variables", {})) + if self.angle_threshold != None and abs(angle) < self.angle_threshold: return exp_term.pauli_map = extract_pauli_dict(gate.paulistring) - exp_term.angle = gate.parameter + exp_term.angle = angle circuit.append(exp_term) elif isinstance(gate, RotationGateImpl): - if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: + angle = self.assign_parameter(gate.parameter, kwargs.get("variables", {})) + if self.angle_threshold != None and abs(angle) < self.angle_threshold: return exp_term.pauli_map = extract_pauli_dict(gate.generator) - exp_term.angle = gate.parameter + exp_term.angle = angle circuit.append(exp_term) elif isinstance(gate, QGateImpl): From e021826876287dee3ebd6111b96ab6e4d705efc0 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:51:52 +0100 Subject: [PATCH 19/32] fix for test_initial_state_from_integer and test_wfn_simple_consistency --- src/tequila/simulators/simulator_spex.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 1dbac12b..d1e06cfb 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -253,7 +253,7 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF if initial_state == 0: state = spex_tequila.initialize_zero_state(self.n_qubits) else: - state = {initial_state: 1.0 + 0j} + state = { reverse_bits(initial_state, self.n_qubits): 1.0 + 0j } else: # initial_state is already a QubitWaveFunction state = initial_state.to_dictionary() @@ -262,14 +262,18 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF threshold = self.amplitude_threshold if self.amplitude_threshold is not None else -1.0 final_state = spex_tequila.apply_U(self.circuit, state, threshold) - wfn = QubitWaveFunction(n_qubits=self.n_qubits, numbering=numbering) + wfn_LSB = QubitWaveFunction(n_qubits=self.n_qubits, numbering=BitNumbering.LSB) for state, amplitude in final_state.items(): - wfn[state] = amplitude + wfn_LSB[state] = amplitude + + wfn_MSB = QubitWaveFunction(n_qubits=self.n_qubits, numbering=BitNumbering.MSB) + for key, amplitude in final_state.items(): + wfn_MSB[reverse_bits(key, self.n_qubits)] = amplitude del final_state gc.collect() - return wfn + return wfn_MSB class BackendExpectationValueSpex(BackendExpectationValue): From 0b5a14d43f31182852babc341e5d720fdf8a1ae6 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Fri, 7 Feb 2025 12:14:26 +0100 Subject: [PATCH 20/32] num_qubits param for spex backend (from LSB to MSB) --- src/tequila/simulators/simulator_spex.py | 45 +++++++++++++++--------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index d1e06cfb..0b9640e6 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -12,7 +12,7 @@ import spex_tequila import gc -numbering = BitNumbering.LSB +numbering = BitNumbering.MSB class TequilaSpexException(TequilaException): """Custom exception for SPEX simulator errors""" @@ -105,9 +105,25 @@ def __init__(self, @property def n_qubits(self): """Get number of qubits after compression (if enabled)""" - if self.compress_qubits and (self.n_qubits_compressed is not None): - return self.n_qubits_compressed - return super().n_qubits + used = set() + if self.cached_circuit: + for term in self.cached_circuit: + used.update(term.pauli_map.keys()) + + if self.abstract_circuit is not None and hasattr(self.abstract_circuit, "gates"): + for gate in self.abstract_circuit.gates: + if hasattr(gate, "target"): + if isinstance(gate.target, (list, tuple)): + used.update(gate.target) + else: + used.add(gate.target) + if hasattr(gate, "control") and gate.control: + if isinstance(gate.control, (list, tuple)): + used.update(gate.control) + else: + used.add(gate.control) + computed = max(used) + 1 if used else 0 + return max(super().n_qubits, computed) def initialize_circuit(self, *args, **kwargs): return [] @@ -146,7 +162,7 @@ def compress_qubit_indices(self): used_qubits.update(term.pauli_map.keys()) if not used_qubits: - self._n_qubits_compressed = 0 + self.n_qubits_compressed = 0 return # Create qubit mapping and remap all terms @@ -160,7 +176,7 @@ def compress_qubit_indices(self): for term, _ in ham: term.pauli_map = {qubit_map[old]: op for old, op in term.pauli_map.items()} - self._n_qubits_compressed = len(used_qubits) + self.n_qubits_compressed = len(used_qubits) def assign_parameter(self, param, variables): if isinstance(param, (int, float, complex)): @@ -253,26 +269,23 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF if initial_state == 0: state = spex_tequila.initialize_zero_state(self.n_qubits) else: - state = { reverse_bits(initial_state, self.n_qubits): 1.0 + 0j } + state = {initial_state: 1.0 + 0j} else: # initial_state is already a QubitWaveFunction state = initial_state.to_dictionary() # Apply circuit with amplitude thresholding, -1.0 disables threshold in spex_tequila threshold = self.amplitude_threshold if self.amplitude_threshold is not None else -1.0 - final_state = spex_tequila.apply_U(self.circuit, state, threshold) - - wfn_LSB = QubitWaveFunction(n_qubits=self.n_qubits, numbering=BitNumbering.LSB) - for state, amplitude in final_state.items(): - wfn_LSB[state] = amplitude + final_state = spex_tequila.apply_U(self.circuit, state, threshold, self.n_qubits) wfn_MSB = QubitWaveFunction(n_qubits=self.n_qubits, numbering=BitNumbering.MSB) - for key, amplitude in final_state.items(): - wfn_MSB[reverse_bits(key, self.n_qubits)] = amplitude + for state, amplitude in final_state.items(): + wfn_MSB[state] = amplitude del final_state gc.collect() + print("wfn_MSB:", wfn_MSB) return wfn_MSB @@ -350,7 +363,7 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): self.U.circuit = [t for t in self.U.circuit if abs(t.angle) >= self.U.angle_threshold] threshold = self.amplitude_threshold if self.amplitude_threshold is not None else -1.0 - final_state = spex_tequila.apply_U(self.U.circuit, state, threshold) + final_state = spex_tequila.apply_U(self.U.circuit, state, threshold, self.n_qubits) del state if "SPEX_NUM_THREADS" in os.environ: @@ -361,7 +374,7 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): # Calculate the expectation value for each Hamiltonian results = [] for H_terms in self.H: - val = spex_tequila.expectation_value_parallel(final_state, final_state, H_terms, num_threads=-1) + val = spex_tequila.expectation_value_parallel(final_state, final_state, H_terms, self.n_qubits, num_threads=-1) results.append(val.real) del final_state From 8c182b0c13cc29692fadf9489d9b63820e9a7365 Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Tue, 11 Feb 2025 20:48:46 +0100 Subject: [PATCH 21/32] Update ci_backends.yml --- .github/workflows/ci_backends.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_backends.yml b/.github/workflows/ci_backends.yml index 2a9f41df..b90943fa 100644 --- a/.github/workflows/ci_backends.yml +++ b/.github/workflows/ci_backends.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9'] + python-version: ['3.10'] steps: - uses: actions/checkout@v2 From a47a24602597fa646602ac6cff14b39c1a283cdc Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Tue, 11 Feb 2025 21:03:02 +0100 Subject: [PATCH 22/32] Update ci_backends.yml --- .github/workflows/ci_backends.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_backends.yml b/.github/workflows/ci_backends.yml index b90943fa..2fcfa3e1 100644 --- a/.github/workflows/ci_backends.yml +++ b/.github/workflows/ci_backends.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.10'] + python-version: ['3.11'] steps: - uses: actions/checkout@v2 From e6d04288046e78bd5de5a3680a46b91239e6f22e Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Tue, 11 Feb 2025 21:18:58 +0100 Subject: [PATCH 23/32] manual compiling --- .github/workflows/ci_backends.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_backends.yml b/.github/workflows/ci_backends.yml index 2fcfa3e1..11c3c8ab 100644 --- a/.github/workflows/ci_backends.yml +++ b/.github/workflows/ci_backends.yml @@ -16,20 +16,26 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.11'] - + python-version: ['3.10'] + include: + - os: ubuntu-latest + cxx: /usr/bin/g++-14 steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} + - name: Install spex + run: | + + pip install git+https://github.com/Mikel-Ma/spex + - name: Install basic dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install "cirq" "qiskit" "qulacs" "spex-tequila" - pip install -e . + pip install "cirq" "qiskit" "qulacs" - name: Lint with flake8 run: | pip install flake8 From 6311136f1802cd52fa5dbb080af4007722d070ab Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Tue, 11 Feb 2025 21:23:48 +0100 Subject: [PATCH 24/32] Update ci_backends.yml --- .github/workflows/ci_backends.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_backends.yml b/.github/workflows/ci_backends.yml index 11c3c8ab..937e54f7 100644 --- a/.github/workflows/ci_backends.yml +++ b/.github/workflows/ci_backends.yml @@ -35,7 +35,9 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install "cirq" "qiskit" "qulacs" + pip install "qiskit" "qulacs" + # pip install cirq # installs too much stuff currently + pip install -e . - name: Lint with flake8 run: | pip install flake8 From a482853876ef69776ade506ba9ad0a45caf0f9ec Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Tue, 11 Feb 2025 21:41:18 +0100 Subject: [PATCH 25/32] Update ci_backends.yml --- .github/workflows/ci_backends.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_backends.yml b/.github/workflows/ci_backends.yml index 937e54f7..1c7dd362 100644 --- a/.github/workflows/ci_backends.yml +++ b/.github/workflows/ci_backends.yml @@ -29,8 +29,8 @@ jobs: - name: Install spex run: | - pip install git+https://github.com/Mikel-Ma/spex - + #pip install git+https://github.com/Mikel-Ma/spex + pip install spex-tequila - name: Install basic dependencies run: | python -m pip install --upgrade pip From 068eca2a7fae6e1ff6c88d5bd2df50b39a0e94f9 Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Tue, 11 Feb 2025 21:44:37 +0100 Subject: [PATCH 26/32] Update ci_backends.yml --- .github/workflows/ci_backends.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_backends.yml b/.github/workflows/ci_backends.yml index 1c7dd362..034edae7 100644 --- a/.github/workflows/ci_backends.yml +++ b/.github/workflows/ci_backends.yml @@ -28,7 +28,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install spex run: | - + python -m pip install --upgrade pip #pip install git+https://github.com/Mikel-Ma/spex pip install spex-tequila - name: Install basic dependencies From 88cff1f2a2fd14977d7318648745f402000a40ac Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Tue, 11 Feb 2025 21:48:39 +0100 Subject: [PATCH 27/32] Update ci_backends.yml --- .github/workflows/ci_backends.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci_backends.yml b/.github/workflows/ci_backends.yml index 034edae7..0e0c8ce8 100644 --- a/.github/workflows/ci_backends.yml +++ b/.github/workflows/ci_backends.yml @@ -29,8 +29,7 @@ jobs: - name: Install spex run: | python -m pip install --upgrade pip - #pip install git+https://github.com/Mikel-Ma/spex - pip install spex-tequila + pip install git+https://github.com/Mikel-Ma/spex@devel - name: Install basic dependencies run: | python -m pip install --upgrade pip From 1e87864b6f2b0271431c01644d561bb1c3b19f19 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Tue, 25 Feb 2025 16:49:04 +0100 Subject: [PATCH 28/32] adding variables to hash --- src/tequila/simulators/simulator_spex.py | 30 ++- src/tequila/simulators/test_spex_simulator.py | 211 ++++++++++++++++++ 2 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 src/tequila/simulators/test_spex_simulator.py diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 0b9640e6..57e7f041 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -33,7 +33,7 @@ def extract_pauli_dict(ps): return dict(ps.paulistrings[0].items()) raise TequilaSpexException("Unsupported generator type") -def circuit_hash(abstract_circuit): +def circuit_hash(abstract_circuit, variables=None): """ Create MD5 hash for circuit caching Uses gate types, targets, controls and generators for uniqueness @@ -42,8 +42,11 @@ def circuit_hash(abstract_circuit): if abstract_circuit is None: return None for g in abstract_circuit.gates: - gate_str = f"{type(g).__name__}:{g.name}:{g.target}:{g.control}:{g.generator}\n" + gate_str = f"{type(g).__name__}:{g.name}:{g.target}:{g.control}:{g.generator}:{getattr(g, 'parameter', None)}\n" sha.update(gate_str.encode('utf-8')) + if variables: + for key, value in sorted(variables.items()): + sha.update(f"{key}:{value}\n".encode('utf-8')) return sha.hexdigest() def reverse_bits(x: int, n_bits: int) -> int: @@ -130,12 +133,19 @@ def initialize_circuit(self, *args, **kwargs): def create_circuit(self, abstract_circuit=None, variables=None, *args, **kwargs): """Compile circuit with caching using MD5 hash""" + print("create circuit") + print(abstract_circuit is None) if abstract_circuit is None: + print("abort") abstract_circuit = self.abstract_circuit - new_hash = circuit_hash(abstract_circuit) + new_hash = circuit_hash(abstract_circuit, variables) + + print(new_hash) + print(self.cached_circuit_hash) if (new_hash is not None) and (new_hash == self.cached_circuit_hash): + print("abort") return self.cached_circuit circuit = super().create_circuit(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs) @@ -178,9 +188,23 @@ def compress_qubit_indices(self): self.n_qubits_compressed = len(used_qubits) + def update_variables(self, variables, *args, **kwargs): + if variables is None: + variables = {} + self.cached_circuit_hash = None + self.cached_circuit = [] + super().update_variables(variables) + self.circuit = self.create_circuit(abstract_circuit=self.abstract_circuit, variables=variables) + def assign_parameter(self, param, variables): + print("assigning ", param, " with ", variables) if isinstance(param, (int, float, complex)): return float(param) + if isinstance(param, str): + if param in variables: + return float(variables[param]) + else: + raise TequilaSpexException(f"Variable '{param}' not found in variables") if callable(param): result = param(variables) return float(result) diff --git a/src/tequila/simulators/test_spex_simulator.py b/src/tequila/simulators/test_spex_simulator.py new file mode 100644 index 00000000..dfc7a76b --- /dev/null +++ b/src/tequila/simulators/test_spex_simulator.py @@ -0,0 +1,211 @@ +import tequila as tq +import numpy as np +import time +import os +import csv +from tequila.hamiltonian import PauliString +import pytest + + +#!/usr/bin/env python +import tequila as tq +import numpy as np +import time + +print("Test: Circuit und Hamiltonian mit Variablen (SPEX vs. Qulacs)") + +# --- Parameterdefinition --- +# Definiere mehrere Variablen für die Rotation +a = tq.Variable("a") +b = tq.Variable("b") +c = tq.Variable("c") +variables = {"a": np.pi/3, "b": np.pi/4, "c": np.pi/6} + +# --- Circuitaufbau --- +# Erzeuge einen Circuit, der auf 3 Qubits operiert: +# - Eine Rx-Rotation auf Qubit 0 (Winkel "a") +# - Eine Ry-Rotation auf Qubit 1 (Winkel "b") +# - Eine Rz-Rotation auf Qubit 2 (Winkel "c") +# - Zusätzlich eine parametrische exponentielle Pauli-Rotation (ExpPauli) auf Qubit 0 und 2 (Pauli-String "X(0)Z(2)") +U = tq.gates.Rx(angle="a", target=(0,)) \ + + tq.gates.Ry(angle="b", target=(1,)) \ + + tq.gates.Rz(angle="c", target=(2,)) +U += tq.gates.ExpPauli(angle="a", paulistring="X(0)Z(2)") + +print("\nCircuit U:") +print(U) + +# --- Hamiltonianaufbau --- +# Erstelle einen zusammengesetzten Hamiltonian auf 3 Qubits, +# z.B.: H = Z(0) + X(1)Y(2) + Z(0)Z(1) +H = tq.QubitHamiltonian("Z(0) + X(1)Y(2) + Z(0)Z(1)") +print("\nHamiltonian H:") +print(H) + +# Erzeuge ein Erwartungswertobjekt +E = tq.ExpectationValue(U=U, H=H) + +# --- Simulation mit SPEX --- +start = time.time() +wfn_spex = tq.simulate(U, variables, backend='spex') +exp_spex = tq.simulate(E, variables, backend='spex') +end = time.time() +time_spex = end - start + +# --- Simulation mit Qulacs --- +start = time.time() +wfn_qulacs = tq.simulate(U, variables, backend='qulacs') +exp_qulacs = tq.simulate(E, variables, backend='qulacs') +end = time.time() +time_qulacs = end - start + +# --- Ergebnisse ausgeben --- +print("\nSimulationsergebnisse:") +print("Wellenfunktion (SPEX backend):") +print(wfn_spex) +print("\nWellenfunktion (Qulacs backend):") +print(wfn_qulacs) + +print("\nErwartungswert (SPEX backend):", exp_spex, f"(Simulationszeit: {time_spex:.3f}s)") +print("Erwartungswert (Qulacs backend):", exp_qulacs, f"(Simulationszeit: {time_qulacs:.3f}s)") + +# Optional: Vergleiche das innere Produkt der beiden Wavefunctions (Quadrat des Betrags) +inner_prod = np.abs(wfn_spex.inner(wfn_qulacs))**2 +print("\nInneres Produkt (Quadrat) zwischen SPEX und Qulacs:", inner_prod) + + + +""" +print("\nTest: 1") +U = tq.gates.ExpPauli(paulistring=PauliString({0: "X", 1: "Z"}), angle=np.pi / 2) +H = tq.QubitHamiltonian("Z(0)") +E = tq.ExpectationValue(U=U, H=H) + +print(U) +print("spex-U:", tq.simulate(U, backend='spex')) +print("qulacs-U:", tq.simulate(U, backend='qulacs')) +print("spex:", tq.simulate(E, backend='spex')) +print("qulacs:", tq.simulate(E, backend='qulacs')) + + +print("\nTest: 2") +H = tq.QubitHamiltonian("Z(0)X(1)") + +U = tq.gates.Rx(angle=np.pi / 4, target=(0,)) +U += tq.gates.Ry(angle=np.pi / 3, target=(1,)) +E = tq.ExpectationValue(U=U, H=H) + +print(U) +print("spex-U:", tq.simulate(U, backend='spex')) +print("qulacs-U:", tq.simulate(U, backend='qulacs')) +print("spex:", tq.simulate(E, backend='spex')) +print("qulacs:", tq.simulate(E, backend='qulacs')) + + +print("\nTest: 3") +H = tq.QubitHamiltonian("Z(0)") + +U = tq.gates.X(target=(0,)) +U += tq.gates.Y(target=(1,)) +E = tq.ExpectationValue(U=U, H=H) + +print(U) +print("spex-U:", tq.simulate(U, backend='spex')) +print("qulacs-U:", tq.simulate(U, backend='qulacs')) +print("spex:", tq.simulate(E, backend='spex')) +print("qulacs:", tq.simulate(E, backend='qulacs')) + + +print("\nTest: 4") +U = ( + tq.gates.Rx(angle=np.pi / 2, target=(0,)) + + tq.gates.Rz(angle=np.pi / 2, target=(1,)) + + tq.gates.Ry(angle=np.pi / 3, target=(2,)) + + tq.gates.ExpPauli(paulistring=PauliString({0: "X", 1: "X", 2: "X"}), angle=np.pi / 4) +) + +H = tq.QubitHamiltonian("X(0)X(1)X(2) + Z(0) + Z(1) + Z(2)") +E = tq.ExpectationValue(U=U, H=H) + +print(U) +print("spex-U:", tq.simulate(U, backend='spex')) +print("qulacs-U:", tq.simulate(U, backend='qulacs')) +print("spex:", tq.simulate(E, backend='spex')) +print("qulacs:", tq.simulate(E, backend='qulacs')) + +print("\nTest: 5") +os.environ["OMP_NUM_THREADS"] = "6" + +results = [] + +for n in range(1, 15): + # 1) Geometrie aufbauen + geom = "" + for k in range(2*n): + geom += f"h 0.0 0.0 {1.5*k}\n" + + # 2) Molekül + Hamiltonian + mol = tq.Molecule(geometry=geom, basis_set="sto-3g") + H = mol.make_hardcore_boson_hamiltonian() + + # 3) Ansatz U + edges = [(2*i, 2*i+1) for i in range(n)] + U = mol.make_ansatz(name="HCB-SPA", edges=edges) + # Alle im Ansatz auftretenden Variablen auf 1.0 setzen + U = U.map_variables({var: 1.0 for var in U.extract_variables()}) + + # 4) Erwartungswert-Objekt + E_obj = tq.ExpectationValue(H=H, U=U) + + # -- SPEX-Berechnung -- + start_spex = time.time() + E_spex = tq.simulate(E_obj, backend='spex', num_threads=6) + end_spex = time.time() + time_spex = end_spex - start_spex + + # -- Qulacs-Berechnung -- + if n <= 10: + start_qulacs = time.time() + E_qulacs = tq.simulate(E_obj, backend='qulacs') + end_qulacs = time.time() + time_qulacs = end_qulacs - start_qulacs + + total_measurements = E_obj.count_measurements() + + # Speichern der Daten + results.append({ + 'n': n, + 'total_measurements' : total_measurements, + 'E_spex': E_spex, + 'time_spex': time_spex, + 'E_qulacs': E_qulacs, + 'time_qulacs': time_qulacs + }) + + if E_qulacs is not None: + print(f"n={n:2d} | total_measurements={total_measurements} | " + f"E_spex={E_spex:.6f} (dt={time_spex:.2f}s) | " + f"E_qulacs={E_qulacs:.6f} (dt={time_qulacs:.2f}s)") + else: + print(f"n={n:2d} | total_measurements={total_measurements} | " + f"E_spex={E_spex:.6f} (dt={time_spex:.2f}s) | " + f"E_qulacs=--- (dt=---) (für n>13 nicht berechnet)") + + with open("spex_qulacs_comparison.csv", mode="w", newline="") as f: + writer = csv.writer(f) + # Kopfzeile + writer.writerow(["n", "total_measurements", "E_spex", "time_spex", "E_qulacs", "time_qulacs"]) + # Datenzeilen + for entry in results: + writer.writerow([ + entry["n"], + entry["total_measurements"], + entry["E_spex"], + entry["time_spex"], + entry["E_qulacs"] if entry["E_qulacs"] is not None else "NA", + entry["time_qulacs"] if entry["time_qulacs"] is not None else "NA" + ]) + + E_qulacs = None + +""" \ No newline at end of file From bf352abf4e52106f36c77749e12778d73c77b9e0 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Wed, 26 Feb 2025 11:13:03 +0100 Subject: [PATCH 29/32] disabled compressing and added prints --- src/tequila/simulators/simulator_spex.py | 50 ++++++++++++------------ tests/test_simulator_backends.py | 4 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 57e7f041..311a7c5f 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -86,12 +86,11 @@ def __init__(self, num_threads=-1, amplitude_threshold=1e-14, angle_threshold=1e-14, - compress_qubits=True, + compress_qubits=False, *args, **kwargs): # Circuit chaching - self.cached_circuit_hash = None - self.cached_circuit = [] + self.circuit_cache = {} # Performance parameters self.num_threads = num_threads @@ -109,8 +108,8 @@ def __init__(self, def n_qubits(self): """Get number of qubits after compression (if enabled)""" used = set() - if self.cached_circuit: - for term in self.cached_circuit: + if hasattr(self, "circuit") and self.circuit: + for term in self.circuit: used.update(term.pauli_map.keys()) if self.abstract_circuit is not None and hasattr(self.abstract_circuit, "gates"): @@ -133,25 +132,24 @@ def initialize_circuit(self, *args, **kwargs): def create_circuit(self, abstract_circuit=None, variables=None, *args, **kwargs): """Compile circuit with caching using MD5 hash""" - print("create circuit") - print(abstract_circuit is None) + print(">> create_circuit called") + print(" given variables:", variables) if abstract_circuit is None: - print("abort") abstract_circuit = self.abstract_circuit - new_hash = circuit_hash(abstract_circuit, variables) + key = circuit_hash(abstract_circuit, variables) + print(" calculated cache-key:", key) - print(new_hash) - print(self.cached_circuit_hash) - - if (new_hash is not None) and (new_hash == self.cached_circuit_hash): - print("abort") - return self.cached_circuit + if key in self.circuit_cache: + print(" circuit found in cache for key:", key) + return self.circuit_cache[key] + print(" no circuit found, creating a new one") circuit = super().create_circuit(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs) - self.cached_circuit = circuit - self.cached_circuit_hash = new_hash + self.circuit_cache[key] = circuit + print(" new circuit with key:", key) + print(" current cache:", list(self.circuit_cache.keys())) return circuit @@ -160,12 +158,12 @@ def compress_qubit_indices(self): Optimize qubit indices by mapping used qubits to contiguous range Reduces memory usage by eliminating unused qubit dimensions """ - if not self.compress_qubits or not self.cached_circuit: + if not self.compress_qubits or not (hasattr(self, "circuit") and self.circuit): return # Collect all qubits used in circuit and Hamiltonians used_qubits = set() - for term in self.cached_circuit: + for term in self.circuit: used_qubits.update(term.pauli_map.keys()) for ham in self.hamiltonians: for term, _ in ham: @@ -178,7 +176,7 @@ def compress_qubit_indices(self): # Create qubit mapping and remap all terms qubit_map = {old: new for new, old in enumerate(sorted(used_qubits))} - for term in self.cached_circuit: + for term in self.circuit: term.pauli_map = {qubit_map[old]: op for old, op in term.pauli_map.items()} if self.hamiltonians is not None: @@ -191,13 +189,14 @@ def compress_qubit_indices(self): def update_variables(self, variables, *args, **kwargs): if variables is None: variables = {} - self.cached_circuit_hash = None - self.cached_circuit = [] + print(">> update_variables called") + print(" given variables:", variables) super().update_variables(variables) self.circuit = self.create_circuit(abstract_circuit=self.abstract_circuit, variables=variables) + print(" after update:") + print(" current cache:", list(self.circuit_cache.keys())) def assign_parameter(self, param, variables): - print("assigning ", param, " with ", variables) if isinstance(param, (int, float, complex)): return float(param) if isinstance(param, str): @@ -309,7 +308,8 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF del final_state gc.collect() - print("wfn_MSB:", wfn_MSB) + print("\nWavefunctions:\n") + print("before return in do_simulate:", wfn_MSB, "\n") return wfn_MSB @@ -321,7 +321,7 @@ def __init__(self, *args, num_threads=-1, amplitude_threshold=1e-14, angle_threshold=1e-14, - compress_qubits=True, + compress_qubits=False, **kwargs): super().__init__(*args, **kwargs) diff --git a/tests/test_simulator_backends.py b/tests/test_simulator_backends.py index 5e90ffac..6746aa34 100644 --- a/tests/test_simulator_backends.py +++ b/tests/test_simulator_backends.py @@ -298,8 +298,8 @@ def test_wfn_simple_consistency(simulator): print(ac) wfn0 = tequila.simulators.simulator_api.simulate(ac, backend=simulator) wfn1 = tequila.simulators.simulator_api.simulate(ac, backend=None) - print(wfn0) - print(wfn1) + print("spex:", wfn0, "\n") + print("reference:", wfn1, "\n") assert (wfn0.isclose(wfn1)) From e7ad7d845f5f083c9d306a489ef46826812c6857 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Tue, 4 Mar 2025 13:00:22 +0100 Subject: [PATCH 30/32] fix qubit mapping by overriding simulate in BackendCircuitSpex to avoid KeyMapSubregisterToRegister --- src/tequila/simulators/simulator_spex.py | 51 +++++++++++++----------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 311a7c5f..80bac2ed 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -86,7 +86,7 @@ def __init__(self, num_threads=-1, amplitude_threshold=1e-14, angle_threshold=1e-14, - compress_qubits=False, + compress_qubits=True, *args, **kwargs): # Circuit chaching @@ -132,24 +132,17 @@ def initialize_circuit(self, *args, **kwargs): def create_circuit(self, abstract_circuit=None, variables=None, *args, **kwargs): """Compile circuit with caching using MD5 hash""" - print(">> create_circuit called") - print(" given variables:", variables) if abstract_circuit is None: abstract_circuit = self.abstract_circuit key = circuit_hash(abstract_circuit, variables) - print(" calculated cache-key:", key) if key in self.circuit_cache: - print(" circuit found in cache for key:", key) return self.circuit_cache[key] - print(" no circuit found, creating a new one") circuit = super().create_circuit(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs) self.circuit_cache[key] = circuit - print(" new circuit with key:", key) - print(" current cache:", list(self.circuit_cache.keys())) return circuit @@ -189,12 +182,8 @@ def compress_qubit_indices(self): def update_variables(self, variables, *args, **kwargs): if variables is None: variables = {} - print(">> update_variables called") - print(" given variables:", variables) super().update_variables(variables) self.circuit = self.create_circuit(abstract_circuit=self.abstract_circuit, variables=variables) - print(" after update:") - print(" current cache:", list(self.circuit_cache.keys())) def assign_parameter(self, param, variables): if isinstance(param, (int, float, complex)): @@ -287,30 +276,39 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF QubitWaveFunction: Sparse state representation """ + if self.compress_qubits and self.n_qubits_compressed is not None and self.n_qubits_compressed > 0: + n_qubits = self.n_qubits_compressed + else: + n_qubits = self.n_qubits + # Initialize state - if isinstance(initial_state, int): + if isinstance(initial_state, (int, numpy.integer)): if initial_state == 0: - state = spex_tequila.initialize_zero_state(self.n_qubits) + state = spex_tequila.initialize_zero_state(n_qubits) else: state = {initial_state: 1.0 + 0j} else: # initial_state is already a QubitWaveFunction - state = initial_state.to_dictionary() + state = state = {k: v for k, v in initial_state.raw_items()} # Apply circuit with amplitude thresholding, -1.0 disables threshold in spex_tequila threshold = self.amplitude_threshold if self.amplitude_threshold is not None else -1.0 - final_state = spex_tequila.apply_U(self.circuit, state, threshold, self.n_qubits) + final_state = spex_tequila.apply_U(self.circuit, state, threshold, n_qubits) - wfn_MSB = QubitWaveFunction(n_qubits=self.n_qubits, numbering=BitNumbering.MSB) + wfn_MSB = QubitWaveFunction(n_qubits=n_qubits, numbering=BitNumbering.MSB) for state, amplitude in final_state.items(): wfn_MSB[state] = amplitude del final_state gc.collect() - print("\nWavefunctions:\n") - print("before return in do_simulate:", wfn_MSB, "\n") return wfn_MSB + + def simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveFunction: + """Override simulate to avoid automatic mapping by KeyMapSubregisterToRegister""" + self.update_variables(variables) + result = self.do_simulate(variables=variables, initial_state=initial_state, *args, **kwargs) + return result class BackendExpectationValueSpex(BackendExpectationValue): @@ -321,7 +319,7 @@ def __init__(self, *args, num_threads=-1, amplitude_threshold=1e-14, angle_threshold=1e-14, - compress_qubits=False, + compress_qubits=True, **kwargs): super().__init__(*args, **kwargs) @@ -374,20 +372,25 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): if self.U.compress_qubits: self.U.compress_qubit_indices() + if self.U.compress_qubits and self.U.n_qubits_compressed is not None and self.U.n_qubits_compressed > 0: + n_qubits = self.U.n_qubits_compressed + else: + n_qubits = self.U.n_qubits + # Prepare the initial state if isinstance(initial_state, int): if initial_state == 0: - state = spex_tequila.initialize_zero_state(self.n_qubits) + state = spex_tequila.initialize_zero_state(n_qubits) else: state = {initial_state: 1.0 + 0j} else: # initial_state is a QubitWaveFunction - state = initial_state.to_dictionary() + state = {k: v for k, v in initial_state.raw_items()} self.U.circuit = [t for t in self.U.circuit if abs(t.angle) >= self.U.angle_threshold] threshold = self.amplitude_threshold if self.amplitude_threshold is not None else -1.0 - final_state = spex_tequila.apply_U(self.U.circuit, state, threshold, self.n_qubits) + final_state = spex_tequila.apply_U(self.U.circuit, state, threshold, n_qubits) del state if "SPEX_NUM_THREADS" in os.environ: @@ -398,7 +401,7 @@ def simulate(self, variables, initial_state=0, *args, **kwargs): # Calculate the expectation value for each Hamiltonian results = [] for H_terms in self.H: - val = spex_tequila.expectation_value_parallel(final_state, final_state, H_terms, self.n_qubits, num_threads=-1) + val = spex_tequila.expectation_value_parallel(final_state, final_state, H_terms, n_qubits, num_threads=-1) results.append(val.real) del final_state From a37247ce16281b39cdf0f95749c9b95b2a540b73 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Tue, 4 Mar 2025 15:22:58 +0100 Subject: [PATCH 31/32] cleanup --- src/tequila/simulators/simulator_spex.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 80bac2ed..565a1082 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -49,11 +49,6 @@ def circuit_hash(abstract_circuit, variables=None): sha.update(f"{key}:{value}\n".encode('utf-8')) return sha.hexdigest() -def reverse_bits(x: int, n_bits: int) -> int: - bin_str = format(x, f'0{n_bits}b') - reversed_str = bin_str[::-1] - return int(reversed_str, 2) - class BackendCircuitSpex(BackendCircuit): """SPEX circuit implementation using sparse state representation""" @@ -204,14 +199,14 @@ def add_basic_gate(self, gate, circuit, *args, **kwargs): """Convert Tequila gates to SPEX exponential Pauli terms""" exp_term = spex_tequila.ExpPauliTerm() if isinstance(gate, ExponentialPauliGateImpl): - if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: + if self.angle_threshold is not None and abs(gate.parameter) < self.angle_threshold: return exp_term.pauli_map = extract_pauli_dict(gate.paulistring) exp_term.angle = gate.parameter circuit.append(exp_term) elif isinstance(gate, RotationGateImpl): - if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: + if self.angle_threshold is not None and abs(gate.parameter) < self.angle_threshold: return exp_term.pauli_map = extract_pauli_dict(gate.generator) exp_term.angle = gate.parameter @@ -221,7 +216,7 @@ def add_basic_gate(self, gate, circuit, *args, **kwargs): # Convert standard gates to Pauli rotations for ps in gate.make_generator(include_controls=True).paulistrings: angle = numpy.pi * ps.coeff - if self.angle_threshold != None and abs(angle) < self.angle_threshold: + if self.angle_threshold is not None and abs(angle) < self.angle_threshold: continue exp_term = spex_tequila.ExpPauliTerm() exp_term.pauli_map = dict(ps.items()) @@ -239,7 +234,7 @@ def add_parametrized_gate(self, gate, circuit, *args, **kwargs): exp_term = spex_tequila.ExpPauliTerm() if isinstance(gate, ExponentialPauliGateImpl): angle = self.assign_parameter(gate.parameter, kwargs.get("variables", {})) - if self.angle_threshold != None and abs(angle) < self.angle_threshold: + if self.angle_threshold is not None and abs(angle) < self.angle_threshold: return exp_term.pauli_map = extract_pauli_dict(gate.paulistring) exp_term.angle = angle @@ -247,7 +242,7 @@ def add_parametrized_gate(self, gate, circuit, *args, **kwargs): elif isinstance(gate, RotationGateImpl): angle = self.assign_parameter(gate.parameter, kwargs.get("variables", {})) - if self.angle_threshold != None and abs(angle) < self.angle_threshold: + if self.angle_threshold is not None and abs(angle) < self.angle_threshold: return exp_term.pauli_map = extract_pauli_dict(gate.generator) exp_term.angle = angle @@ -255,7 +250,7 @@ def add_parametrized_gate(self, gate, circuit, *args, **kwargs): elif isinstance(gate, QGateImpl): for ps in gate.make_generator(include_controls=True).paulistrings: - if self.angle_threshold != None and abs(gate.parameter) < self.angle_threshold: + if self.angle_threshold is not None and abs(gate.parameter) < self.angle_threshold: continue exp_term = spex_tequila.ExpPauliTerm() exp_term.pauli_map = dict(ps.items()) @@ -289,7 +284,7 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF state = {initial_state: 1.0 + 0j} else: # initial_state is already a QubitWaveFunction - state = state = {k: v for k, v in initial_state.raw_items()} + state = {k: v for k, v in initial_state.raw_items()} # Apply circuit with amplitude thresholding, -1.0 disables threshold in spex_tequila threshold = self.amplitude_threshold if self.amplitude_threshold is not None else -1.0 From e3e51424e82ba4f3e33cefc4d3c83dc510b092d1 Mon Sep 17 00:00:00 2001 From: Mikel-Ma <116980451+Mikel-Ma@users.noreply.github.com> Date: Tue, 4 Mar 2025 15:46:15 +0100 Subject: [PATCH 32/32] QubitExcitationImpl --- src/tequila/simulators/simulator_spex.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/tequila/simulators/simulator_spex.py b/src/tequila/simulators/simulator_spex.py index 565a1082..769d97cd 100644 --- a/src/tequila/simulators/simulator_spex.py +++ b/src/tequila/simulators/simulator_spex.py @@ -2,7 +2,7 @@ from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction from tequila.utils import TequilaException from tequila.hamiltonian import PauliString -from tequila.circuit._gates_impl import ExponentialPauliGateImpl, QGateImpl, RotationGateImpl, QubitHamiltonian +from tequila.circuit._gates_impl import ExponentialPauliGateImpl, QGateImpl, RotationGateImpl, QubitHamiltonian, QubitExcitationImpl from tequila import BitNumbering @@ -212,6 +212,11 @@ def add_basic_gate(self, gate, circuit, *args, **kwargs): exp_term.angle = gate.parameter circuit.append(exp_term) + elif isinstance(gate, QubitExcitationImpl): + compiled_gate = gate.compile(exponential_pauli=True) + for sub_gate in compiled_gate.abstract_circuit.gates: + self.add_basic_gate(sub_gate, circuit, *args, **kwargs) + elif isinstance(gate, QGateImpl): # Convert standard gates to Pauli rotations for ps in gate.make_generator(include_controls=True).paulistrings: @@ -247,6 +252,11 @@ def add_parametrized_gate(self, gate, circuit, *args, **kwargs): exp_term.pauli_map = extract_pauli_dict(gate.generator) exp_term.angle = angle circuit.append(exp_term) + + elif isinstance(gate, QubitExcitationImpl): + compiled_gate = gate.compile(exponential_pauli=True) + for sub_gate in compiled_gate.abstract_circuit.gates: + self.add_parametrized_gate(sub_gate, circuit, *args, **kwargs) elif isinstance(gate, QGateImpl): for ps in gate.make_generator(include_controls=True).paulistrings: