diff --git a/.nojekyll b/.nojekyll
index 9454f81..2b060d8 100644
--- a/.nojekyll
+++ b/.nojekyll
@@ -1 +1 @@
-e183e519
\ No newline at end of file
+cb39cde6
\ No newline at end of file
diff --git a/FAQ.html b/FAQ.html
index 90cc173..63f95e2 100644
--- a/FAQ.html
+++ b/FAQ.html
@@ -210,7 +210,7 @@
-The circuit list consists of the circuits preparing the i-th state. The addition is not an actual addition, but the concatenation operator for quantum circuits.
+The circuit list consists of the circuits preparing the i-th state. The addition is not an actual addition, but the concatenation operator for quantum circuits.
2
-This is the 0-projector.
+This is the 0-projector.
@@ -895,7 +895,7 @@
Chemistry example
1
-This is the given circuit, which consists of two rotations and two correlations. Thus, we are particularly able to analize the correlators and study their behaviour.
+This is the given circuit, which consists of two rotations and two correlations. Thus, we are particularly able to analize the correlators and study their behaviour.
diff --git a/Researches/Eigensolver/Eigensolver.pdf b/Researches/Eigensolver/Eigensolver.pdf
index fa652eb..4ba0d9f 100644
Binary files a/Researches/Eigensolver/Eigensolver.pdf and b/Researches/Eigensolver/Eigensolver.pdf differ
diff --git a/Researches/quantum_classifier/quantum_classifier.html b/Researches/quantum_classifier/quantum_classifier.html
index fa5d9c3..e108f42 100644
--- a/Researches/quantum_classifier/quantum_classifier.html
+++ b/Researches/quantum_classifier/quantum_classifier.html
@@ -322,31 +322,31 @@
1
-Initialize tunable variables, that will be optimized later
+Initialize tunable variables, that will be optimized later
2
-Create a unitary/quantum circuit using a given problem structure
+Create a unitary/quantum circuit using a given problem structure
3
-Define an Observable whose expectation we want to measure
+Define an Observable whose expectation we want to measure
4
-Define the Objective/Loss function we want to minimize as the expectation value of the Observable after applying the Unitary
+Define the Objective/Loss function we want to minimize as the expectation value of the Observable after applying the Unitary
5
-Define some initial values for the variables we want to optimize
+Define some initial values for the variables we want to optimize
6
-Minimize the Objective through one of many different minimization methods to compute optimal variable assignments
+Minimize the Objective through one of many different minimization methods to compute optimal variable assignments
7
-Return the calculated parameters of the circuit and the minimal value of the Objective
+Return the calculated parameters of the circuit and the minimal value of the Objective
@@ -615,15 +615,15 @@
3.4 Loss function
1
-We iterate through the training data and calculate the impact of that datapoint on the loss function
+We iterate through the training data and calculate the impact of that datapoint on the loss function
2
-The fidelity between the circuit result and the correct label state is expressed as a expectation value
+The fidelity between the circuit result and the correct label state is expressed as a expectation value
3
-A value in \([0,1]\) gets added based on how good the current datapoint is treated by the circuit
+A value in \([0,1]\) gets added based on how good the current datapoint is treated by the circuit
-creates an empty circuit object with no gates
+creates an empty circuit object with no gates
2
-add a hadamard gate to qubit 0
+add a hadamard gate to qubit 0
3
-add a \(CNOT\) with control in qubit 1 and target in 1
+add a \(CNOT\) with control in qubit 1 and target in 1
@@ -309,11 +309,11 @@
1. Self overlap
1
-get the real and the imaginary part of the state
+get the real and the imaginary part of the state
2
-join the save and imaginary part of the overlap. self_overlap is not a complex number
+join the save and imaginary part of the overlap. self_overlap is not a complex number
@@ -327,11 +327,11 @@
1. Self overlap
1
-compute the self overlap of the state
+compute the self overlap of the state
2
-if the self overlap is not equal to \(1\), this should throw an exception
+if the self overlap is not equal to \(1\), this should throw an exception
@@ -348,7 +348,7 @@
1. Self overlap
1
-printing the overlap shows all of its object attributes
+printing the overlap shows all of its object attributes
@@ -371,11 +371,11 @@
2. Ov
1
-this circuit will perpare the two-qubit state |10> since we assume two input-qubits in state |0> and a \(X\) (NOT) is applied to the first one
+this circuit will perpare the two-qubit state |10> since we assume two input-qubits in state |0> and a \(X\) (NOT) is applied to the first one
2
-also here we assume an input of |00> and send it through the circuit that generated the 4 bell-states
+also here we assume an input of |00> and send it through the circuit that generated the 4 bell-states
@@ -391,19 +391,19 @@
2. Ov
1
-specify input states for writing them in the braket notation
+specify input states for writing them in the braket notation
2
-joining real and imaginary part based on the states produced by the circuits U0 and U1
+joining real and imaginary part based on the states produced by the circuits U0 and U1
3
-simulates the tq.Objective object to obtain a numeric values
+simulates the tq.Objective object to obtain a numeric values
4
-prints the overlap, this one should be equal to \(0\)
+prints the overlap, this one should be equal to \(0\)
@@ -439,43 +439,43 @@
2. Ov
1
-declare a variable of for a parametrized angle
+declare a variable of for a parametrized angle
2
-Bell state followed by a rotation around X-axis
+Bell state followed by a rotation around X-axis
3
-fetch once again the real and imaginary components after writing the input states in braket form
+fetch once again the real and imaginary components after writing the input states in braket form
4
-prepare real and imaginary part for overlap computation
+prepare real and imaginary part for overlap computation
5
-translate to backend - compile the state with an arbitrary parametrized angle
+translate to backend - compile the state with an arbitrary parametrized angle
6
-generates values from the intervall \([0,4.1]\) with steps of \(0.1\) for simulating multiple angles
+generates values from the intervall \([0,4.1]\) with steps of \(0.1\) for simulating multiple angles
7
-associate angle with generated value from range above
+associate angle with generated value from range above
8
-simulate the tq.Objective object ad get the computed overlap
+simulate the tq.Objective object ad get the computed overlap
9
-append the achieved value to a list of overlaps, since we compute multiple overlaps for multiple angles
+append the achieved value to a list of overlaps, since we compute multiple overlaps for multiple angles
10
-display the resulted numerical values of the overlaps as a graph
+display the resulted numerical values of the overlaps as a graph
@@ -500,11 +500,11 @@
3. Exp
1
-as before define the first bell state with the circuit generating the bell-basis and an input of |00>
+as before define the first bell state with the circuit generating the bell-basis and an input of |00>
2
-define an hamiltonian - this one is a sequence of Pauli-\(X\) matrices acting on the second and the first qubits respectivly and then a scale factor of \(-1\)
+define an hamiltonian - this one is a sequence of Pauli-\(X\) matrices acting on the second and the first qubits respectivly and then a scale factor of \(-1\)
@@ -523,19 +523,19 @@
3. Exp
1
-applying the braket function on the bell-state and the hamiltonian, defining the hamiltonian as an operator - important
+applying the braket function on the bell-state and the hamiltonian, defining the hamiltonian as an operator - important
2
-joining real and imaginary part for computation
+joining real and imaginary part for computation
3
-simulate the expectation value
+simulate the expectation value
4
-show the expectatino value, should be equal to \(1\)
+show the expectatino value, should be equal to \(1\)
@@ -580,47 +580,47 @@
4. Tr
1
-create the bell state the same way as before
+create the bell state the same way as before
2
-define a general angle for later computations as in the last example
+define a general angle for later computations as in the last example
3
-add a rotation after the bell state
+add a rotation after the bell state
4
-define the hamiltonian the same way as before
+define the hamiltonian the same way as before
5
-apply braket function, specify the ket and the bra as the bell state and the bell state followed by the \(X\) rotation respectively and get the real and imaginary part
+apply braket function, specify the ket and the bra as the bell state and the bell state followed by the \(X\) rotation respectively and get the real and imaginary part
6
-use the real and imaginary part for creating a complex number
+use the real and imaginary part for creating a complex number
7
-compile this complex number by translating it to the backend
+compile this complex number by translating it to the backend
8
-use the same interval as a range for angle generation as before and the same step size
+use the same interval as a range for angle generation as before and the same step size
9
-set the of the angle we want to simulate
+set the of the angle we want to simulate
10
-simulate the tq.Objective to get the computed value
+simulate the tq.Objective to get the computed value
11
-store these values rounded by 3 digits after the comma into a list of transitions for later plot
+store these values rounded by 3 digits after the comma into a list of transitions for later plot
@@ -674,43 +674,43 @@
4. Tr
1
-declare a variable of a general angle \(a\)
+declare a variable of a general angle \(a\)
2
-declare a rotation around \(X\) with the angle \(a\pi\)
+declare a rotation around \(X\) with the angle \(a\pi\)
3
-rotation around \(Y\) axis with a fixed angle of \(\pi\)
+rotation around \(Y\) axis with a fixed angle of \(\pi\)
4
-hamiltonian with a paulii \(Z\) gate on the first qubit
+hamiltonian with a paulii \(Z\) gate on the first qubit
5
-specify ket, bra and operator and fetch the imaginary and real part
+specify ket, bra and operator and fetch the imaginary and real part
6
-transalte to backend and get the operator
+transalte to backend and get the operator
7
-same range and step size as before
+same range and step size as before
8
-set the angle values we want to simulate
+set the angle values we want to simulate
9
-simulate the tq.Objective to get the computed value
+simulate the tq.Objective to get the computed value
10
-store the computed values in a list for plotting
+store the computed values in a list for plotting
@@ -786,19 +786,19 @@
Mak
1
-assume two qubits in \(|00>\) and flip the first qubit to get \(|10>\)
+assume two qubits in \(|00>\) and flip the first qubit to get \(|10>\)
2
-prepare a bell state as before
+prepare a bell state as before
3
-get the real and imaginary parts consistently with the explained notation
+get the real and imaginary parts consistently with the explained notation
4
-optionally draw the resulted circuit, should look like the one below
+optionally draw the resulted circuit, should look like the one below
@@ -825,23 +825,23 @@
Mak
1
-prepare state for \(U_0\) the same way as before
+prepare state for \(U_0\) the same way as before
2
-prepare bell state
+prepare bell state
3
-decalre hermitian operator consisting of a \(Z\) and two \(X\) paulis
+decalre hermitian operator consisting of a \(Z\) and two \(X\) paulis
4
-use the braketfunction to fetch imaginary and real consistently with the explained notation as before
+use the braketfunction to fetch imaginary and real consistently with the explained notation as before
5
-optional: draw the circuits, thuy should look like the two circuits below
+optional: draw the circuits, thuy should look like the two circuits below
-add a rotation Z gate with an angle of 90 deg.
+add a rotation Z gate with an angle of 90 deg.
3
-add a Z gate with a power of 0.5
+add a Z gate with a power of 0.5
4
-print the gates with their names and generator values
+print the gates with their names and generator values
@@ -327,19 +327,19 @@
On this page
1
-add a conrtolled X rotation of 90 deg. with. Control qubit is the first and target the second
+add a conrtolled X rotation of 90 deg. with. Control qubit is the first and target the second
2
-collect generators parameters of each generator in circuit qc
+collect generators parameters of each generator in circuit qc
3
-collect parameters of full generators in qc
+collect parameters of full generators in qc
4
-output the parameters of each gate in circuit qc
+output the parameters of each gate in circuit qc
@@ -367,11 +367,11 @@
Gates decomposition
1
-create a squared hadamard gate with a target qubit
+create a squared hadamard gate with a target qubit
2
-compile the circuit qc, containing only one hadamard gate, - this will decompose the gate as shown above to rotations and paulis
+compile the circuit qc, containing only one hadamard gate, - this will decompose the gate as shown above to rotations and paulis
@@ -401,15 +401,15 @@
Gates decomposition
1
-create a parametrized rotation \(Z\) gate with two controls and an angle of 180 deg.
+create a parametrized rotation \(Z\) gate with two controls and an angle of 180 deg.
2
-compile the circuit - this will decompose it to a sequence of parametrized rotation \(Z\) gates and \(CNOT\) gates
+compile the circuit - this will decompose it to a sequence of parametrized rotation \(Z\) gates and \(CNOT\) gates
3
-display the compiled circuit
+display the compiled circuit
@@ -438,11 +438,11 @@
Gates decomposition
1
-create a gate based of a given Pauli-String of \(Z\) gates parameterized with an angle of 45 deg.
+create a gate based of a given Pauli-String of \(Z\) gates parameterized with an angle of 45 deg.
2
-compile this gate - this will decompose it to a combination of \(CNOT\) gates and rotations
+compile this gate - this will decompose it to a combination of \(CNOT\) gates and rotations
@@ -463,11 +463,11 @@
Gates decomposition
1
-create a controlled gate in qubit 1. with control qubit in 0. and a phase of 180 deg.
+create a controlled gate in qubit 1. with control qubit in 0. and a phase of 180 deg.
2
-compile this gate - thus decompose it to rotations and \(CNOT\)s
+compile this gate - thus decompose it to rotations and \(CNOT\)s
@@ -489,11 +489,11 @@
Gates decomposition
1
-create a Toffoli gate with controls in qubits 1 and 2 and target in qubit 2
+create a Toffoli gate with controls in qubits 1 and 2 and target in qubit 2
2
-compile this gate - since this gate is recognized by the backend it won’t get decomposed to rotations and \(CNOT\) gates
+compile this gate - since this gate is recognized by the backend it won’t get decomposed to rotations and \(CNOT\) gates
@@ -510,11 +510,11 @@
Gates decomposition
1
-create the same Toffoli gate as before
+create the same Toffoli gate as before
2
-compile the gate with a different backend “qiskit”. Since this backend doesn’t recognize this gate it will decompose it to smaller gates
+compile the gate with a different backend “qiskit”. Since this backend doesn’t recognize this gate it will decompose it to smaller gates
-Set the random seed for reproducibility
+Set the random seed for reproducibility
2
-Number of Krylov states to generate
+Number of Krylov states to generate
3
-Create random quantum circuits, in this way it is very unlikely they will be orthogonal
+Create random quantum circuits, in this way it is very unlikely they will be orthogonal
4
-Create the wavefunctions from the circuits
+Create the wavefunctions from the circuits
@@ -297,19 +297,19 @@
Simple example
1
-Generate a list of all posible couples of Krylov states
+Generate a list of all posible couples of Krylov states
2
-Create a Hamiltonian from the obtained wavefunctions
+Create a Hamiltonian from the obtained wavefunctions
3
-Initialize an empty QubitHamiltonian object
+Initialize an empty QubitHamiltonian object
4
-For each couple of Krylov states, compute the braket and substract the term from the Hamiltonian
+For each couple of Krylov states, compute the braket and substract the term from the Hamiltonian
@@ -324,15 +324,15 @@
Simple example
1
-Applying the Krylov method
+Applying the Krylov method
2
-Extract the ground state energy
+Extract the ground state energy
3
-Extract the coefficients
+Extract the coefficients
@@ -344,7 +344,7 @@
Simple example
1
-Perform exact diagonalization of the Hamiltonian
+Perform exact diagonalization of the Hamiltonian
@@ -369,15 +369,15 @@
Simple example
1
-Initialize the ground state
+Initialize the ground state
2
-Construct the ground state wavefunction by adding the scaled Krylov states
+Construct the ground state wavefunction by adding the scaled Krylov states
3
-Print the ground state
+Print the ground state
@@ -393,7 +393,7 @@
Simple example
1
-Create a QubitWaveFunction object from the array representing the first eigenvector
+Create a QubitWaveFunction object from the array representing the first eigenvector
@@ -410,7 +410,7 @@
Simple example
1
-Compute the fidelity between the two states
+Compute the fidelity between the two states
-A circuit is being created
+A circuit is being created
2
-The corresponding qpic-file to this circuit is being created. For generating the corresponding png one has to write “qpic -f png C1_new.qpic” into the command line
+The corresponding qpic-file to this circuit is being created. For generating the corresponding png one has to write “qpic -f png C1_new.qpic” into the command line
@@ -325,7 +325,7 @@
Export to OpenQASM
1
-Shows the circuit of openqasmcode
+Shows the circuit of openqasmcode
@@ -369,7 +369,7 @@
Export to OpenQASM
1
-Shows the circuit of openqasmcode
+Shows the circuit of openqasmcode
@@ -424,7 +424,7 @@
Import from OpenQASM
1
-In the case of having the OpenQASM code in a file, it is possible to load that file to generate the Tequila circuit from there, for this the import_open_qasm_from_file function is used
+In the case of having the OpenQASM code in a file, it is possible to load that file to generate the Tequila circuit from there, for this the import_open_qasm_from_file function is used
@@ -501,7 +501,7 @@
Import from OpenQASM
1
-Shows the circuit of openqasmcode_no_y
+Shows the circuit of openqasmcode_no_y
@@ -529,7 +529,7 @@
Import from OpenQASM
1
-Shows the circuit of openqasmcode
+Shows the circuit of openqasmcode
-Get a list of all available optimizers
+Get a list of all available optimizers
@@ -343,19 +343,19 @@
Overview
1
-Optimizing the circuit in terms of pi makes the result of the optimization easier to interpret
+Optimizing the circuit in terms of pi makes the result of the optimization easier to interpret
2
-Create a 2-qubit quantum circuit for the Objective\(O_1\)
+Create a 2-qubit quantum circuit for the Objective\(O_1\)
3
-Define the Hamiltonian for \(O_1\) to optimize over
+Define the Hamiltonian for \(O_1\) to optimize over
4
-Define the expectation value of the Hamiltonian after applying the quantum circuit
+Define the expectation value of the Hamiltonian after applying the quantum circuit
@@ -382,19 +382,19 @@
Overview
1
-This time, we don’t optimize the circuit in terms of pi
+This time, we don’t optimize the circuit in terms of pi
2
-Create a 3-qubit quantum circuit for the Objective\(O_2\)
+Create a 3-qubit quantum circuit for the Objective\(O_2\)
3
-Define the Hamiltonian for \(O_2\) to optimize over
+Define the Hamiltonian for \(O_2\) to optimize over
4
-Define the expectation value of the Hamiltonian after applying the quantum circuit
+Define the expectation value of the Hamiltonian after applying the quantum circuit
@@ -418,7 +418,7 @@
The GD Optimizer
1
-Get a list of all available optimization methods for the GD optimizer
+Get a list of all available optimization methods for the GD optimizer
@@ -454,19 +454,19 @@
The GD Optimizer
1
-Initialize all four variables to \(\frac{1}{4}\pi\)
+Initialize all four variables to \(\frac{1}{4}\pi\)
2
-Set the learning rate to 0.1
+Set the learning rate to 0.1
3
-Optimize the objective \(O_1\) using the Adam method with learning rate 0.1 and maximal iterations 80
+Optimize the objective \(O_1\) using the Adam method with learning rate 0.1 and maximal iterations 80
4
-Set silent=True to suppress output
+Set silent=True to suppress output
@@ -481,11 +481,11 @@
The GD Optimizer
1
-Plot energy from Adam optimization
+Plot energy from Adam optimization
2
-Plot angles from Adam optimization
+Plot angles from Adam optimization
@@ -527,23 +527,23 @@
The GD Optimizer
1
-Initialize all four variables to \(\frac{1}{4}\pi\)
+Initialize all four variables to \(\frac{1}{4}\pi\)
2
-Set the learning rate to 0.01
+Set the learning rate to 0.01
3
-Optimize the objective \(O_1\) using the RMSprop method with learning rate 0.01 and maximal iterations 80
+Optimize the objective \(O_1\) using the RMSprop method with learning rate 0.01 and maximal iterations 80
4
-Plot energy from RMSprop optimization
+Plot energy from RMSprop optimization
5
-Plot angles from RMSprop optimization
+Plot angles from RMSprop optimization
@@ -584,23 +584,23 @@
The GD Optimizer
1
-Initialize all four variables to \(\frac{1}{4}\pi\)
+Initialize all four variables to \(\frac{1}{4}\pi\)
2
-Set the learning rate to 0.1
+Set the learning rate to 0.1
3
-Optimize the objective \(O_1\) using the Momentum method with learning rate 0.1 and maximal iterations 80
+Optimize the objective \(O_1\) using the Momentum method with learning rate 0.1 and maximal iterations 80
4
-Plot energy from Momentum optimization
+Plot energy from Momentum optimization
5
-Plot angles from Momentum optimization
+Plot angles from Momentum optimization
-Set the learning rate to 0.1
+Set the learning rate to 0.1
3
-Optimize the objective \(O_1\) using the standard gradient descent method with learning rate 0.1, maximal iterations 80 and a tolerance of 1e-10
+Optimize the objective \(O_1\) using the standard gradient descent method with learning rate 0.1, maximal iterations 80 and a tolerance of 1e-10
@@ -668,11 +668,11 @@
The GD Optimizer
1
-Optimize the objective \(O_1\) using the standard gradient descent method with learning rate 0.1, maximal iterations 80, a tolerance of 1e-10 and DIIS acceleration
+Optimize the objective \(O_1\) using the standard gradient descent method with learning rate 0.1, maximal iterations 80, a tolerance of 1e-10 and DIIS acceleration
2
-Set the DIIS acceleration with a tolerance of 1e-10
+Set the DIIS acceleration with a tolerance of 1e-10
@@ -695,27 +695,27 @@
The GD Optimizer
1
-Import the numpy library
+Import the numpy library
2
-Extract the last energy values from the DISS optimization
+Extract the last energy values from the DISS optimization
3
-Plot the error on energy for every step in the optimization without DIIS acceleration
+Plot the error on energy for every step in the optimization without DIIS acceleration
4
-Plot the error on energy for every step in the optimization with DIIS acceleration
+Plot the error on energy for every step in the optimization with DIIS acceleration
5
-Label the y-axis of the plot as ‘Error on Energy’
+Label the y-axis of the plot as ‘Error on Energy’
6
-Add a legend to the plot to differentiate between the results with and without DIIS
+Add a legend to the plot to differentiate between the results with and without DIIS
@@ -749,23 +749,23 @@
The GD Optimizer
1
-keyword ‘stop_count’ stops optimization if no improvement occurs after 50 epochs
+keyword ‘stop_count’ stops optimization if no improvement occurs after 50 epochs
2
-Initialize the four variables to random values between -2 and 2
+Initialize the four variables to random values between -2 and 2
3
-Set the learning rate to 0.01
+Set the learning rate to 0.01
4
-Optimize the objective \(O_2\) using the standard gradient descent method with learning rate 0.01, maximal iterations 200 and the Quantum Natural Gradient (QNG)
+Optimize the objective \(O_2\) using the standard gradient descent method with learning rate 0.01, maximal iterations 200 and the Quantum Natural Gradient (QNG)
5
-Set the gradient to the Quantum Natural Gradient (QNG)
+Set the gradient to the Quantum Natural Gradient (QNG)
@@ -780,11 +780,11 @@
The GD Optimizer
1
-Plot the energy from the optimization with the Quantum Natural Gradient
+Plot the energy from the optimization with the Quantum Natural Gradient
2
-Plot the angles from the optimization with the Quantum Natural Gradient
+Plot the angles from the optimization with the Quantum Natural Gradient
@@ -826,19 +826,19 @@
The GD Optimizer
1
-Set the learning rate to 0.01
+Set the learning rate to 0.01
2
-Optimize the objective \(O_2\) using the standard gradient descent method with learning rate 0.01 and maximal iterations 200
+Optimize the objective \(O_2\) using the standard gradient descent method with learning rate 0.01 and maximal iterations 200
3
-Plot the energy from the optimization without the Quantum Natural Gradient
+Plot the energy from the optimization without the Quantum Natural Gradient
4
-Plot the angles from the optimization without the Quantum Natural Gradient
+Plot the angles from the optimization without the Quantum Natural Gradient
@@ -869,7 +869,7 @@
The SciPy Optimizer
1
-Get a list of all available optimization methods for the SciPy optimizer
+Get a list of all available optimization methods for the SciPy optimizer
@@ -915,23 +915,23 @@
The SciPy Optimizer
1
-Initialize all four variables to fixed values of \(0.25\)
+Initialize all four variables to fixed values of \(0.25\)
2
-Optimize the objective \(O_1\) using the COBYLA method with a tolerance of \(1.e-3\)
+Optimize the objective \(O_1\) using the COBYLA method with a tolerance of \(1.e-3\)
3
-Set the gradient tolerance to \(1.e-3\)
+Set the gradient tolerance to \(1.e-3\)
4
-Plot the energy from the optimization with the COBYLA method
+Plot the energy from the optimization with the COBYLA method
5
-Plot the angles from the optimization with the COBYLA method
+Plot the angles from the optimization with the COBYLA method
@@ -967,19 +967,19 @@
The SciPy Optimizer
1
-Optimize the objective \(O_1\) using the L-BFGS-B method with a tolerance of \(1.e-3\)
+Optimize the objective \(O_1\) using the L-BFGS-B method with a tolerance of \(1.e-3\)
2
-Set the gradient tolerance to \(1.e-3\)
+Set the gradient tolerance to \(1.e-3\)
3
-Plot the energy from the optimization with the L-BFGS-B method
+Plot the energy from the optimization with the L-BFGS-B method
4
-Plot the angles from the optimization with the L-BFGS-B method
+Plot the angles from the optimization with the L-BFGS-B method
@@ -1015,19 +1015,19 @@
The SciPy Optimizer
1
-Optimize the objective \(O_1\) using the NEWTON-CG method with a tolerance of \(1.e-3\)
+Optimize the objective \(O_1\) using the NEWTON-CG method with a tolerance of \(1.e-3\)
2
-Set the gradient tolerance to \(1.e-3\)
+Set the gradient tolerance to \(1.e-3\)
3
-Plot the energy from the optimization with the NEWTON-CG method
+Plot the energy from the optimization with the NEWTON-CG method
4
-Plot the angles from the optimization with the NEWTON-CG method
+Plot the angles from the optimization with the NEWTON-CG method
@@ -1068,23 +1068,23 @@
The SciPy Optimizer
1
-Optimize the objective \(O_1\) using the L-BFGS-B method with a tolerance of \(1.e-3\), numerical gradients, and a step size of \(1.e-4\)
+Optimize the objective \(O_1\) using the L-BFGS-B method with a tolerance of \(1.e-3\), numerical gradients, and a step size of \(1.e-4\)
2
-Set the gradient to 2-point to use numerical gradients
+Set the gradient to 2-point to use numerical gradients
3
-Set the step size to \(1.e-4\)
+Set the step size to \(1.e-4\)
4
-Plot the energy from the optimization with the L-BFGS-B method and numerical gradients
+Plot the energy from the optimization with the L-BFGS-B method and numerical gradients
5
-Plot the angles from the optimization with the L-BFGS-B method and numerical gradients
+Plot the angles from the optimization with the L-BFGS-B method and numerical gradients
@@ -1128,27 +1128,27 @@
The SciPy Optimizer
1
-Initialize the four variables to random values between -2 and 2
+Initialize the four variables to random values between -2 and 2
2
-Set the learning rate to 0.01
+Set the learning rate to 0.01
3
-Optimize the objective \(O_2\) using the BFGS method with learning rate 0.01, maximal iterations 200 and the Quantum Natural Gradient (QNG)
+Optimize the objective \(O_2\) using the BFGS method with learning rate 0.01, maximal iterations 200 and the Quantum Natural Gradient (QNG)
4
-Set the gradient to the Quantum Natural Gradient (QNG)
+Set the gradient to the Quantum Natural Gradient (QNG)
5
-Plot the energy from the optimization with the BFGS method and the Quantum Natural Gradient
+Plot the energy from the optimization with the BFGS method and the Quantum Natural Gradient
6
-Plot the angles from the optimization with the BFGS method and the Quantum Natural Gradient
+Plot the angles from the optimization with the BFGS method and the Quantum Natural Gradient
@@ -1188,19 +1188,19 @@
The SciPy Optimizer
1
-Optimize the objective \(O_2\) using the BFGS method with learning rate 0.01 and maximal iterations 200
+Optimize the objective \(O_2\) using the BFGS method with learning rate 0.01 and maximal iterations 200
2
-Set the gradient to None
+Set the gradient to None
3
-Plot the energy from the optimization with the BFGS method
+Plot the energy from the optimization with the BFGS method
4
-Plot the angles from the optimization with the BFGS method
+Plot the angles from the optimization with the BFGS method
@@ -1241,15 +1241,15 @@
Numerical and Customized Gradients
1
-Set the learning rate to 0.01
+Set the learning rate to 0.01
2
-Optimize the objective \(O_2\) using the standard gradient descent method with learning rate 0.01, maximal iterations 200 and numerical gradients with a step size of \(1.e-4\)
+Optimize the objective \(O_2\) using the standard gradient descent method with learning rate 0.01, maximal iterations 200 and numerical gradients with a step size of \(1.e-4\)
3
-Set the gradient to a dictionary with the method 2-point for numerical gradients and a step size of $1.e-4
+Set the gradient to a dictionary with the method 2-point for numerical gradients and a step size of $1.e-4
@@ -1260,7 +1260,7 @@
Numerical and Customized Gradients
1
-Plot the energy from the ‘standard gradient descent’ optimization with numerical gradients
+Plot the energy from the ‘standard gradient descent’ optimization with numerical gradients
@@ -1313,35 +1313,35 @@
Numerical and Customized Gradients
1
-Import the copy library to deep copy objects
+Import the copy library to deep copy objects
2
-Create a deep copy of the variable
+Create a deep copy of the variable
3
-Shift the variable to the right by half of the step size
+Shift the variable to the right by half of the step size
4
-Create another deep copy of the variable
+Create another deep copy of the variable
5
-Shift the variable to the left by half of the step size
+Shift the variable to the left by half of the step size
6
-Calculate the difference gradient using the symmetric difference quotient.
+Calculate the difference gradient using the symmetric difference quotient.
7
-Optimize the objective \(O_2\) using the standard gradient descent method with learning rate 0.01, maximal iterations 200 and an objective gradient with a step size of \(1.e-4\)
+Optimize the objective \(O_2\) using the standard gradient descent method with learning rate 0.01, maximal iterations 200 and an objective gradient with a step size of \(1.e-4\)
8
-Set the gradient to a dictionary with the method my_finite_difference_stencil and a step size of \(1.e-4\)
+Set the gradient to a dictionary with the method my_finite_difference_stencil and a step size of \(1.e-4\)
In the code block above, we did not specify the simulator to be used, tequila determines this automatically. You can however specify the similator via the backend keyword. If you are not sure which simulators are installed on your system, use tq.show_available_simulators()
note here, that the variables dictionary does not need to be initialized with the variable type, the plain names (as hashable types like strings) are enough - in the background this will all be converted to tq.Variable.
+[docs]
+classQCircuit():
+"""
+ Fundamental class representing an abstract circuit.
+
+ Attributes
+ ----------
+ canonical_depth:
+ the depth of the circuit, if converted to alternating parametrized and unparametrized layers.
+ canonical_moments:
+ returns the circuit as a list of Moment objects alternating between parametrized and unparametrized layers.
+ depth:
+ returns the gate depth of the circuit.
+ gates:
+ returns the gates in the circuit, as a list.
+ moments:
+ returns the circuit as a list of Moment objects.
+ n_qubits:
+ the number of qubits on which the circuit operates.
+ numbering:
+ returns the numbering convention use by tequila circuits.
+ qubits:
+ returns a list of qubits acted upon by the circuit.
+
+
+ Methods
+ -------
+ make_parameter_map:
+
+
+ """
+
+
+[docs]
+ defexport_to(self,*args,**kwargs):
+"""
+ Export to png, pdf, qpic, tex with qpic backend
+ Convenience: see src/tequila/circuit/qpic.py - export_to for more
+ Parameters
+ """
+ returnexport_to(self,*args,**kwargs)
+
+
+ @property
+ defmoments(self):
+"""
+ Divide self into subcircuits representing layers of simultaneous gates. Attempts to minimize gate depth.
+ Returns
+ -------
+ list:
+ list of Moment objects.
+ """
+ table={i:0foriinself.qubits}
+ moms=[]
+ moms.append(Moment())
+ forginself.gates:
+ qus=g.qubits
+ spots=[table[q]forqinqus]
+
+ ifmax(spots)==len(moms):
+
+ moms.append(Moment([g]))
+ else:
+ moms[max(spots)].add_gate(g)
+ forqinqus:
+ table[q]=max(spots)+1
+ formominmoms:
+ mom.sort_gates()
+ returnmoms
+
+ @property
+ defcanonical_moments(self):
+"""
+ Divide self into subcircuits of alternating unparametrized and parametrized layers.
+ Returns
+ -------
+ list of Moment objects.
+ """
+ table_u={i:0foriinself.qubits}
+ table_p={i:0foriinself.qubits}
+ moms=[]
+ moms.append((Moment(),Moment()))
+
+ forginself.gates:
+ p=0
+ qus=g.qubits
+ ifg.is_parametrized():
+ ifhasattr(g.parameter,'extract_variables'):
+ p=1
+
+ ifp==0:
+ spots=[table_u[q]forqinqus]+[table_p[q]forqinqus]
+ ifmax(spots)==len(moms):
+ moms.append((Moment([g]),Moment()))
+ else:
+ moms[max(spots)][0].add_gate(g)
+ forqinqus:
+ table_u[q]=max(spots)+1
+ table_p[q]=max(spots)
+
+ else:
+ spots=[max(table_p[q],table_u[q]-1)forqinqus]
+ ifmax(spots)==len(moms):
+ moms.append((Moment(),Moment([g])))
+ else:
+ moms[max(spots)][1].add_gate(g)
+ forqinqus:
+ table_u[q]=table_p[q]=max(spots)+1
+ noms=[]
+ forminmoms:
+ noms.extend([m[0],m[1]])
+
+ fornominnoms:
+ nom.sort_gates()
+ returnnoms
+
+ @property
+ defdepth(self):
+"""
+ gate depth of the abstract circuit.
+ Returns
+ -------
+ int: the depth.
+
+ """
+ returnlen(self.moments)
+
+ @property
+ defcanonical_depth(self):
+"""
+ gate depth of the abstract circuit in alternating layer form.
+ Returns
+ -------
+ int: depth of the alternating layer form.
+ """
+ returnlen(self.canonical_moments)
+
+ @property
+ defgates(self):
+ ifself._gatesisNone:
+ return[]
+ else:
+ returnself._gates
+
+ @property
+ defnumbering(self)->BitNumbering:
+ returnBitNumbering.LSB
+
+ @property
+ defqubits(self):
+ accumulate=[]
+ forginself.gates:
+ accumulate+=list(g.qubits)
+ returnsorted(list(set(accumulate)))
+
+ @property
+ defn_qubits(self):
+ returnmax(self.max_qubit()+1,self._min_n_qubits)
+
+ @n_qubits.setter
+ defn_qubits(self,other):
+ self._min_n_qubits=other
+ ifother<self.max_qubit()+1:
+ raiseTequilaException(
+ "You are trying to set n_qubits to "+str(
+ other)+" but your circuit needs at least: "+str(
+ self.max_qubit()+1))
+ returnself
+
+ def__init__(self,gates=None,parameter_map=None):
+"""
+ init
+ Parameters
+ ----------
+ gates:
+ (Default value = None)
+ the gates to include in the circuit.
+ parameter_map:
+ (Default value = None)
+ mapping to indicate where in the circuit certain parameters appear.
+ """
+ self._n_qubits=None
+ self._min_n_qubits=0
+ ifgatesisNone:
+ self._gates=[]
+ else:
+ self._gates=list(gates)
+
+ ifparameter_mapisNone:
+ self._parameter_map=self.make_parameter_map()
+ else:
+ self._parameter_map=parameter_map
+
+
+[docs]
+ defmake_parameter_map(self)->dict:
+"""
+ Returns
+ -------
+ ParameterMap of the circuit: A dictionary with
+ keys: variables in the circuit
+ values: list of all gates and their positions in the circuit
+ e.g. result[Variable("a")] = [(3, Rx), (5, Ry), ...]
+ """
+ parameter_map=defaultdict(list)
+ foridx,gateinenumerate(self.gates):
+ ifgate.is_parametrized():
+ variables=gate.extract_variables()
+ forvariableinvariables:
+ parameter_map[variable]+=[(idx,gate)]
+
+ returnparameter_map
+
+
+
+[docs]
+ defis_primitive(self):
+"""
+ Check if this is a single gate wrapped in this structure
+ :return: True if the circuit is just a single gate
+ """
+ returnlen(self.gates)==1
+[docs]
+ defreplace_gates(self,positions:list,circuits:list,replace:list=None):
+"""
+ Notes
+ ----------
+ Replace or insert gates at specific positions into the circuit
+ at different positions (faster than multiple calls to replace_gate)
+
+ Parameters
+ ----------
+ positions: list of int:
+ the positions at which the gates should be added. Always refer to the positions in the original circuit
+ circuits: list or QCircuit:
+ the gates to add at the corresponding positions
+ replace: list of bool: (Default value: None)
+ Default is None which corresponds to all true
+ decide if gates shall be replaces or if the new parts shall be inserted without replacement
+ if replace[i] = true: gate at position [i] will be replaces by gates[i]
+ if replace[i] = false: gates[i] circuit will be inserted at position [i] (beaming before gate previously at position [i])
+ Returns
+ -------
+ new circuit with inserted gates
+ """
+
+ assertlen(circuits)==len(positions)
+ ifreplaceisNone:
+ replace=[True]*len(circuits)
+ else:
+ assertlen(circuits)==len(replace)
+
+ dataset=zip(positions,circuits,replace)
+ dataset=sorted(dataset,key=lambdax:x[0])
+
+ offset=0
+ new_gatelist=self.gates
+ foridx,circuit,do_replaceindataset:
+
+ # failsafe
+ ifhasattr(circuit,"gates"):
+ gatelist=circuit.gates
+ elifisinstance(circuit,typing.Iterable):
+ gatelist=circuit
+ else:
+ gatelist=[circuit]
+
+ pos=idx+offset
+ ifdo_replace:
+ new_gatelist=new_gatelist[:pos]+gatelist+new_gatelist[pos+1:]
+ offset+=len(gatelist)-1
+ else:
+ new_gatelist=new_gatelist[:pos]+gatelist+new_gatelist[pos:]
+ offset+=len(gatelist)
+
+ result=QCircuit(gates=new_gatelist)
+ result.n_qubits=max(result.n_qubits,self.n_qubits)
+ returnresult
+[docs]
+ defdagger(self):
+"""
+ Returns
+ ------
+ QCircuit:
+ The adjoint of the circuit
+ """
+ result=QCircuit()
+ forginreversed(self.gates):
+ result+=g.dagger()
+ returnresult
+
+
+
+[docs]
+ defextract_variables(self)->list:
+"""
+ return a list containing all the variable objects contained in any of the gates within the unitary
+ including those nested within gates themselves.
+
+ Returns
+ -------
+ list:
+ the variables of the circuit
+ """
+ returnlist(self._parameter_map.keys())
+
+
+
+[docs]
+ defmax_qubit(self):
+"""
+ Returns:
+ int:
+ Highest index of qubits in the circuit
+ """
+ qmax=0
+ forginself.gates:
+ qmax=max(qmax,g.max_qubit)
+ returnqmax
+
+
+
+[docs]
+ defis_fully_parametrized(self):
+"""
+ Returns
+ -------
+ bool:
+ whether or not all gates in the circuit are paremtrized
+ """
+ forgateinself.gates:
+ ifnotgate.is_parametrized():
+ returnFalse
+ else:
+ ifhasattr(gate,'parameter'):
+ ifnothasattr(gate.parameter,'wrap'):
+ returnFalse
+ else:
+ continue
+ else:
+ continue
+ returnTrue
+
+
+
+[docs]
+ defis_fully_unparametrized(self):
+"""
+ Returns
+ -------
+ bool:
+ whether or not all gates in the circuit are unparametrized
+ """
+ forgateinself.gates:
+ ifnotgate.is_parametrized():
+ continue
+ else:
+ ifhasattr(gate,'parameter'):
+ ifnothasattr(gate.parameter,'wrap'):
+ continue
+ else:
+ returnFalse
+ else:
+ returnFalse
+ returnTrue
+[docs]
+ @staticmethod
+ defwrap_gate(gate:QGateImpl):
+"""
+ take a gate and return a qcircuit containing only that gate.
+ Parameters
+ ----------
+ gate: QGateImpl
+ the gate to wrap in a circuit.
+
+ Returns
+ -------
+ QCircuit:
+ a one gate circuit.
+ """
+ ifisinstance(gate,QCircuit):
+ returngate
+ ifisinstance(gate,list):
+ returnQCircuit(gates=gate)
+ else:
+ returnQCircuit(gates=[gate])
+
+
+
+[docs]
+ defto_networkx(self):
+"""
+ Turn a given quantum circuit from tequila into graph form via NetworkX
+ :param self: tq.gates.QCircuit
+ :return: G, a graph in NetworkX with qubits as nodes and gate connections as edges
+ """
+ # avoiding dependcies (only used here so far)
+ importnetworkxasnx
+ G=nx.Graph()
+ forqinself.qubits:
+ G.add_node(q)
+ Gdict={s:[]forsinself.qubits}
+ forgateinself.gates:
+ ifgate.control:
+ forsingate.control:
+ fortingate.target:
+ tstr=''
+ tstr+=str(t)
+ target=int(tstr)
+ Gdict[s].append(target)# add target to key of correlated controls
+ forpingate.target:
+ forringate.control:
+ cstr=''
+ cstr+=str(r)
+ control=int(cstr)
+ Gdict[p].append(control)# add control to key of correlated targets
+ else:
+ forsingate.target:
+ fortingate.target:
+ tstr2=''
+ tstr2+=str(t)
+ target2=int(tstr2)
+ Gdict[s].append(target2)
+ lConn=[]# List of connections between qubits
+ fora,binGdict.items():
+ forqinb:
+ lConn.append((a,q))
+ G.add_edges_from(lConn)
+ GPaths=list(nx.connected_components(
+ G))# connections of various qubits, excluding repetitions (ex- (1,3) instead of (1,3) and (3,1))
+ GIso=[gforginGPathsiflen(g)==1]# list of Isolated qubits
+ returnG
+
+
+
+[docs]
+ @staticmethod
+ deffrom_moments(moments:typing.List):
+"""
+ build a circuit from Moment objects.
+
+ Parameters
+ ----------
+ moments: list:
+ a list of Moment objects.
+
+ Returns
+ -------
+ """
+ c=QCircuit()
+ forminmoments:
+ c+=m.as_circuit()
+ returnc
+
+
+
+[docs]
+ defverify(self)->bool:
+"""
+ make sure self is built properly and of the correct types.
+ Returns
+ -------
+ bool:
+ whether or not the circuit is properly constructed.
+
+ """
+ fork,v,inself._parameter_map.items():
+ test=[self.gates[x[0]]==x[1]forxinv]
+ test+=[kinself._gates[x[0]].extract_variables()forxinv]
+ returnall(test)
+
+
+
+[docs]
+ defmap_qubits(self,qubit_map):
+"""
+
+ E.G. Rx(1)Ry(2) --> Rx(3)Ry(1) with qubit_map = {1:3, 2:1}
+
+ Parameters
+ ----------
+ qubit_map
+ a dictionary which maps old to new qubits
+
+ Returns
+ -------
+ A new circuit with mapped qubits
+ """
+
+ new_gates=[gate.map_qubits(qubit_map)forgateinself.gates]
+ # could speed up by applying qubit_map to parameter_map here
+ # currently its recreated in the init function
+ returnQCircuit(gates=new_gates)
+
+
+
+[docs]
+ defadd_controls(self,control,inpl:typing.Optional[bool]=False) \
+ ->typing.Optional[QCircuit]:
+"""Depending on the truth value of inpl:
+ - return controlled version of self with control as the control qubits if inpl;
+ - mutate self so that the qubits in control are added as the control qubits if not inpl.
+
+ Raise ValueError if there any qubits in common between self and control.
+ """
+ ifinpl:
+ self._inpl_control_circ(control)
+ returnself
+ else:
+ # return self._return_control_circ(control)
+ circ=copy.deepcopy(self)
+ returncirc.add_controls(control,inpl=True)
+
+
+ def_return_control_circ(self,control)->QCircuit:
+"""Return controlled version of self with control as the control qubits.
+
+ This is not an in-place method, so it DOES NOT mutates self, and only returns a circuit.
+
+ Raise TequilaWarning if there any qubits in common between self and control.
+ """
+ control=list(set(list_assignment(control)))
+
+ gates=self.gates
+ cgates=[]
+
+ forgateingates:
+ cgate=copy.deepcopy(gate)
+
+ ifcgate.is_controlled():
+ control_lst=list(set(list(cgate.control)+list(control)))
+
+ iflen(control_lst)<len(gate.control)+len(control):
+ # warnings.warn("Some of the controls {} were already included in the control "
+ # "of a gate {}.".format(control, gate), TequilaWarning)
+ raiseTequilaWarning(f'Some of the controls {control} were already included '
+ f'in the control of a gate {gate}.')
+ else:
+ control_lst,not_control=list(control),list()
+
+ # Raise TequilaWarning if target and control are the same qubit
+ ifany(qubitincontrolforqubitinnot_control):
+ # warnings.warn("The target and control {} were the same qubit for a gate {}."
+ # .format(control, gate), TequilaWarning)
+ raiseTequilaWarning(f'The target for a gate {gate} '
+ f'and the control list {control_lst} had a common qubit.')
+
+ cgate._control=tuple(control_lst)
+ cgate.finalize()
+ cgates.append(cgate)
+
+ returnQCircuit(gates=cgates)
+
+ def_inpl_control_circ(self,control)->None:
+"""Mutate self such that the qubits in control are added as the control qubits
+
+ This is an in-place method, so it mutates self and doesn't return any value.
+
+ Raise TequilaWarning if there any qubits in common between self and control.
+ """
+ gates=self.gates
+ control=list_assignment(control)
+
+ forgateingates:
+ ifgate.is_controlled():
+ control_lst=list(set(list(gate.control)+list(control)))
+
+ # Need to check duplicates
+ not_control=list(set(qubitforqubitinlist(gate.qubits)
+ ifqubitnotinlist(gate.control)))
+
+ # Raise TequilaWarning if control qubit is duplicated
+ iflen(control_lst)<len(gate.control)+len(control):
+ # warnings.warn("Some of the controls {} were already included in the control "
+ # "of a gate {}.".format(control, gate), TequilaWarning)
+ raiseTequilaWarning(f'Some of the controls {control} were already included '
+ f'in the control of a gate {gate}.')
+ else:
+ control_lst,not_control=list(control),list()
+
+ # Raise TequilaWarning if target and control are the same qubit
+ ifany(qubitincontrolforqubitinnot_control):
+ # warnings.warn("The target and control {} were the same qubit for a gate {}."
+ # .format(control, gate), TequilaWarning)
+ raiseTequilaWarning(f'The target for a gate {gate} '
+ f'and the control list {control} had a common qubit.')
+
+ gate._control=tuple(control_lst)
+ gate.finalize()
+
+
+[docs]
+ defmap_variables(self,variables:dict,*args,**kwargs):
+"""
+
+ Parameters
+ ----------
+ variables
+ dictionary with old variable names as keys and new variable names or values as values
+ Returns
+ -------
+ Circuit with changed variables
+
+ """
+
+ variables={assign_variable(k):assign_variable(v)fork,vinvariables.items()}
+
+ # failsafe
+ my_variables=self.extract_variables()
+ fork,vinvariables.items():
+ ifknotinmy_variables:
+ warnings.warn(
+ "map_variables: variable {} is not part of circuit with variables {}".format(k,
+ my_variables),
+ TequilaWarning)
+
+ new_gates=[copy.deepcopy(gate).map_variables(variables)forgateinself.gates]
+
+ returnQCircuit(gates=new_gates)
+
+
+
+
+
+[docs]
+classMoment(QCircuit):
+"""
+ the class which represents a set of simultaneously applicable gates.
+
+ Methods
+ -------
+ with_gate:
+ attempt to add a gate to the moment, replacing any gate it may conflict with.
+ with_gates:
+ attempt to add multiple gates to the moment, replacing any gates they may conflict with.
+
+ """
+
+ @property
+ defmoments(self):
+ return[self]
+
+ @property
+ defcanonical_moments(self):
+"""
+ Break self up into parametrized and unparametrized layers.
+ Returns
+ -------
+ list:
+ list of 2 Moments, one of which may be empty.
+ """
+ mu=[]
+ mp=[]
+ forgateinself.gates:
+ ifnotgate.is_parametrized():
+ mu.append(gate)
+ else:
+ ifhasattr(gate,'parameter'):
+ ifnothasattr(gate.parameter,'wrap'):
+ mu.append(gate)
+ else:
+ mp.append(gate)
+ else:
+ mp.append(gate)
+ return[Moment(mu),Moment(mp)]
+
+ @property
+ defdepth(self):
+ return1
+
+ @property
+ defcanonical_depth(self):
+ return2
+
+ def__init__(self,gates:typing.List[QGateImpl]=None,sort=False):
+"""
+ initialize a moment from gates.
+ Parameters
+ ----------
+ gates: list:
+ a list of gates. Can be None.
+ sort:
+ whether or not to sort the gates into order from lowest to highest qubit designate.
+ """
+ super().__init__(gates=gates)
+ occ=[]
+ ifgatesisnotNone:
+ forginlist(gates):
+ forqing.qubits:
+ ifqinocc:
+ raiseTequilaException(
+ 'cannot have doubly occupied qubits, which occurred at qubit {}'.format(
+ str(q)))
+ else:
+ occ.append(q)
+ ifsort:
+ self.sort_gates()
+
+
+[docs]
+ defwith_gate(self,gate:typing.Union[QCircuit,QGateImpl]):
+"""
+ Add a gate, or else replace some gate with it.
+
+ Parameters
+ ----------
+ gate:
+ the gate to insert into the moment.
+
+ Returns
+ -------
+ Moment:
+ a New moment with the proper gates.
+
+ """
+ prev=self.qubits
+ newq=gate.qubits
+ overlap=[]
+ forninnewq:
+ ifninprev:
+ overlap.append(n)
+
+ gates=copy.deepcopy(self.gates)
+ iflen(overlap)==0:
+ gates.append(gate)
+ else:
+ fori,ginenumerate(gates):
+ ifany([qinoverlapforqing.qubits]):
+ delg
+ gates.append(gate)
+
+ returnMoment(gates=gates)
+
+
+
+[docs]
+ defwith_gates(self,gates):
+"""
+ with gate, but on multiple gates.
+
+ Parameters
+ ----------
+ gates:
+ list of QGates
+
+ Returns
+ -------
+ Moment:
+ a new Moment, with the desired gates insert into self.
+
+ """
+ gl=list(gates)
+ first_overlap=[]
+ forgingl:
+ forqing.qubits:
+ ifqnotinfirst_overlap:
+ first_overlap.append(q)
+ else:
+ raiseTequilaException(
+ 'cannot have a moment with multiple operations acting on the same qubit!')
+
+ new=self.with_gate(gl[0])
+ forgingl[1:]:
+ new=new.with_gate(g)
+ new.sort_gates()
+ returnnew
+
+
+
+[docs]
+ defadd_gate(self,gate:typing.Union[QCircuit,QGateImpl]):
+"""
+ add a gate to the moment.
+
+ Parameters
+ ----------
+ gate:
+ the desired gate.
+
+ Returns
+ -------
+ Moment
+ a new moment, of self + a new gate
+ """
+ prev=self.qubits
+ newq=gate.qubits
+ forninnewq:
+ ifninprev:
+ raiseTequilaException(
+ 'cannot add gate {} to moment; qubit {} already occupied.'.format(str(gate),
+ str(n)))
+
+ self._gates.append(gate)
+ self.sort_gates()
+ returnself
+
+
+
+[docs]
+ defreplace_gate(self,position,gates):
+"""
+ substitute a gate at a given position with other gates.
+ Parameters
+ ----------
+ position:
+ where in the list of gates the gate to be replaced occurs.
+ gates:
+ the gates to replace the unwanted gate with.
+
+ Returns
+ -------
+ QCircuit or Moment:
+ self, with unwanted gate removed and new gates inserted. May not be a moment.
+ """
+ ifhasattr(gates,'__iter__'):
+ gs=gates
+ else:
+ gs=[gates]
+
+ new=self.gates[:position]
+ new.extend(gs)
+ new.extend(self.gates[(position+1):])
+ try:
+ returnMoment(gates=new)
+ except:
+ returnQCircuit(gates=new)
+
+
+
+[docs]
+ defas_circuit(self):
+"""
+ convert back into the unrestricted QCircuit.
+ Returns
+ -------
+ QCircuit:
+ a circuit with the same gates as self.
+ """
+ returnQCircuit(gates=self.gates)
+
+
+
+[docs]
+ deffully_parametrized(self):
+"""
+ Todo: Why not just inherit from base?
+ Returns
+ -------
+ bool:
+ whether or not EVERY gate in self.gates is parameterized.
+ """
+ forgateinself.gates:
+ ifnotgate.is_parametrized():
+ returnFalse
+ else:
+ ifhasattr(gate,'parameter'):
+ ifnothasattr(gate.parameter,'wrap'):
+ returnFalse
+ else:
+ continue
+ else:
+ continue
+ returnTrue
+[docs]
+ @staticmethod
+ defwrap_gate(gate:QGateImpl):
+"""
+ Parameters
+ ----------
+ gate: QGateImpl:
+ the gate, to wrap as a moment
+
+ Returns
+ -------
+ Moment:
+ a moment with one gate in it.
+ """
+ ifisinstance(gate,QCircuit):
+ returngate
+ else:
+ returnMoment(gates=[gate])
+
+
+
+[docs]
+ @staticmethod
+ deffrom_moments(moments:typing.List):
+"""
+ Raises
+ ------
+ TequilaException
+ """
+ raiseTequilaException(
+ 'this method should never be called from Moment. Call from the QCircuit class itself instead.')
+
+
+
+
+
+
+[docs]
+deffind_unused_qubit(U0:QCircuit=None,U1:QCircuit=None)->int:
+'''
+ Function that checks which are the active qubits of two circuits and
+ provides an unused qubit that is not among them. If all qubits are used
+ it adds a new one.
+
+ Parameters
+ ----------
+ U0 : QCircuit, corresponding to the first state.
+
+ U1 : QCircuit, corresponding to the second state.
+
+ Returns
+ -------
+ control_qubit : int
+
+ '''
+
+ active_qubits=list(set(U0.qubits+U1.qubits))
+ # default
+ free_qubit=max(active_qubits)+1
+ # see if we can use another one
+ forninrange(max(active_qubits)+1):
+ ifnnotinactive_qubits:
+ free_qubit=n
+ break
+ assertfree_qubitnotinactive_qubits
+
+ returnfree_qubit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/circuit/compiler.html b/docs/sphinx/_modules/tequila_code/circuit/compiler.html
new file mode 100644
index 0000000..d050fa6
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/circuit/compiler.html
@@ -0,0 +1,1215 @@
+
+
+
+
+
+
+
+ tequila_code.circuit.compiler — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+classCircuitCompiler:
+"""
+ an object that performs abstract compilation of QCircuits and Objectives.
+
+ Note
+ ----
+ see init for attributes, since all are specified there
+
+ Methods
+ -------
+ compile_objective
+ perform compilation on an entire objective
+ compile_objective_argument
+ perform compilation on a single arg of objective
+ compile_circuit:
+ perform compilation on a circuit.
+ """
+
+
+[docs]
+ @classmethod
+ defall_flags_true(cls,*args,**kwargs):
+ # convenience: Initialize with all flags set to true
+ # set exceptions in kwargs
+ c=cls()
+ forkinc.__dict__.keys():
+ try:
+ c.__dict__[k]=True
+ except:
+ pass
+ fork,vinkwargs.items():
+ ifkinc.__dict__:
+ c.__dict__[k]=v
+ c.gradient_mode=False
+
+ ifnotc.multicontrol:
+ c.cc_max=False
+ returnc
+
+
+
+[docs]
+ @classmethod
+ defstandard_gate_set(cls,*args,**kwargs):
+ # convenience: Initialize with all flags set to true
+ # but not for standard gates like ry
+ # set exceptions in kwargs
+ c=cls.all_flags_true()
+ c.gradient_mode=False
+ c.y_gate=False
+ c.ry_gate=False
+
+ fork,vinkwargs.items():
+ ifkinc.__dict__:
+ c.__dict__[k]=v
+
+ ifnotc.multicontrol:
+ c.cc_max=False
+ returnc
+
+
+ def__init__(self,
+ multitarget=False,
+ multicontrol=False,
+ trotterized=False,
+ generalized_rotation=False,
+ exponential_pauli=False,
+ controlled_exponential_pauli=False,
+ hadamard_power=False,
+ controlled_power=False,
+ power=False,
+ toffoli=False,
+ controlled_phase=False,
+ phase=False,
+ phase_to_z=False,
+ controlled_rotation=False,
+ swap=False,
+ cc_max=False,
+ gradient_mode=False,
+ ry_gate=False,
+ y_gate=False,
+ ch_gate=False,
+ hadamard=False
+ ):
+
+"""
+ all parameters are booleans.
+ Parameters
+ ----------
+ multitarget:
+ whether or not to split multitarget gates into single target (if gate isn't inherently multitarget)
+ multicontrol:
+ whether or not to split gates into single controlled gates.
+ trotterized:
+ whether or not to break down TrotterizedGateImpl into other types
+ generalized_rotation:
+ whether or not to break down GeneralizedRotationGateImpl into other types
+ exponential_pauli:
+ whether or not to break down ExponentialPauliGateImpl into other types
+ controlled_exponential_pauli
+ whether or not to break down controlled exponential pauli gates.
+ hadamard_power:
+ whether or not to break down Hadamard gates, raised to a power, into other rotation gates.
+ controlled_power:
+ whether or not to break down controlled power gates into CNOT and other gates.
+ power:
+ whether or not to break down parametrized power gates into rotation gates
+ toffoli:
+ whether or not to break down the toffoli gate into CNOTs and other single qubit gates.
+ controlled_phase:
+ whether or not to break down controlled phase gates into CNOTs and phase gates.
+ phase:
+ whether to replace phase gates
+ phase_to_z:
+ specifically, whether to replace phase gates with the z gate
+ controlled_rotation:
+ whether or not to break down controlled rotation gates into CNot and single qubit gates
+ swap:
+ whether or not to break down swap gates into CNOT gates.
+ cc_max:
+ whether or not to break down all controlled gates with 2 or more controls.
+ ry_gate:
+ whether or not to break down all rotational y gates
+ y_gate:
+ whether or not to break down all y gates
+ ch_gate:
+ whether or not to break down all controlled-H gates
+ """
+ self.multitarget=multitarget
+ self.multicontrol=multicontrol
+ self.generalized_rotation=generalized_rotation
+ self.trotterized=trotterized
+ self.exponential_pauli=exponential_pauli
+ self.controlled_exponential_pauli=controlled_exponential_pauli
+ self.hadamard_power=hadamard_power
+ self.hadamard=hadamard
+ self.controlled_power=controlled_power
+ self.power=power
+ self.toffoli=toffoli
+ self.controlled_phase=controlled_phase
+ self.phase=phase
+ self.phase_to_z=phase_to_z
+ self.controlled_rotation=controlled_rotation
+ self.swap=swap
+ self.cc_max=cc_max
+ self.gradient_mode=gradient_mode
+ self.ry_gate=ry_gate
+ self.y_gate=y_gate
+ self.ch_gate=ch_gate
+
+ def__call__(self,objective:typing.Union[Objective,QCircuit,ExpectationValueImpl],variables=None,*args,
+ **kwargs):
+
+"""
+ Perform compilation
+ Parameters
+ ----------
+ objective:
+ the object (not necessarily an objective) to compile.
+ variables: optional:
+ Todo: Jakob, what is this for?
+ args
+ kwargs
+
+ Returns
+ -------
+ a compiled version of objective
+ """
+
+ ifisinstance(objective,Objective)orhasattr(objective,"args"):
+ result=self.compile_objective(objective=objective,variables=variables,*args,**kwargs)
+ elifisinstance(objective,QCircuit)orhasattr(objective,"gates"):
+ result=self.compile_circuit(abstract_circuit=objective,variables=variables,*args,**kwargs)
+ elifisinstance(objective,ExpectationValueImpl)orhasattr(objective,"U"):
+ result=self.compile_objective_argument(arg=objective,variables=variables,*args,**kwargs)
+ else:
+ raiseTequilaCompilerException("Tequila compiler can't process type {}".format(type(objective)))
+
+ returnresult
+
+
+[docs]
+defcompiler(f):
+"""
+ Decorator for compile functions.
+
+ Make them applicable for single gates as well as for whole circuits
+ Note that all arguments need to be passed as keyword arguments
+ """
+
+ defwrapper(gate,**kwargs):
+ ifhasattr(gate,"gates"):
+ result=QCircuit()
+ forgingate.gates:
+ result+=f(gate=g,**kwargs)
+ returnresult
+
+ elifhasattr(gate,'U'):
+ cU=QCircuit()
+ forgingate.U.gates:
+ cU+=f(gate=g,**kwargs)
+ returntype(gate)(U=cU,H=gate.H)
+ elifhasattr(gate,'transformations'):
+ outer=[]
+ forargsingate.argsets:
+ compiled=[]
+ forEinargs:
+ ifhasattr(E,'name'):
+ compiled.append(E)
+ else:
+ cU=QCircuit()
+ forginE.U.gates:
+ cU+=f(gate=g,**kwargs)
+ compiled.append(type(E)(U=cU,H=E.H))
+ outer.append(compiled)
+ ifisinstance(gate,Objective):
+ returntype(gate)(args=outer[0],transformation=gate._transformation)
+ else:
+ returnf(gate=gate,**kwargs)
+
+ returnwrapper
+
+
+
+
+[docs]
+defchange_basis(target,axis=None,name=None,daggered=False):
+"""
+ helper function; returns circuit that performs change of basis.
+ Parameters
+ ----------
+ target:
+ the qubit having its basis changed
+ axis:
+ The axis of rotation to shift into.
+ daggered: bool:
+ adjusts the sign of the gate if axis = 1, I.E, change of basis about Y axis.
+
+ Returns
+ -------
+ QCircuit that performs change of basis on target qubit onto desired axis
+
+ """
+ ifaxisisNoneandnameisNone:
+ raiseTequilaException('axis or name must be given.')
+
+ ifname:
+ name=name.lower()
+ ifnamein['h','hadamard']anddaggered:
+ returnRy(angle=numpy.pi/4,target=target)
+ elifnamein['h','hadamard']:
+ returnRy(angle=-numpy.pi/4,target=target)
+ else:
+ name_to_axis={'rx':0,'ry':1,'rz':2}
+ axis=name_to_axis.get(name,name)
+
+ ifisinstance(axis,str):
+ axis=RotationGateImpl.string_to_axis[axis.lower()]
+
+ ifaxis==0anddaggered:
+ returnRy(angle=numpy.pi/2,target=target)
+ elifaxis==0:
+ returnRy(angle=-numpy.pi/2,target=target)
+ elifaxis==1anddaggered:
+ returnRx(angle=-numpy.pi/2,target=target)
+ elifaxis==1:
+ returnRx(angle=numpy.pi/2,target=target)
+ else:
+ returnQCircuit()
+
+
+
+[docs]
+@compiler
+defcompile_multitarget(gate,*args,**kwargs)->QCircuit:
+"""
+ If a gate is 'trivially' multitarget, split it into single target gates.
+ Parameters
+ ----------
+ gate:
+ the gate in question
+
+ Returns
+ -------
+ QCircuit, the result of compilation.
+ """
+ targets=gate.target
+
+ # don't compile real multitarget gates
+ ifhasattr(gate,"generator")orhasattr(gate,"generators")orhasattr(gate,"paulistring"):
+ returnQCircuit.wrap_gate(gate)
+
+ ifisinstance(gate,ExponentialPauliGateImpl)orisinstance(gate,TrotterizedGateImpl):
+ returnQCircuit.wrap_gate(gate)
+
+ iflen(targets)==1:
+ returnQCircuit.wrap_gate(gate)
+
+ ifgate.name.lower()in["swap","iswap"]:
+ returnQCircuit.wrap_gate(gate)
+
+ result=QCircuit()
+ fortintargets:
+ gx=copy.deepcopy(gate)
+ gx._target=(t,)
+ result+=gx
+
+ returnresult
+
+
+
+# return index of control qubits in Gray Code order.
+def_pattern(n):
+ ifn==1:
+ return[0]
+ pn=_pattern(n-1)
+
+ returnpn+[n-1]+pn
+
+
+
+[docs]
+@compiler
+defcompile_controlled_rotation(gate:RotationGateImpl)->QCircuit:
+"""
+ Recompilation of a controlled-rotation gate
+ Basis change into Rz then recompilation of controled Rz, then change basis back
+ :param gate: The rotational gate
+ :return: set of gates wrapped in QCircuit class
+ """
+
+ ifnotgate.is_controlled():
+ returnQCircuit.wrap_gate(gate)
+
+ ifnotisinstance(gate,RotationGateImpl):
+ returnQCircuit.wrap_gate(gate)
+
+ iflen(gate.target)>1:
+ returncompile_controlled_rotation(gate=compile_multitarget(gate=gate))
+
+ target=gate.target
+ control=gate.control
+ k=len(control)
+ cind=_pattern(k)+[k-1]
+
+ result=QCircuit()
+ result+=change_basis(target=target,axis=gate._axis)
+ coeff=-1/pow(2,k)
+ fori,ciinenumerate(cind):
+ coeff*=-1
+
+ result+=Rz(target=target,angle=coeff*gate.parameter)
+ result+=CNOT(control[ci],target)
+ result+=change_basis(target=target,axis=gate._axis,daggered=True)
+
+ result.n_qubits=result.max_qubit()+1
+ returnresult
+
+
+
+
+[docs]
+@compiler
+defcompile_to_single_control(gate)->QCircuit:
+"""
+ break down a gate into a sequence with no more than single-controlled gates.
+ Parameters
+ ----------
+ gate:
+ the gate.
+
+ Returns
+ -------
+ A QCircuit; the result of compilation.
+ """
+ ifnotgate.is_controlled:
+ returnQCircuit.wrap_gate(gate)
+ cl=len(gate.control)
+ target=gate.target
+ control=gate.control
+ ifcl<=1:
+ returnQCircuit.wrap_gate(gate)
+ name=gate.name
+ back=QCircuit()
+ ifnamein['X','x','Y','y','Z','z','H','h']:
+ ifisinstance(gate,PowerGateImpl):
+ power=gate.parameter
+ else:
+ power=1.0
+ new=PowerGateImpl(name=name,power=power,target=target,control=control,generator=gate.make_generator())
+ partial=compile_power_gate(gate=new)
+ back+=compile_to_single_control(gate=partial)
+ elifisinstance(gate,RotationGateImpl):
+ partial=compile_controlled_rotation(gate=gate)
+ back+=compile_to_single_control(gate=partial)
+ elifisinstance(gate,PhaseGateImpl):
+ partial=compile_controlled_phase(gate=gate)
+ back+=compile_to_single_control(gate=partial)
+ else:
+ print(gate)
+ raiseTequilaException('frankly, what the fuck is this gate?')
+ returnback
+
+
+
+
+[docs]
+@compiler
+defcompile_toffoli(gate)->QCircuit:
+"""
+ break down a toffoli gate into a sequence of CNOT and single qubit gates.
+ Parameters
+ ----------
+ gate:
+ the gate.
+
+ Returns
+ -------
+ A QCircuit; the result of compilation.
+ """
+
+ ifgate.name.lower!='x':
+ returnQCircuit.wrap_gate(gate)
+ control=gate.control
+ c1=control[1]
+ c0=control[0]
+ target=gate.target
+ result=QCircuit()
+ result+=H(target)
+ result+=CNOT(c1,target)
+ result+=T(target).dagger()
+ result+=CNOT(c0,target)
+ result+=T(target)
+ result+=CNOT(c1,target)
+ result+=T(target).dagger()
+ result+=CNOT(c0,target)
+ result+=T(c1)
+ result+=T(target)
+ result+=CNOT(c0,c1)
+ result+=H(target)
+ result+=T(c0)
+ result+=T(c1).dagger()
+ result+=CNOT(c0,c1)
+
+ return(result)
+
+
+
+
+[docs]
+@compiler
+defcompile_power_gate(gate)->QCircuit:
+"""
+ break down power gates into the rotation gates.
+ Parameters
+ ----------
+ gate:
+ the gate.
+
+ Returns
+ -------
+ A QCircuit; the result of compilation.
+ """
+ ifnotisinstance(gate,PowerGateImpl):
+ returnQCircuit.wrap_gate(gate)
+ ifnotgate.is_controlled():
+ returncompile_power_base(gate=gate)
+
+ returncompile_controlled_power(gate=gate)
+
+
+
+
+[docs]
+@compiler
+defcompile_power_base(gate):
+"""
+ Base case of compile_power_gate: convert a 1-qubit parametrized power gate into rotation gates.
+ Parameters
+ ----------
+ gate:
+ the gate.
+
+ Returns
+ -------
+ A QCircuit; the result of compilation.
+ """
+ ifnotisinstance(gate,PowerGateImpl):
+ returnQCircuit.wrap_gate(gate)
+
+ ifgate.is_controlled():
+ returnQCircuit.wrap_gate(gate)
+
+ power=gate.power
+ ifgate.name.lower()in['h','hadamard']:
+ ### off by global phase of Exp[ pi power /2]
+ theta=power*numpy.pi
+
+ result=QCircuit()
+ result+=Ry(angle=-numpy.pi/4,target=gate.target)
+ result+=Rz(angle=theta,target=gate.target)
+ result+=Ry(angle=numpy.pi/4,target=gate.target)
+ elifgate.name=='X':
+ ### off by global phase of Exp[ pi power /2]
+'''
+ if we wanted to do it formally we would use the following
+ a=-numpy.pi/2
+ b=numpy.pi/2
+ theta = power*numpy.pi
+
+ result = QCircuit()
+ result+= Rz(angle=b,target=gate.target)
+ result+= Ry(angle=theta,target=gate.target)
+ result+= Rz(angle=a,target=gate.target)
+ '''
+ result=Rx(angle=power*numpy.pi,target=gate.target)
+ elifgate.name=='Y':
+ ### off by global phase of Exp[ pi power /2]
+ theta=power*numpy.pi
+
+ result=QCircuit()
+ result+=Ry(angle=theta,target=gate.target)
+ elifgate.name=='Z':
+ ### off by global phase of Exp[ pi power /2]
+ a=0
+ b=power*numpy.pi
+ theta=0
+ result=QCircuit()
+ result+=Rz(angle=b,target=gate.target)
+ else:
+ raiseTequilaException('passed a gate with name '+gate.name+', which cannot be handled!')
+ returnresult
+
+
+
+
+[docs]
+@compiler
+defcompile_controlled_power(gate:PowerGateImpl)->QCircuit:
+"""
+ Recompilation of a controlled-power gate
+ Basis change into Z then recompilation of controled Z, then change basis back
+ :param gate: The power gate
+ :return: set of gates wrapped in QCircuit class
+ """
+ ifnotgate.is_controlled():
+ returnQCircuit.wrap_gate(gate)
+
+ ifnotisinstance(gate,PowerGateImpl):
+ returnQCircuit.wrap_gate(gate)
+
+ iflen(gate.target)>1:
+ returncompile_controlled_power(gate=compile_multitarget(gate=gate))
+
+ power=gate.power
+ target=gate.target
+ control=gate.control
+
+ result=QCircuit()
+ result+=Phase(target=control[0],control=control[1:],phi=power*pi/2)
+ result+=change_basis(target=target,name=gate.name)
+ result+=Rz(target=target,control=control,angle=power*pi)
+ result+=change_basis(target=target,name=gate.name,daggered=True)
+
+ result.n_qubits=result.max_qubit()+1
+ returnresult
+
+
+
+
+[docs]
+@compiler
+defcompile_phase(gate)->QCircuit:
+"""
+ Compile phase gates into Rz gates and cnots, if controlled
+ Parameters
+ ----------
+ gate:
+ the gate
+
+ Returns
+ -------
+ QCircuit, the result of compilation.
+ """
+ ifnotisinstance(gate,PhaseGateImpl):
+ returnQCircuit.wrap_gate(gate)
+ phase=gate.parameter
+ result=QCircuit()
+ iflen(gate.control)==0:
+ returnRz(angle=phase,target=gate.target)
+
+ result=compile_controlled_phase(gate)
+ result=compile_phase(result)
+ returnresult
+[docs]
+@compiler
+defcompile_phase_to_z(gate)->QCircuit:
+"""
+ Compile phase gate to parametrized Z gate.
+ Parameters
+ ----------
+ gate:
+ the gate.
+
+ Returns
+ -------
+ QCircuit, the result of compilation.
+
+ """
+ ifnotisinstance(gate,PhaseGateImpl):
+ returnQCircuit.wrap_gate(gate)
+ phase=gate.parameter
+ returnZ(power=phase/pi,target=gate.target,control=gate.control)
+
+
+
+
+[docs]
+@compiler
+defcompile_swap(gate)->QCircuit:
+"""
+ Compile swap gates into CNOT.
+ Parameters
+ ----------
+ gate:
+ the gate.
+
+ Returns
+ -------
+ QCircuit, the result of compilation.
+ """
+ ifgate.name.lower()=="swap":
+ iflen(gate.target)!=2:
+ raiseTequilaCompilerException("SWAP gates needs two targets")
+ power=1
+ ifhasattr(gate,"power"):
+ ifpowerisNoneorpowerin[1,1.0]:
+ pass
+ else:
+ raiseTequilaCompilerException("Parametrized SWAPs should be decomposed on top level! Something went wrong")
+
+ c=[]
+ ifgate.controlisnotNone:
+ c=gate.control
+ returnX(target=gate.target[0],control=[gate.target[1]]) \
+ +X(target=gate.target[1],control=[gate.target[0]]+list(c),power=power) \
+ +X(target=gate.target[0],control=[gate.target[1]])
+
+ else:
+ returnQCircuit.wrap_gate(gate)
+
+
+
+
+[docs]
+@compiler
+defcompile_exponential_pauli_gate(gate)->QCircuit:
+"""
+ Returns the circuit: exp(i*angle*paulistring)
+ primitively compiled into X,Y Basis Changes and CNOTs and Z Rotations
+ :param paulistring: The paulistring in given as tuple of tuples (openfermion format)
+ like e.g ( (0, 'Y'), (1, 'X'), (5, 'Z') )
+ :param angle: The angle which parametrizes the gate -> should be real
+ :returns: the above mentioned circuit as abstract structure
+ """
+
+ ifhasattr(gate,"paulistring"):
+
+ angle=gate.paulistring.coeff*gate.parameter
+
+ circuit=QCircuit()
+
+ # the general circuit will look like:
+ # series which changes the basis if necessary
+ # series of CNOTS associated with basis changes
+ # Rz gate parametrized on the angle
+ # series of CNOT (inverted direction compared to before)
+ # series which changes the basis back
+ ubasis=QCircuit()
+ ubasis_t=QCircuit()
+ cnot_cascade=QCircuit()
+
+ last_qubit=None
+ previous_qubit=None
+ fork,vingate.paulistring.items():
+ pauli=v
+ qubit=[k]# wrap in list for targets= ...
+
+ # see if we need to change the basis
+ axis=2
+ ifpauli.upper()=="X":
+ axis=0
+ elifpauli.upper()=="Y":
+ axis=1
+ ubasis+=change_basis(target=qubit,axis=axis)
+ ubasis_t+=change_basis(target=qubit,axis=axis,daggered=True)
+
+ ifprevious_qubitisnotNone:
+ cnot_cascade+=X(target=qubit,control=previous_qubit)
+ previous_qubit=qubit
+ last_qubit=qubit
+
+ reversed_cnot=cnot_cascade.dagger()
+
+ # assemble the circuit
+ circuit+=ubasis
+ circuit+=cnot_cascade
+ circuit+=Rz(target=last_qubit,angle=angle,control=gate.control)
+ circuit+=reversed_cnot
+ circuit+=ubasis_t
+
+ returncircuit
+
+ else:
+ returnQCircuit.wrap_gate(gate)
+[docs]
+@compiler
+defcompile_ry(gate:RotationGateImpl,controlled_rotation:bool=False)->QCircuit:
+"""
+ Compile Ry gates into Rx and Rz.
+ Parameters
+ ----------
+ gate:
+ the gate.
+ controlled_rotation:
+ determines if the decomposition of the controlled-Ry gate will be performed in compile_controlled_rotation,
+ if not, decomposition will be performed here
+
+ Returns
+ -------
+ QCircuit, the result of compilation.
+ """
+ ifgate.name.lower()=="ry":
+
+ ifnot(gate.is_controlled()andcontrolled_rotation):
+
+ returnRz(target=gate.target,control=None,angle=-numpy.pi/2) \
+ +Rx(target=gate.target,control=gate.control,angle=gate.parameter) \
+ +Rz(target=gate.target,control=None,angle=numpy.pi/2)
+
+ returnQCircuit.wrap_gate(gate)
+
+
+
+
+[docs]
+@compiler
+defcompile_y(gate)->QCircuit:
+"""
+ Compile Y gates into X and Rz.
+ Parameters
+ ----------
+ gate:
+ the gate.
+
+ Returns
+ -------
+ QCircuit, the result of compilation.
+ """
+ ifgate.name.lower()=="y":
+
+ returnRz(target=gate.target,control=None,angle=-numpy.pi/2) \
+ +X(target=gate.target,control=gate.control,power=gate.powerifgate.is_parametrized()elseNone) \
+ +Rz(target=gate.target,control=None,angle=numpy.pi/2)
+
+ else:
+ returnQCircuit.wrap_gate(gate)
+
+
+
+
+[docs]
+@compiler
+defcompile_ch(gate:QGateImpl)->QCircuit:
+"""
+ Compile CH gates into its equivalent:
+ CH = Ry(0.25pi) CZ Ry(-0.25pi)
+ Parameters
+ ----------
+ gate:
+ the gate.
+
+ Returns
+ -------
+ QCircuit, the result of compilation.
+ """
+ ifgate.name.lower()=="h"andgate.is_controlled():
+
+ returnRy(target=gate.target,control=None,angle=-numpy.pi/4) \
+ +Z(target=gate.target,control=gate.control,power=gate.powerifgate.is_parametrized()elseNone) \
+ +Ry(target=gate.target,control=None,angle=numpy.pi/4)
+ else:
+ returnQCircuit.wrap_gate(gate)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/circuit/gates.html b/docs/sphinx/_modules/tequila_code/circuit/gates.html
new file mode 100644
index 0000000..d1a9ac8
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/circuit/gates.html
@@ -0,0 +1,1393 @@
+
+
+
+
+
+
+
+ tequila_code.circuit.gates — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+defPhase(target:typing.Union[list,int],
+ control:typing.Union[list,int]=None,angle:typing.Union[typing.Hashable,numbers.Number]=None,*args,
+ **kwargs)->QCircuit:
+"""
+ Notes
+ ----------
+ Initialize an abstract phase gate which acts as
+
+ .. math::
+ S(\\phi) = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i\\phi} \\end{pmatrix}
+
+ Parameters
+ ----------
+ angle
+ defines the phase, can be numeric type (static gate) or hashable non-numeric type (parametrized gate)
+ target
+ int or list of int
+ control
+ int or list of int
+
+ Returns
+ -------
+ QCircuit object
+
+ """
+
+ # ensure backward compatibility
+ if"phi"inkwargs:
+ ifangleisNone:
+ angle=kwargs["phi"]
+ else:
+ raiseException(
+ "tq.gates.Phase initialization: You gave two angles angle={} and phi={}. Please only use angle".format(
+ angle,kwargs["phi"]))
+
+ ifangleisNone:
+ angle=np.pi
+
+ target=list_assignment(target)
+ gates=[impl.PhaseGateImpl(phase=angle,target=q,control=control)forqintarget]
+
+ returnQCircuit.wrap_gate(gates)
+
+
+
+
+[docs]
+defS(target:typing.Union[list,int],control:typing.Union[list,int]=None)->QCircuit:
+"""
+ Notes
+ ----------
+
+ .. math::
+ S = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i\\frac{\\pi}{2}} \\end{pmatrix}
+
+ Parameters
+ ----------
+ target
+ int or list of int
+ control
+ int or list of int
+
+ Returns
+ -------
+ QCircuit object
+ """
+ returnPhase(angle=np.pi/2,target=target,control=control)
+
+
+
+
+[docs]
+defT(target:typing.Union[list,int],control:typing.Union[list,int]=None):
+"""
+ Notes
+ ----------
+ Fixed phase gate
+
+ .. math::
+ T = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i\\frac{\\pi}{4}} \\end{pmatrix}
+
+ Parameters
+ ----------
+ target
+ int or list of int
+ control
+ int or list of int
+
+ Returns
+ -------
+ QCircuit object
+
+ """
+ returnPhase(angle=np.pi/4,target=target,control=control)
+
+
+
+[docs]
+defRx(angle,target:typing.Union[list,int],control:typing.Union[list,int]=None,assume_real=False)->QCircuit:
+"""
+ Notes
+ ----------
+ Rx gate of the form
+
+ .. math::
+ R_{x}(\\text{angle}) = e^{-i\\frac{\\text{angle}}{2} \\sigma_{x}}
+
+
+ Parameters
+ ----------
+ angle
+ Hashable type (will be treated as Variable) or Numeric type (static angle)
+ target
+ integer or list of integers
+ control
+ integer or list of integers
+ assume_real
+ enable improved gradient compilation for controlled gates
+ Returns
+ -------
+ QCircuit object with this RotationGate
+
+ """
+ returnRotationGate(axis=0,angle=angle,target=target,control=control,assume_real=assume_real)
+
+
+
+
+[docs]
+defRy(angle,target:typing.Union[list,int],control:typing.Union[list,int]=None,assume_real=False)->QCircuit:
+"""
+ Notes
+ ----------
+ Ry gate of the form
+
+ .. math::
+ R_{y}(\\text{angle}) = e^{-i\\frac{\\text{angle}}{2} \\sigma_{y}}
+
+
+ Parameters
+ ----------
+ angle
+ Hashable type (will be treated as Variable) or Numeric type (static angle)
+ target
+ integer or list of integers
+ control
+ integer or list of integers
+
+ Returns
+ -------
+ QCircuit object with this RotationGate
+ """
+ returnRotationGate(axis=1,angle=angle,target=target,control=control,assume_real=assume_real)
+
+
+
+
+[docs]
+defRz(angle,target:typing.Union[list,int],control:typing.Union[list,int]=None,assume_real=False)->QCircuit:
+"""
+ Notes
+ ----------
+ Rz gate of the form
+
+ .. math::
+ R_{z}(\\text{angle}) = e^{-i\\frac{\\text{angle}}{2} \\sigma_{z}}
+
+
+ Parameters
+ ----------
+ angle
+ Hashable type (will be treated as Variable) or Numeric type (static angle)
+ target
+ integer or list of integers
+ control
+ integer or list of integers
+
+ Returns
+ QCircuit object with this RotationGate
+ -------
+ """
+ returnRotationGate(axis=2,angle=angle,target=target,control=control,assume_real=assume_real)
+
+
+
+
+[docs]
+defX(target:typing.Union[list,int],control:typing.Union[list,int]=None,power=None,angle=None,*args,**kwargs)->QCircuit:
+"""
+ Notes
+ ----------
+ Pauli X Gate
+
+ Parameters
+ ----------
+ target
+ int or list of int
+ control
+ int or list of int
+ power
+ numeric type (fixed exponent) or hashable type (parametrized exponent)
+ angle
+ similar to power, but will be interpreted as
+ .. math::
+ U(\\text{angle})=e^{-i\\frac{angle}{2} (1-X)}
+ the default is angle=pi
+ .. math::
+ U(\\pi) = X
+ If angle and power are given both, tequila will combine them
+
+ Returns
+ -------
+ QCircuit object
+ """
+
+ generator=lambdaq:paulis.X(q)-paulis.I(q)
+ return_initialize_power_gate(name="X",power=power,angle=angle,target=target,control=control,
+ generator=generator,*args,**kwargs)
+
+
+
+
+[docs]
+defH(target:typing.Union[list,int],control:typing.Union[list,int]=None,power=None,angle=None,*args,**kwargs)->QCircuit:
+"""
+ Notes
+ ----------
+ Hadamard gate
+
+ Parameters
+ ----------
+ target
+ int or list of int
+ control
+ int or list of int
+ power
+ numeric type (fixed exponent) or hashable type (parametrized exponent)
+ angle
+ similar to power, but will be interpreted as
+ .. math::
+ U(\\text{angle})=e^{-i\\frac{angle}{2} generator}
+ the default is angle=pi
+ .. math::
+ U(\\pi) = H
+ If angle and power are given both, tequila will combine them
+
+ Returns
+ -------
+ QCircuit object
+
+ """
+ coef=1/np.sqrt(2)
+ generator=lambdaq:coef*(paulis.Z(q)+paulis.X(q))-paulis.I(q)
+ return_initialize_power_gate(name="H",power=power,angle=angle,target=target,control=control,
+ generator=generator,*args,**kwargs)
+
+
+
+
+[docs]
+defY(target:typing.Union[list,int],control:typing.Union[list,int]=None,power=None,angle=None,*args,**kwargs)->QCircuit:
+"""
+ Notes
+ ----------
+ Pauli Y Gate
+
+ Parameters
+ ----------
+ target:
+ int or list of int
+ control
+ int or list of int
+ power
+ numeric type (fixed exponent) or hashable type (parametrized exponent)
+ angle
+ similar to power, but will be interpreted as
+ .. math::
+ U(\\text{angle})=e^{-i\\frac{angle}{2} (1-Y)}
+ the default is angle=pi
+ .. math::
+ U(\\pi) = Y
+ If angle and power are given both, tequila will combine them
+
+ Returns
+ -------
+ QCircuit object
+
+ """
+ generator=lambdaq:paulis.Y(q)-paulis.I(q)
+ return_initialize_power_gate(name="Y",power=power,angle=angle,target=target,control=control,
+ generator=generator)
+
+
+
+
+[docs]
+defZ(target:typing.Union[list,int],control:typing.Union[list,int]=None,power=None,angle=None,*args,**kwargs)->QCircuit:
+"""
+ Notes
+ ----------
+ Pauli Z Gate
+
+ Parameters
+ ----------
+ target
+ int or list of int
+ control
+ int or list of int
+ power
+ numeric type (fixed exponent) or hashable type (parametrized exponent)
+ angle
+ similar to power, but will be interpreted as
+ .. math::
+ U(\\text{angle})=e^{-i\\frac{angle}{2} (1-Z)}
+ the default is angle=pi
+ .. math::
+ U(\\pi) = Z
+ If angle and power are given both, tequila will combine them
+
+ Returns
+ -------
+ QCircuit object
+
+ """
+ generator=lambdaq:paulis.Z(q)-paulis.I(q)
+ return_initialize_power_gate(name="Z",power=power,angle=angle,target=target,control=control,
+ generator=generator,*args,**kwargs)
+
+
+
+
+[docs]
+defExpPauli(paulistring:typing.Union[PauliString,str,dict],angle,control:typing.Union[list,int]=None,*args,**kwargs):
+"""Exponentiated Pauligate:
+
+ ExpPauli(PauliString, angle) = exp(-i* angle/2* PauliString)
+
+ Parameters
+ ----------
+ paulistring :
+ given as PauliString structure or as string or dict or list
+ if given as string: Format should be like X(0)Y(3)Z(2)
+ if given as list: Format should be like [(0,'X'),(3,'Y'),(2,'Z')]
+ if given as dict: Format should be like { 0:'X', 3:'Y', 2:'Z' }
+ angle :
+ the angle (will be multiplied by paulistring coefficient if there is one)
+ control :
+ control qubits
+ paulistring: typing.Union[PauliString :
+
+ str] :
+
+ control: typing.Union[list :
+
+ int] :
+ (Default value = None)
+
+ Returns
+ -------
+ type
+ Gate wrapped in circuit
+
+ """
+
+ ps=_convert_Paulistring(paulistring)
+
+ # Failsave: If the paulistring contains just one pauli matrix
+ # it is better to initialize a rotational gate due to strange conventions in some simulators
+ iflen(ps.items())==1:
+ target,axis=tuple(ps.items())[0]
+ returnQCircuit.wrap_gate(
+ impl.RotationGateImpl(axis=axis,target=target,angle=ps.coeff*assign_variable(angle),control=control,*args,**kwargs))
+ else:
+ returnQCircuit.wrap_gate(impl.ExponentialPauliGateImpl(paulistring=ps,angle=angle,control=control,*args,**kwargs))
+
+
+
+
+[docs]
+defRp(paulistring:typing.Union[PauliString,str],angle,control:typing.Union[list,int]=None,*args,**kwargs):
+"""
+ Same as ExpPauli
+ """
+ returnExpPauli(paulistring=paulistring,angle=angle,control=control,*args,**kwargs)
+[docs]
+defGeneralizedRotation(angle:typing.Union[typing.List[typing.Hashable],typing.List[numbers.Real]],
+ generator:QubitHamiltonian,
+ control:typing.Union[list,int]=None,
+ eigenvalues_magnitude:float=0.5,p0=None,
+ steps:int=1,assume_real=False)->QCircuit:
+"""
+
+ Notes
+ --------
+
+ A gates which is shift-rule differentiable
+ - its generator only has two distinguishable eigenvalues
+ - it is then differentiable by the shift rule
+ - eigenvalues_magnitude needs to be given upon initialization (this is "r" from Schuld et. al. and the default is r=1/2)
+ - the generator will not (!) be verified to fullfill the properties
+ Compiling will be done in analogy to a trotterized gate with steps=1 as default
+
+ The gate will act in the same way as rotations and exppauli gates
+
+ .. math::
+ U_{G}(\\text{angle}) = e^{-i\\frac{\\text{angle}}{2} G}
+
+ Parameters
+ ----------
+ angle
+ numeric type or hashable symbol or tequila objective
+ generator
+ tequila QubitHamiltonian or any other structure with paulistrings
+ control
+ list of control qubits
+ eigenvalues_magnitude
+ magnitude of eigenvalues, in most papers referred to as "r" (default 0.5)
+ p0
+ possible nullspace projector (if the rotation is happens in Q = 1-P0). See arxiv:2011.05938
+ steps
+ possible Trotterization steps (default 1)
+
+ Returns
+ -------
+ The gate wrapped in a circuit
+ """
+
+ returnQCircuit.wrap_gate(
+ impl.GeneralizedRotationImpl(angle=assign_variable(angle),generator=generator,control=control,
+ eigenvalues_magnitude=eigenvalues_magnitude,steps=steps,assume_real=assume_real,p0=p0))
+
+
+
+
+
+
+[docs]
+defTrotterized(generator:QubitHamiltonian=None,
+ steps:int=1,
+ angle:typing.Union[typing.Hashable,numbers.Real,Variable]=None,
+ control:typing.Union[list,int]=None,
+ randomize=False,
+ *args,**kwargs)->QCircuit:
+"""
+
+ Parameters
+ ----------
+ generator :
+ generator of the gate U = e^{-i\frac{angle}{2} G }
+ angles :
+ coefficients for each generator
+ steps :
+ trotter steps
+ control :
+ control qubits
+ generators: QubitHamiltonian :
+ The generator of the gate
+ steps: int :
+ Trotter Steps
+ angle: typing.Hashable :
+ A symbol that will be converted to a tq.Variable
+ numbers.Real :
+ A fixed real number
+ Variable :
+ A tequila Variable
+ control: control qubits
+ Returns
+ -------
+ QCircuit
+
+ """
+
+ # downward compatibility
+ if"generators"inkwargs:
+ ifgeneratorisNone:
+ iflen(kwargs["generators"])>1:
+ if"angles"notinkwargs:
+ angles=[angle]*len(kwargs["generators"])
+ else:
+ angles=kwargs["angles"]
+ result=QCircuit()
+ forangle,ginzip(angles,kwargs["generators"]):
+ result+=Trotterized(generator=g,angle=angle,steps=steps,control=control,randomize=randomize)
+ returnresult
+ else:
+ generator=kwargs["generators"][0]
+ else:
+ raiseException("Trotterized: You gave generators={} and generator={}".format(generator,kwargs["generators"]))
+
+ if"angles"inkwargs:
+ ifangleisNone:
+ iflen(kwargs["angles"])>1:
+ raiseException("multiple angles given, but only one generator")
+ angle=kwargs["angles"][0]
+ else:
+ raiseException("Trotterized: You gave angles={} and angle={}".format(angle,kwargs["angles"]))
+
+ angle=assign_variable(angle)
+
+ returnQCircuit.wrap_gate(impl.TrotterizedGateImpl(generator=generator,angle=angle,steps=steps,control=control,randomize=randomize,**kwargs))
+
+
+
+
+[docs]
+defSWAP(first:int,second:int,angle:float=None,control:typing.Union[int,list]=None,power:float=None,*args,
+ **kwargs)->QCircuit:
+"""
+ Notes
+ ----------
+ SWAP gate, order of targets does not matter
+
+ Parameters
+ ----------
+ first: int
+ target qubit
+ second: int
+ target qubit
+ angle: numeric type or hashable type
+ exponent in the for e^{-i a/2 G}
+ control
+ int or list of ints
+ power
+ numeric type (fixed exponent) or hashable type (parametrized exponent in the form (SWAP)^n
+
+ Returns
+ -------
+ QCircuit
+
+ """
+
+ target=[first,second]
+ ifangleisnotNone:
+ assertpowerisNone
+ angle=assign_variable(angle)
+ elifpowerisnotNone:
+ angle=assign_variable(power)*np.pi
+ generator=0.5*(paulis.X(target)+paulis.Y(target)+paulis.Z(target)-paulis.I(target))
+ ifangleisNoneorpowerin[1,1.0]:
+ returnQGate(name="SWAP",target=target,control=control,generator=generator)
+ else:
+ returnGeneralizedRotation(angle=angle,control=control,generator=generator,
+ eigenvalues_magnitude=0.25)
+
+
+
+
+[docs]
+defiSWAP(first:int,second:int,control:typing.Union[int,list]=None,power:float=1.0,*args,
+ **kwargs)->QCircuit:
+"""
+ Notes
+ ----------
+ iSWAP gate
+ .. math::
+ iSWAP = e^{i\\frac{\\pi}{4} (X \\otimes X + Y \\otimes Y )}
+
+ Parameters
+ ----------
+ first: int
+ target qubit
+ second: int
+ target qubit
+ control
+ int or list of ints
+ power
+ numeric type (fixed exponent) or hashable type (parametrized exponent)
+
+ Returns
+ -------
+ QCircuit
+
+ """
+
+ generator=paulis.from_string(f"X({first})X({second}) + Y({first})Y({second})")
+
+ p0=paulis.Projector("|00>")+paulis.Projector("|11>")
+ p0=p0.map_qubits({0:first,1:second})
+
+ gate=QubitExcitationImpl(angle=power*(-np.pi/2),target=generator.qubits,generator=generator,p0=p0,control=control,compile_options="vanilla",*args,**kwargs)
+
+ returnQCircuit.wrap_gate(gate)
+
+
+
+
+[docs]
+defGivens(first:int,second:int,control:typing.Union[int,list]=None,angle:float=None,*args,
+ **kwargs)->QCircuit:
+"""
+ Notes
+ ----------
+ Givens gate G
+ .. math::
+ G = e^{-i\\theta \\frac{(Y \\otimes X - X \\otimes Y )}{2}}
+
+ Parameters
+ ----------
+ first: int
+ target qubit
+ second: int
+ target qubit
+ control
+ int or list of ints
+ angle
+ numeric type (fixed exponent) or hashable type (parametrized exponent), theta in the above formula
+
+ Returns
+ -------
+ QCircuit
+
+ """
+
+ returnQubitExcitation(target=[second,first],angle=2*angle,control=control,*args,**kwargs)# twice the angle since theta is not divided by two in the matrix exponential
+
+
+
+"""
+Convenience Initialization Routines for controlled gates following the patern: Gate(control_qubit, target_qubit, possible_parameter)
+All can be initialized as well with the standard operations above
+"""
+
+
+
+[docs]
+defu1(lambd,target:typing.Union[list,int],control:typing.Union[list,int]=None)->QCircuit:
+"""
+ Notes
+ ----------
+ Convenient gate, one of the abstract gates defined by Quantum Experience Standard Header.
+ Changes the phase of a carrier without applying any pulses.
+
+ .. math::
+ from OpenQASM 2.0 specification:
+ u1(\\lambda) \\sim U(0, 0, \\lambda) = R_z(\\lambda) = e^{-i\\frac{\\lambda}{2} \\sigma_{z}}
+ also is equal to:
+ u1(\\lambda) = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i\\lambda} \\end{pmatrix}
+ which is the Tequila Phase gate:
+ u1(\\lambda) = Phase(\\lambda)
+
+ Parameters
+ ----------
+ lambd
+ parameter angle
+ target
+ int or list of int
+ control
+ int or list of int
+
+ Returns
+ -------
+ QCircuit object
+ """
+
+ returnPhase(phi=lambd,target=target,control=control)
+
+
+
+
+[docs]
+defu2(phi,lambd,target:typing.Union[list,int],control:typing.Union[list,int]=None)->QCircuit:
+"""
+ Notes
+ ----------
+ Convenient gate, one of the abstract gates defined by Quantum Experience Standard Header.
+ Uses a single \\pi/2-pulse.
+
+ .. math::
+ u2(\\phi, \\lambda) = U(\\pi/2, \\phi, \\lambda) = R_z(\\phi + \\pi/2)R_x(\\pi/2)R_z(\\lambda - \\pi/2)
+
+ u2(\\phi, \\lambda) = \\frac{1}{\\sqrt{2}}
+ \\begin{pmatrix}
+ 1 & -e^{i\\lambda} \\\\
+ e^{i\\phi} & e^{i(\\phi+\\lambda)}
+ \\end{pmatrix}
+
+ Parameters
+ ----------
+ phi
+ first parameter angle
+ lambd
+ second parameter angle
+ target
+ int or list of int
+ control
+ int or list of int
+
+ Returns
+ -------
+ QCircuit object
+ """
+
+ returnU(theta=np.pi/2,phi=phi,lambd=lambd,target=target,control=control)
+
+
+
+
+[docs]
+defu3(theta,phi,lambd,target:typing.Union[list,int],control:typing.Union[list,int]=None)->QCircuit:
+"""
+ Notes
+ ----------
+ Convenient gate, one of the abstract gates defined by Quantum Experience Standard Header
+ The most general single-qubit gate.
+ Uses a pair of \\pi/2-pulses.
+
+ .. math::
+ u3(\\theta, \\phi, \\lambda) = U(\\theta, \\phi, \\lambda)
+ = \\begin{pmatrix}
+ \\cos{\\frac{\\5theta}{2}} &
+ -e^{i \\lambda} \\sin{\\frac{\\theta}{2}} \\\\
+ e^{i \\phi} \\sin{\\frac{\\theta}{2}} &
+ e^{i (\\phi+\\lambda)} \\cos{\\frac{\\theta}{2}}
+ \\end{pmatrix}
+
+ Parameters
+ ----------
+ theta
+ first parameter angle
+ phi
+ second parameter angle
+ lambd
+ third parameter angle
+ target
+ int or list of int
+ control
+ int or list of int
+
+ Returns
+ -------
+ QCircuit object
+ """
+
+ returnU(theta=theta,phi=phi,lambd=lambd,target=target,control=control)
+
+
+
+
+[docs]
+defQubitExcitation(angle:typing.Union[numbers.Real,Variable,typing.Hashable],target:typing.List,control=None,
+ assume_real:bool=False,compile_options="optimize"):
+"""
+ A Qubit Excitation, as described under "qubit perspective" in https://doi.org/10.1039/D0SC06627C
+ For the Fermionic operators under corresponding Qubit encodings: Use the chemistry interface
+ Parameters
+ ----------
+ angle:
+ the angle of the excitation unitary
+ target:
+ even number of qubit indices interpreted as [0,1,2,3....] = [(0,1), (2,3), ...]
+ i.e. as qubit excitations from 0 to 1, 2 to 3, etc
+ control:
+ possible control qubits
+ assume_real:
+ assume the wavefunction on which this acts is always real (cheaper gradients: see https://doi.org/10.1039/D0SC06627C)
+
+ Returns
+ -------
+ QubitExcitation gate wrapped into a tequila circuit
+
+ """
+ try:
+ assertlen(target)%2==0
+ except:
+ raiseException("QubitExcitation: Needs an even number of targets")
+
+ returnQCircuit.wrap_gate(QubitExcitationImpl(angle=angle,target=target,assume_real=assume_real,compile_options=compile_options,control=control))
+
+
+
+"""
+Helper Functions
+"""
+
+def_initialize_power_gate(name:str,target:typing.Union[list,int],generator,
+ control:typing.Union[list,int]=None,power=None,angle=None,*args,**kwargs)->QCircuit:
+ target=list_assignment(target)
+
+ # allow angle instead of power in initialization for more consistency
+ # if angle is given we just convert it
+ ifangleisnotNone:
+ angle=assign_variable(angle)
+ ifpowerisnotNone:
+ power=power*angle/np.pi
+ else:
+ power=angle/np.pi
+
+ ifpowerisNoneorpowerin[1,1.0]:
+ gates=[impl.QGateImpl(name=name,target=q,control=control,generator=generator(q))forqintarget]
+ else:
+ gates=[impl.PowerGateImpl(name=name,power=power,target=q,control=control,generator=generator(q),*args,**kwargs)forqin
+ target]
+
+ returnQCircuit.wrap_gate(gates)
+
+
+
+[docs]
+defRotationGate(axis:int,angle:typing.Union[typing.Hashable,numbers.Number],target:typing.Union[list,int],control:typing.Union[list,int]=None,assume_real=False):
+"""
+ Notes
+ ----------
+ Initialize an abstract rotation gate of the form
+
+ .. math::
+ R_{\\text{axis}}(\\text{angle}) = e^{-i\\frac{\\text{angle}}{2} \\sigma_{\\text{axis}}}
+
+
+ Parameters
+ ----------
+ axis
+ integer 1 for x, 2 for y, 3 for z
+ angle
+ Hashable type (will be treated as Variable) or Numeric type (static angle)
+ target
+ integer or list of integers
+ control
+ integer or list of integers
+ assume_real
+ enable improved gradient compilation for controlled gates (wavefunction needs to be real)
+
+ Returns
+ -------
+ QCircuit object with this RotationGate
+ """
+ target=list_assignment(target)
+ gates=[impl.RotationGateImpl(axis=axis,angle=angle,target=q,control=control,assume_real=assume_real)forqintarget]
+
+ returnQCircuit.wrap_gate(gates)
+
+
+
+
+[docs]
+defPowerGate(name:str,target:typing.Union[list,int],power:float=None,control:typing.Union[list,int]=None,generator:QubitHamiltonian=None,*args,**kwargs):
+"""
+ Initialize a (potentially parametrized) gate which is supported on the backend
+
+ Parameters
+ ----------
+ name: str
+ name of the gate on the backend (usually, H, X, Y, Z)
+ target
+ int or list of int
+ power
+ numeric type (fixed exponent) or hashable type (parametrized exponent)
+ will be interpreted as
+ angle
+ similar to power, but will be interpreted as
+ .. math::
+ U=e^{-i\\frac{angle}{2} generator}
+ control
+ int or list of int
+
+ Returns
+ -------
+
+ """
+ returnQCircuit.wrap_gate(
+ impl.PowerGateImpl(name=name,power=power,target=target,control=control,generator=generator,*args,**kwargs))
+
+
+"""
+Implementation of specific gates
+Not put into _gates_impl.py for convenience
+Those gate types will not be recognized by the compiler
+and should all implement a compile function that
+returns a QCircuit of primitive tq gates
+"""
+
+
+[docs]
+ defcompile(self,exponential_pauli=False,*args,**kwargs):
+ # optimized compiling for single and double qubit excitaitons following arxiv:2005.14475
+ # Alternative representation in arxiv:2104.05695 (not implemented -> could be added and controlled with optional compile keywords)
+ ifself.is_controlled():
+ control=list(self.control)
+ else:
+ control=[]
+ ifself.compile_options=="optimize"andlen(self.target)==2andexponential_pauli:
+ p,q=self.target
+ U0=X(target=p,control=q)
+ U1=Ry(angle=self.parameter,target=q,control=[p]+control)
+ returnU0+U1+U0
+ elifself.compile_options=="optimize"andlen(self.target)==4andexponential_pauli:
+ p,r,q,s=self.target
+ U0=X(target=q,control=p)
+ U0+=X(target=s,control=r)
+ U0+=X(target=r,control=p)
+ U0+=X(target=q)
+ U0+=X(target=s)
+ U1=Ry(angle=-self.parameter,target=p,control=[q,r,s]+control)
+ returnU0+U1+U0.dagger()
+ else:
+ returnTrotterized(angle=self.parameter,generator=self.generator,steps=1,control=self.control)
+
+
+
+
+def_convert_Paulistring(paulistring:typing.Union[PauliString,str,dict])->PauliString:
+'''
+ Function that given a paulistring as PauliString structure or
+ as string or dict or list, returns the corresponding PauliString
+ structure.
+
+
+ Parameters
+ ----------
+ paulistring : typing.Union[PauliString , str, dict]
+ given as PauliString structure or as string or dict or list
+ if given as string: Format should be like X(0)Y(3)Z(2)
+ if given as list: Format should be like [(0,'X'),(3,'Y'),(2,'Z')]
+ if given as dict: Format should be like { 0:'X', 3:'Y', 2:'Z' }
+
+ Returns
+ -------
+ ps : PauliString
+ '''
+
+ ifisinstance(paulistring,str):
+ ps=PauliString.from_string(string=paulistring)
+ elifisinstance(paulistring,list):
+ ps=PauliString.from_openfermion(key=paulistring)
+ elifisinstance(paulistring,dict):
+ ps=PauliString(data=paulistring)
+ else:
+ ps=paulistring
+
+ returnps
+
+
+[docs]
+defPauliGate(paulistring:typing.Union[PauliString,str,dict],control:typing.Union[list,int]=None,*args,**kwargs)->QCircuit:
+'''
+ Functions that converts a Pauli string into the corresponding quantum
+ circuit.
+
+ Parameters
+ ----------
+ paulistring : typing.Union[PauliString , str, dict]
+ given as PauliString structure or as string or dict or list
+ if given as string: Format should be like X(0)Y(3)Z(2)
+ if given as list: Format should be like [(0,'X'),(3,'Y'),(2,'Z')]
+ if given as dict: Format should be like { 0:'X', 3:'Y', 2:'Z' }
+
+ control: typing.Union[list, int] : (Default value = None)
+ control qubits
+
+ Raises
+ ------
+ Exception: Not a Pauli Operator.
+
+ Returns
+ -------
+ U : QCircuit object corresponding to the Pauli string.
+
+ '''
+
+ ps=_convert_Paulistring(paulistring)
+
+ U=QCircuit()
+
+ fork,vinps.items():
+ ifv.lower()=="x":
+ U+=X(target=k,control=control,*args,**kwargs)
+ elifv.lower()=="y":
+ U+=Y(target=k,control=control,*args,**kwargs)
+ elifv.lower()=="z":
+ U+=Z(target=k,control=control,*args,**kwargs)
+ else:
+ raiseException("{}???".format(v))
+
+ returnU
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/circuit/gradient.html b/docs/sphinx/_modules/tequila_code/circuit/gradient.html
new file mode 100644
index 0000000..cc7ab35
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/circuit/gradient.html
@@ -0,0 +1,362 @@
+
+
+
+
+
+
+
+ tequila_code.circuit.gradient — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+defgrad(objective:typing.Union[Objective,QTensor],variable:Variable=None,no_compile=False,*args,**kwargs):
+'''
+ wrapper function for getting the gradients of Objectives,ExpectationValues, Unitaries (including single gates), and Transforms.
+ :param obj (QCircuit,ParametrizedGateImpl,Objective,ExpectationValue,Transform,Variable): structure to be differentiated
+ :param variables (list of Variable): parameter with respect to which obj should be differentiated.
+ default None: total gradient.
+ return: dictionary of Objectives, if called on gate, circuit, exp.value, or objective; if Variable or Transform, returns number.
+ '''
+
+ ifvariableisNone:
+ # None means that all components are created
+ variables=objective.extract_variables()
+ result={}
+
+ iflen(variables)==0:
+ raiseTequilaException("Error in gradient: Objective has no variables")
+
+ forkinvariables:
+ assert(kisnotNone)
+ result[k]=grad(objective,k,no_compile=no_compile)
+ returnresult
+ else:
+ variable=assign_variable(variable)
+
+
+ ifisinstance(objective,QTensor):
+ f=lambdax:grad(objective=x,variable=variable,*args,**kwargs)
+ ff=vectorize(f)
+ returnff(objective)
+
+ ifvariablenotinobjective.extract_variables():
+ returnObjective()
+
+ # objective translation
+ # if the objective was already translated to a backend
+ # we need to reverse that here
+ ifobjective.is_translated():
+ raiseTequilaException("\n\ngradient of:{}\ncan not form gradient that was already compiled to a quantum backend\ntq.grad neds to be applied to the abstract - non compiled objective\nE.g. for the (compiled) objective E1 \n\tE1 = tq.compile(E0)\ninstead of doing\n\tdE = tq.grad(E1)\ndo\n\tdE = tq.grad(E0)\nand compile dE afterwards (if wanted) with\n\tdE = tq.compile(dE)\n".format(str(objective)))
+
+
+ # circuit compilation
+ ifno_compile:
+ compiled=objective
+ else:
+ compiler=CircuitCompiler(multitarget=True,
+ trotterized=True,
+ hadamard_power=True,
+ power=True,
+ controlled_phase=True,
+ controlled_rotation=True,
+ gradient_mode=True)
+
+ compiled=compiler(objective,variables=[variable])
+
+ ifvariablenotincompiled.extract_variables():
+ raiseTequilaException("Error in taking gradient. Objective does not depend on variable {} ".format(variable))
+
+ ifisinstance(objective,ExpectationValueImpl):
+ return__grad_expectationvalue(E=objective,variable=variable)
+ elifobjective.is_expectationvalue():
+ return__grad_expectationvalue(E=compiled.args[-1],variable=variable)
+ elifisinstance(compiled,Objective)or(hasattr(compiled,"args")andhasattr(compiled,"transformation")):
+ return__grad_objective(objective=compiled,variable=variable)
+ else:
+ raiseTequilaException("Gradient not implemented for other types than ExpectationValue and Objective.")
+
+
+
+def__grad_objective(objective:Objective,variable:Variable):
+ args=objective.args
+ transformation=objective.transformation
+ dO=None
+
+ processed_expectationvalues={}
+ fori,arginenumerate(args):
+ if__AUTOGRAD__BACKEND__=="jax":
+ df=jax.grad(transformation,argnums=i)
+ elif__AUTOGRAD__BACKEND__=="autograd":
+ df=jax.grad(transformation,argnum=i)
+ else:
+ raiseTequilaException("Can't differentiate without autograd or jax")
+
+ # We can detect one simple case where the outer derivative is const=1
+ iftransformationisNoneortransformation==identity:
+ outer=1.0
+ else:
+ outer=Objective(args=args,transformation=df)
+
+ ifhasattr(arg,"U"):
+ # save redundancies
+ ifarginprocessed_expectationvalues:
+ inner=processed_expectationvalues[arg]
+ else:
+ inner=__grad_inner(arg=arg,variable=variable)
+ processed_expectationvalues[arg]=inner
+ else:
+ # this means this inner derivative is purely variable dependent
+ inner=__grad_inner(arg=arg,variable=variable)
+
+ ifinner==0.0:
+ # don't pile up zero expectationvalues
+ continue
+
+ ifdOisNone:
+ dO=outer*inner
+ else:
+ dO=dO+outer*inner
+
+ ifdOisNone:
+ raiseTequilaException("caught None in __grad_objective")
+ returndO
+
+
+# def __grad_vector_objective(objective: Objective, variable: Variable):
+# argsets = objective.argsets
+# transformations = objective._transformations
+# outputs = []
+# for pos in range(len(objective)):
+# args = argsets[pos]
+# transformation = transformations[pos]
+# dO = None
+#
+# processed_expectationvalues = {}
+# for i, arg in enumerate(args):
+# if __AUTOGRAD__BACKEND__ == "jax":
+# df = jax.grad(transformation, argnums=i)
+# elif __AUTOGRAD__BACKEND__ == "autograd":
+# df = jax.grad(transformation, argnum=i)
+# else:
+# raise TequilaException("Can't differentiate without autograd or jax")
+#
+# # We can detect one simple case where the outer derivative is const=1
+# if transformation is None or transformation == identity:
+# outer = 1.0
+# else:
+# outer = Objective(args=args, transformation=df)
+#
+# if hasattr(arg, "U"):
+# # save redundancies
+# if arg in processed_expectationvalues:
+# inner = processed_expectationvalues[arg]
+# else:
+# inner = __grad_inner(arg=arg, variable=variable)
+# processed_expectationvalues[arg] = inner
+# else:
+# # this means this inner derivative is purely variable dependent
+# inner = __grad_inner(arg=arg, variable=variable)
+#
+# if inner == 0.0:
+# # don't pile up zero expectationvalues
+# continue
+#
+# if dO is None:
+# dO = outer * inner
+# else:
+# dO = dO + outer * inner
+#
+# if dO is None:
+# dO = Objective()
+# outputs.append(dO)
+# if len(outputs) == 1:
+# return outputs[0]
+# return outputs
+
+
+def__grad_inner(arg,variable):
+'''
+ a modified loop over __grad_objective, which gets derivatives
+ all the way down to variables, return 1 or 0 when a variable is (isnt) identical to var.
+ :param arg: a transform or variable object, to be differentiated
+ :param variable: the Variable with respect to which par should be differentiated.
+ :ivar var: the string representation of variable
+ '''
+
+ assert(isinstance(variable,Variable))
+ ifisinstance(arg,Variable):
+ ifarg==variable:
+ return1.0
+ else:
+ return0.0
+ elifisinstance(arg,FixedVariable):
+ return0.0
+ elifisinstance(arg,ExpectationValueImpl):
+ return__grad_expectationvalue(arg,variable=variable)
+ elifhasattr(arg,"abstract_expectationvalue"):
+ E=arg.abstract_expectationvalue
+ dE=__grad_expectationvalue(E,variable=variable)
+ returncompile(dE,**arg._input_args)
+ else:
+ return__grad_objective(objective=arg,variable=variable)
+
+
+def__grad_expectationvalue(E:ExpectationValueImpl,variable:Variable):
+'''
+ implements the analytic partial derivative of a unitary as it would appear in an expectation value. See the paper.
+ :param unitary: the unitary whose gradient should be obtained
+ :param variables (list, dict, str): the variables with respect to which differentiation should be performed.
+ :return: vector (as dict) of dU/dpi as Objective (without hamiltonian)
+ '''
+
+ hamiltonian=E.H
+ unitary=E.U
+ ifnot(unitary.verify()):
+ raiseTequilaException("error in grad_expectationvalue unitary is {}".format(unitary))
+
+ # fast return if possible
+ ifvariablenotinunitary.extract_variables():
+ return0.0
+
+ param_gates=unitary._parameter_map[variable]
+
+ dO=Objective()
+ foridx_ginparam_gates:
+ idx,g=idx_g
+ dOinc=__grad_shift_rule(unitary,g,idx,variable,hamiltonian)
+ dO+=dOinc
+
+ assertdOisnotNone
+ returndO
+
+
+def__grad_shift_rule(unitary,g,i,variable,hamiltonian):
+'''
+ function for getting the gradients of directly differentiable gates. Expects precompiled circuits.
+ :param unitary: QCircuit: the QCircuit object containing the gate to be differentiated
+ :param g: a parametrized: the gate being differentiated
+ :param i: Int: the position in unitary at which g appears
+ :param variable: Variable or String: the variable with respect to which gate g is being differentiated
+ :param hamiltonian: the hamiltonian with respect to which unitary is to be measured, in the case that unitary
+ is contained within an ExpectationValue
+ :return: an Objective, whose calculation yields the gradient of g w.r.t variable
+ '''
+
+ # possibility for overwride in custom gate construction
+ ifhasattr(g,"shifted_gates"):
+ inner_grad=__grad_inner(g.parameter,variable)
+ shifted=g.shifted_gates()
+ dOinc=Objective()
+ forxinshifted:
+ w,g=x
+ Ux=unitary.replace_gates(positions=[i],circuits=[g])
+ wx=w*inner_grad
+ Ex=Objective.ExpectationValue(U=Ux,H=hamiltonian)
+ dOinc+=wx*Ex
+ returndOinc
+ else:
+ raiseTequilaException('No shift found for gate {}\nWas the compiler called?'.format(g))
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/circuit/noise.html b/docs/sphinx/_modules/tequila_code/circuit/noise.html
new file mode 100644
index 0000000..6f99a09
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/circuit/noise.html
@@ -0,0 +1,443 @@
+
+
+
+
+
+
+
+ tequila_code.circuit.noise — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+classQuantumNoise:
+"""
+ class representing a specific quantum noise operation on gates of a certain number of qubits.
+
+ Attributes
+ ----------
+ name:
+ what noise to apply
+ probs:
+ the probabilities with which to apply the noise
+ level:
+ the number of qubits in the gates this noise acts upon
+
+ Methods
+ -------
+ from_dict:
+ initialize from a dictionary.
+ """
+ prob_length={
+ 'bit flip':1,
+ 'phase flip':1,
+ 'phase damp':1,
+ 'amplitude damp':1,
+ 'phase-amplitude damp':2,
+ 'depolarizing':1
+ }
+
+ @property
+ defname(self):
+ returnself._name
+
+ @property
+ deflevel(self):
+ returnself._level
+
+ def__init__(self,name:str,probs:typing.List[float],level:int):
+"""
+
+ Parameters
+ ----------
+ name: str
+ what the name of the noise is. Determines how many probabilites are needed additionally.
+ probs: list:
+ a list of probabilities with which to apply the requested noise
+ level: int:
+ the number of qubits in the gates this noise acts upon.
+ """
+ probs=list_assignment(probs)
+ ifnamenotinnoises_available:
+ raiseTequilaException('The name you asked for, {}, is not recognized'.format(name))
+ self._name=name
+ self._level=int(level)
+
+ iflen(probs)!=self.prob_length[name]:
+ raiseTequilaException('{} noise requires {} probabilities; recieved {}'.format(name,self.prob_length[name],len(probs)))
+ ifnameinkrausses:
+ assertsum(probs)<=1.
+ self.probs=list_assignment(probs)
+
+ def__str__(self):
+ back=self.name
+ back+=' on '+str(self._level)+' qubit gates'
+ back+=', probs = '+str(self.probs)
+ returnback
+
+
+[docs]
+ @staticmethod
+ deffrom_dict(d):
+ iftype(d)isdict:
+ returnQuantumNoise(**d)
+ eliftype(d)isQuantumNoise:
+ returnd
+ else:
+ raiseTequilaException('object provided is neither a dictionary nor a QuantumNoise.')
+
+
+
+
+
+[docs]
+classNoiseModel():
+"""
+ class representing noises to apply to a quantum circuit during simulation.
+
+ Attributes
+ ----------
+ noises:
+ a list of all the noises to apply.
+
+ Methods
+ -------
+ without_noise_on_level:
+ remove all noise affecting operations with <level> qubits.
+ without_noise_op:
+ remove all noise of a given type, I.E get rid of all bit flips.
+
+ """
+ def__init__(self,noises:typing.List[typing.Union[dict,QuantumNoise]]=None):
+ ifnoisesisNone:
+ self.noises=[]
+ else:
+ self.noises=[QuantumNoise.from_dict(d)fordinlist_assignment(noises)]
+
+ def__str__(self):
+ back='NoiseModel with: \n'
+ fornoiseinself.noises:
+ back+=str(noise)
+ back+=',\n'
+ returnback
+
+ def__add__(self,other):
+ new=NoiseModel()
+ new.noises+=self.noises
+ iftype(other)isdict:
+ new.noises.append(QuantumNoise.from_dict(other))
+ eliftype(other)isQuantumNoise:
+ new.noises.append(other)
+ elifhasattr(other,'noises'):
+ new.noises.extend(copy.copy(other.noises))
+ returnnew
+
+ def__iadd__(self,other):
+ iftype(other)isdict:
+ self.noises+=QuantumNoise.from_dict(other)
+ elifhasattr(other,'noises'):
+ self.noises.extend(copy.copy(other.noises))
+ returnself
+
+
+[docs]
+defBitFlip(p:float,level:int):
+"""
+ Returns a NoiseModel with one QuantumNoise, having a kraus map corresponding to applying pauli X with likelihood p.
+
+ Parameters
+ ----------
+ p: float:
+ the probability with which the noise is applied.
+ level: int:
+ the # of qubits in operations to apply this noise to.
+
+ Returns
+ -------
+ NoiseModel
+ """
+
+ new=NoiseModel.wrap_noise(QuantumNoise(name='bit flip',probs=list_assignment(p),level=level))
+ returnnew
+
+
+
+[docs]
+defPhaseFlip(p:float,level:int):
+'''
+ Returns a NoiseModel of one QuantumNoise, having a kraus map corresponding to applying pauli Z with likelihood p.
+
+ Parameters
+ ----------
+ p: float:
+ the probability with which the noise is applied.
+ level: int:
+ the # of qubits in operations to apply this noise to.
+
+ Returns
+ -------
+ NoiseModel
+ '''
+
+ new=NoiseModel.wrap_noise(QuantumNoise(name='phase flip',probs=list_assignment(p),level=level))
+ returnnew
+
+
+
+
+[docs]
+defPhaseDamp(p:float,level:int):
+'''
+ Returns a NoiseModel of one QuantumNoise, having a kraus map corresponding to phase damping;
+ Krauss map is defined following Nielsen and Chuang;
+ E_0= [[1,0],
+ [0,sqrt(1-p)]]
+ E_1= [[0,0],
+ [0,sqrt(p)]]
+
+ Parameters
+ ----------
+ p: float:
+ the probability with which the noise is applied.
+ level: int:
+ the # of qubits in operations to apply this noise to.
+
+ Returns
+ -------
+ NoiseModel
+ '''
+
+ new=NoiseModel.wrap_noise(QuantumNoise(name='phase damp',probs=list_assignment(p),level=level))
+ returnnew
+
+
+
+
+[docs]
+defAmplitudeDamp(p:float,level:int):
+'''
+ Returns a NoiseModel one QuantumNoise, corresponding to amplitude damping.
+ this channel takes 1 to 0, but leaves 0 unaffected.
+ kraus maps:
+
+ E_0= [[1,0],
+ [0,sqrt(1-p)]]
+ E_1= [[0,sqrt(p)],
+ [0,0]]
+
+ Parameters
+ ----------
+ p: float:
+ the probability with which the noise is applied.
+ level: int:
+ the # of qubits in operations to apply this noise to.
+
+ Returns
+ -------
+ NoiseModel
+ '''
+
+ new=NoiseModel.wrap_noise(QuantumNoise(name='amplitude damp',probs=list_assignment(p),level=level))
+ returnnew
+
+
+
+[docs]
+defPhaseAmplitudeDamp(p1:float,p2:float,level:int):
+'''
+ Returns a NoiseModel with one QuantumNoise, having a kraus map corresponding to phase and amplitude damping.
+
+ Parameters
+ ----------
+ p1: float:
+ the probability with which phase is damped
+ p2: float:
+ the probability with which amplitude is damped
+ level: int:
+ the # of qubits in operations to apply this noise to.
+
+ Returns
+ -------
+ NoiseModel
+ '''
+ new=NoiseModel.wrap_noise(QuantumNoise(name='phase-amplitude damp',probs=list_assignment([p1,p2]),level=level))
+ returnnew
+
+
+
+[docs]
+defDepolarizingError(p:float,level:int):
+'''
+ Returns a NoiseModel with one QuantumNoise, having a kraus map corresponding to equal
+ probabilities of each of the three pauli matrices being applied.
+
+ Parameters
+ ----------
+ p: float:
+ the probability with which the noise is applied.
+ level: int:
+ the # of qubits in operations to apply this noise to.
+
+ Returns
+ -------
+ NoiseModel
+ '''
+ new=NoiseModel.wrap_noise(QuantumNoise(name='depolarizing',probs=list_assignment(p),level=level))
+ returnnew
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/circuit/pyzx.html b/docs/sphinx/_modules/tequila_code/circuit/pyzx.html
new file mode 100644
index 0000000..f2c11d4
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/circuit/pyzx.html
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+ tequila_code.circuit.pyzx — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+Add to tequila the ability to make ZX-Calculus
+
+Using the pyzx library: https://github.com/Quantomatic/pyzx
+"""
+
+HAS_PYZX=True
+try:
+ importpyzx
+ HAS_PYZX=True
+exceptImportError:
+ HAS_PYZX=False
+
+fromtequilaimportTequilaException
+fromtequilaimportexport_open_qasm,import_open_qasm
+fromtequila.circuitimportQCircuit
+
+
+
+[docs]
+defconvert_to_pyzx(circuit:QCircuit,variables=None):
+"""
+ Allow convert from Tequila circuit to pyzx circuit
+
+ Args:
+ circuit: in Tequila format to be exported to pyzx
+ variables: optional dictionary with values for variables
+
+ Returns:
+ pyzx.circuit.Circuit: pyzx circuit
+ """
+ ifHAS_PYZX:
+ returnpyzx.circuit.Circuit.from_qasm(export_open_qasm(circuit=circuit,variables=variables,version="2.0",zx_calculus=True))
+ else:
+ raiseTequilaException("Pyzx package not installed.")
+
+
+
+
+[docs]
+defconvert_from_pyzx(circuit)->QCircuit:
+"""
+ Allow convert from pyzx circuit to Tequila circuit
+
+ Args:
+ circuit: in pyzx format (pyzx.circuit.Circuit) to be exported to Tequila circuit
+
+ Returns:
+ QCircuit: Tequila circuit
+ """
+ ifHAS_PYZX:
+ ifisinstance(circuit,pyzx.circuit.Circuit):
+ returnimport_open_qasm(circuit.to_qasm(),version="2.0")
+ else:
+ raiseTequilaException("Circuit provided must be of type pyzx.circuit.Circuit")
+ else:
+ raiseTequilaException("Pyzx package not installed.")
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/circuit/qasm.html b/docs/sphinx/_modules/tequila_code/circuit/qasm.html
new file mode 100644
index 0000000..fe25e33
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/circuit/qasm.html
@@ -0,0 +1,573 @@
+
+
+
+
+
+
+
+ tequila_code.circuit.qasm — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+Export QCircuits as qasm code
+
+OPENQASM version 2.0 specification from:
+A. W. Cross, L. S. Bishop, J. A. Smolin, and J. M. Gambetta, e-print arXiv:1707.03429v2 [quant-ph] (2017).
+https://arxiv.org/pdf/1707.03429v2.pdf
+"""
+fromtequilaimportTequilaException
+fromtequila.circuitimportQCircuit
+fromtequila.circuit.compilerimportCircuitCompiler
+importtequila.circuit.gatesasgates
+fromnumpyimportpi
+fromtypingimportDict
+importtyping
+
+
+
+[docs]
+defexport_open_qasm(circuit:QCircuit,variables=None,version:str="2.0",filename:str=None,zx_calculus:bool=False)->str:
+"""
+ Allow export to different versions of OpenQASM
+
+ Args:
+ circuit: to be exported to OpenQASM
+ variables: optional dictionary with values for variables
+ version: of the OpenQASM specification, optional
+ filename: optional file name to save the generated OpenQASM code
+ zx_calculus: indicate if y-gates must be transformed to xz equivalents
+
+ Returns:
+ str: OpenQASM string
+ """
+
+ ifversion=="2.0":
+ result=convert_to_open_qasm_2(circuit=circuit,variables=variables,zx_calculus=zx_calculus)
+ else:
+ return"Unsupported OpenQASM version : "+version
+ # TODO: export to version 3
+
+ iffilenameisnotNone:
+ withopen(filename,"w")asfile:
+ file.write(result)
+ file.close()
+
+ returnresult
+
+
+
+
+[docs]
+defimport_open_qasm(qasm_code:str,version:str="2.0",rigorous:bool=True)->QCircuit:
+"""
+ Allow import from different versions of OpenQASM
+
+ Args:
+ qasm_code: string with the OpenQASM code
+ version: of the OpenQASM specification, optional
+ rigorous: indicates whether the QASM code should be read rigorously
+
+ Returns:
+ QCircuit: equivalent to the OpenQASM code received
+ """
+
+ ifversion=="2.0":
+ result=parse_from_open_qasm_2(qasm_code=qasm_code,rigorous=rigorous)
+ else:
+ return"Unsupported OpenQASM version : "+version
+ # TODO: export to version 3
+
+ returnresult
+
+
+
+
+[docs]
+defimport_open_qasm_from_file(filename:str,version:str="2.0",rigorous:bool=True)->QCircuit:
+"""
+ Allow import from different versions of OpenQASM from a file
+
+ Args:
+ filename: string with the file name with the OpenQASM code
+ variables: optional dictionary with values for variables
+ version: of the OpenQASM specification, optional
+ rigorous: indicates whether the QASM code should be read rigorously
+
+ Returns:
+ QCircuit: equivalent to the OpenQASM code received
+ """
+
+ withopen(filename,"r")asfile:
+ qasm_code=file.read()
+ file.close()
+
+ returnimport_open_qasm(qasm_code,version=version,rigorous=rigorous)
+
+
+
+
+[docs]
+defconvert_to_open_qasm_2(circuit:QCircuit,variables=None,zx_calculus:bool=False)->str:
+"""
+ Allow export to OpenQASM version 2.0
+
+ Args:
+ circuit: to be exported to OpenQASM
+ variables: optional dictionary with values for variables
+ zx_calculus: indicate if y-gates must be transformed to xz equivalents
+
+ Returns:
+ str: OpenQASM string
+ """
+
+ ifvariablesisNoneandnot(len(circuit.extract_variables())==0):
+ raiseTequilaException(
+ "You called export_open_qasm for a parametrized type but forgot to pass down the variables: {}".format(
+ circuit.extract_variables()))
+
+ compiler=CircuitCompiler(multitarget=True,
+ multicontrol=False,
+ trotterized=True,
+ generalized_rotation=True,
+ exponential_pauli=True,
+ 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,
+ gradient_mode=False,
+ ry_gate=zx_calculus,
+ y_gate=zx_calculus,
+ ch_gate=zx_calculus)
+
+ compiled=compiler(circuit,variables=None)
+
+ result="OPENQASM 2.0;\ninclude \"qelib1.inc\";\n"
+
+ qubits_names:Dict[int,str]={}
+ forqincompiled.qubits:
+ name="q["+str(q)+"]"
+ qubits_names[q]=name
+
+ result+="qreg q["+str(compiled.n_qubits)+"];\n"
+ result+="creg c["+str(compiled.n_qubits)+"];\n"
+
+ forgincompiled.gates:
+
+ control_str=''
+ ifg.is_controlled():
+
+ iflen(g.control)>2:
+ raiseTequilaException(
+ "Multi-controls beyond 2 not yet supported for OpenQASM 2.0. Gate was:\n{}".format(g))
+
+ controls=list(map(lambdac:qubits_names[c],g.control))
+ control_str=','.join(controls)+','
+
+ gate_name=name_and_params(g,variables)
+ forting.target:
+ result+=gate_name
+ result+=control_str
+ result+=qubits_names[t]
+ result+=";\n"
+
+ returnresult
+
+
+
+
+[docs]
+defname_and_params(g,variables):
+"""
+ Determines the quantum gate name and its parameters if applicable
+
+ Args:
+ g: gate to get its name
+ variables: dictionary with values for variables
+
+ Returns:
+ str: name (and parameter) to the gate specified
+ """
+
+ res=""
+
+ forcinrange(len(g.control)):
+ res+="c"
+
+ res+=g.name.lower()
+
+ ifhasattr(g,"parameter")andg.parameterisnotNone:
+ res+="("+str(g.parameter(variables))+")"
+
+ res+=" "
+
+ returnres
+[docs]
+defpauli(qubit,type)->QubitHamiltonian:
+"""
+ Parameters
+ ----------
+ qubit: int or list of ints
+
+ type: str or int or list of strquaing or int:
+ define if X, Y or Z (0,1,2)
+
+ Returns
+ -------
+ QubitHamiltonian
+ """
+
+ defassign_axis(axis):
+ ifaxisinQubitHamiltonian.axis_to_string:
+ returnQubitHamiltonian.axis_to_string[axis]
+ elifhasattr(axis,"upper"):
+ returnaxis.upper()
+ else:
+ raiseTequilaException("unknown initialization for pauli operator: {}".format(axis))
+
+ ifnotisinstance(qubit,typing.Iterable):
+ qubit=[qubit]
+ type=[type]
+
+ type=[assign_axis(x)forxintype]
+
+ init_string="".join("{}{} ".format(t,q)fort,qinzip(type,qubit))
+
+ returnQubitHamiltonian.from_string(string=init_string,openfermion_format=True)
+
+
+
+
+[docs]
+defX(qubit)->QubitHamiltonian:
+"""
+ Initialize a single Pauli X Operator
+
+ Parameters
+ ----------
+ qubit: int or list of ints
+ qubit(s) on which the operator should act
+
+ Returns
+ -------
+ QubitHamiltonian
+
+ """
+ qubit=list_assignment(qubit)
+ returnpauli(qubit=qubit,type=["X"]*len(qubit))
+
+
+
+
+[docs]
+defY(qubit)->QubitHamiltonian:
+"""
+ Initialize a single Pauli Y Operator
+
+ Parameters
+ ----------
+ qubit: int or list of ints
+ qubit(s) on which the operator should act
+
+ Returns
+ -------
+ QubitHamiltonian
+
+ """
+ qubit=list_assignment(qubit)
+ returnpauli(qubit=qubit,type=["Y"]*len(qubit))
+
+
+
+
+[docs]
+defZ(qubit)->QubitHamiltonian:
+"""
+ Initialize a single Pauli Z Operator
+
+ Parameters
+ ----------
+ qubit: int or list of ints
+ qubit(s) on which the operator should act
+
+ Returns
+ -------
+ QubitHamiltonian
+
+ """
+ qubit=list_assignment(qubit)
+ returnpauli(qubit=qubit,type=["Z"]*len(qubit))
+[docs]
+defQp(qubit)->QubitHamiltonian:
+"""
+ Notes
+ ----------
+ Initialize
+
+ .. math::
+ \\frac{1}{2} \\left( 1 - \\sigma_z \\right)
+
+ Parameters
+ ----------
+ qubit: int or list of ints
+ qubit(s) on which the operator should act
+
+ Returns
+ -------
+ QubitHamiltonian
+
+ """
+ qubit=list_assignment(qubit)
+ result=I()
+ forqinqubit:
+ result*=0.5*(I(qubit=q)+Z(qubit=q))
+ returnresult
+
+
+
+
+[docs]
+defQm(qubit)->QubitHamiltonian:
+"""
+ Notes
+ ----------
+ Initialize
+
+ .. math::
+ \\frac{1}{2} \\left( 1 + \\sigma_z \\right)
+
+ Parameters
+ ----------
+ qubit: int or list of ints
+ qubit(s) on which the operator should act
+
+ Returns
+ -------
+ QubitHamiltonian
+
+ """
+ qubit=list_assignment(qubit)
+ result=I()
+ forqinqubit:
+ result*=0.5*(I(qubit=q)-Z(qubit=q))
+ returnresult
+
+
+
+
+[docs]
+defSp(qubit)->QubitHamiltonian:
+"""
+ Notes
+ ----------
+ Initialize
+
+ .. math::
+ \\frac{1}{2} \\left( \\sigma_x + i\\sigma_y \\right)
+
+ Parameters
+ ----------
+ qubit: int or list of ints
+ qubit(s) on which the operator should act
+
+ Returns
+ -------
+ QubitHamiltonian
+
+ """
+ qubit=list_assignment(qubit)
+ result=I()
+ forqinqubit:
+ result*=0.5*(X(qubit=q)+1.j*Y(qubit=q))
+ returnresult
+
+
+
+
+[docs]
+defSm(qubit)->QubitHamiltonian:
+"""
+ Notes
+ ----------
+ Initialize
+
+ .. math::
+ \\frac{1}{2} \\left( \\sigma_x - i \\sigma_y \\right)
+
+ Parameters
+ ----------
+ qubit: int or list of ints
+ qubit(s) on which the operator should act
+
+ Returns
+ -------
+ QubitHamiltonian
+
+ """
+ qubit=list_assignment(qubit)
+ result=I()
+ forqinqubit:
+ result*=0.5*(X(qubit=q)-1.j*Y(qubit=q))
+ returnresult
+
+
+
+
+[docs]
+defProjector(wfn,threshold=0.0,n_qubits=None)->QubitHamiltonian:
+"""
+ Notes
+ ----------
+ Initialize a projector given by
+
+ .. math::
+ H = \\lvert \\Psi \\rangle \\langle \\Psi \\rvert
+
+ Parameters
+ ----------
+ wfn: QubitWaveFunction or int, or string, or array :
+ The wavefunction onto which the projector projects
+ Needs to be passed down as tequilas QubitWaveFunction type
+ See the documentation on how to initialize a QubitWaveFunction from
+ integer, string or array (can also be passed down diretly as one of those types)
+
+
+ threshold: float: (Default value = 0.0)
+ neglect small parts of the operator
+
+ n_qubits: only needed when an integer is given as wavefunction
+
+ Returns
+ -------
+
+ """
+
+ wfn=QubitWaveFunction(state=wfn,n_qubits=n_qubits)
+
+ H=QubitHamiltonian.zero()
+ fork1,v1inwfn.items():
+ fork2,v2inwfn.items():
+ c=v1.conjugate()*v2
+ ifnotnumpy.isclose(c,0.0,atol=threshold):
+ H+=c*decompose_transfer_operator(bra=k1,ket=k2)
+ assert(H.is_hermitian())
+ returnH
+
+
+
+
+[docs]
+defKetBra(ket:QubitWaveFunction,bra:QubitWaveFunction,hermitian:bool=False,threshold:float=1.e-6,
+ n_qubits=None):
+"""
+ Notes
+ ----------
+ Initialize the general KetBra operator
+ .. math::
+ H = \\lvert ket \\rangle \\langle bra \\rvert
+
+ e.g.
+ wfn1 = tq.QubitWaveFunction.from_string("1.0*|00> + 1.0*|11>").normalize()
+ wfn2 = tq.QubitWaveFunction.from_string("1.0*|00>")
+ operator = tq.paulis.KetBra(ket=wfn1, bra=wfn1)
+ initializes the transfer operator from the all-zero state to a Bell state
+
+ Parameters
+ ----------
+ ket: QubitWaveFunction:
+ QubitWaveFunction which defines the ket element
+ can also be given as string or array or integer
+ bra: QubitWaveFunction:
+ QubitWaveFunction which defines the bra element
+ can also be given as string or array or integer
+ hermitian: bool: (Default False)
+ if True the hermitian version H + H^\dagger is returned
+ threshold: float: (Default 1.e-6)
+ elements smaller than the threshold will be ignored
+ n_qubits: only needed if ket and/or bra are passed down as integers
+
+ Returns
+ -------
+ QubitHamiltonian:
+ a tequila QubitHamiltonian (not necessarily hermitian) representing the KetBra operator desired.
+
+ """
+ H=QubitHamiltonian.zero()
+ ket=QubitWaveFunction(state=ket,n_qubits=n_qubits)
+ bra=QubitWaveFunction(state=bra,n_qubits=n_qubits)
+
+ fork1,v1inbra.items():
+ fork2,v2inket.items():
+ c=v1.conjugate()*v2
+ ifnotnumpy.isclose(c,0.0,atol=threshold):
+ H+=c*decompose_transfer_operator(bra=k1,ket=k2)
+ ifhermitian:
+ returnH.split()[0]
+ else:
+ returnH.simplify(threshold=threshold)
+
+
+
+
+[docs]
+defdecompose_transfer_operator(ket:BitString,bra:BitString,qubits:typing.List[int]=None)->QubitHamiltonian:
+"""
+ Notes
+ ----------
+ Create the operator
+
+ Note that this is operator is not necessarily hermitian
+ So be careful when using it as a generator for gates
+
+ e.g.
+ decompose_transfer_operator(ket="01", bra="10", qubits=[2,3])
+ gives the operator
+
+ .. math::
+ \\lvert 01 \\rangle \\langle 10 \\rvert_{2,3}
+
+ acting on qubits 2 and 3
+
+ Parameters
+ ----------
+ ket: pass an integer, string, or tequila BitString
+ bra: pass an integer, string, or tequila BitString
+ qubits: pass the qubits onto which the operator acts
+
+ Returns
+ -------
+
+ """
+
+ opmap={
+ (0,0):Qp,
+ (0,1):Sp,
+ (1,0):Sm,
+ (1,1):Qm
+ }
+
+ nbits=None
+ ifqubitsisnotNone:
+ nbits=len(qubits)
+
+ ifisinstance(bra,int):
+ bra=BitString.from_int(integer=bra,nbits=nbits)
+ ifisinstance(ket,int):
+ ket=BitString.from_int(integer=ket,nbits=nbits)
+
+ b_arr=bra.array
+ k_arr=ket.array
+ assert(len(b_arr)==len(k_arr))
+ n_qubits=len(k_arr)
+
+ ifqubitsisNone:
+ qubits=range(n_qubits)
+
+ assert(n_qubits<=len(qubits))
+
+ result=QubitHamiltonian.unit()
+ forq,binenumerate(b_arr):
+ k=k_arr[q]
+ result*=opmap[(k,b)](qubit=qubits[q])
+
+ returnresult
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/optimizers.html b/docs/sphinx/_modules/tequila_code/optimizers.html
new file mode 100644
index 0000000..0967e93
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/optimizers.html
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+
+ tequila_code.optimizers — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+defshow_available_optimizers(module=None):
+"""
+ Returns
+ -------
+ A list of available optimization methods
+ The list depends on optimization packages installed in your system
+ """
+ ifmoduleisNone:
+ print("available methods for optimizer modules found on your system:")
+ else:
+ print("available methods for optimizer module {}".format(module))
+ ifmodulenotinINSTALLED_OPTIMIZERS:
+ print("module {} not found!".format(module))
+ module=None
+
+ print("{:20} | {}".format("method","optimizer module"))
+ print("--------------------------")
+ fork,vinINSTALLED_OPTIMIZERS.items():
+ ifmoduleisnotNoneandmodule!=k:
+ continue
+ formethodinv.methods:
+ print("{:20} | {}".format(method,k))
+
+ ifmoduleisNone:
+ print("Supported optimizer modules: ",SUPPORTED_OPTIMIZERS)
+ print("Installed optimizer modules: ",list(INSTALLED_OPTIMIZERS.keys()))
+
+
+
+
+[docs]
+defminimize(objective,
+ method:str="bfgs",
+ variables:list=None,
+ initial_values:typing.Union[dict,numbers.Number,typing.Callable]=0.0,
+ maxiter:int=None,
+ *args,
+ **kwargs):
+"""
+
+ Parameters
+ ----------
+ method: str:
+ The optimization method (e.g. bfgs, cobyla, nelder-mead, ...)
+ see 'tq.optimizers.show_available_methods()' for an overview
+ objective: tq.Objective:
+ The abstract tequila objective to be optimized
+ variables: list of names:
+ The variables which shall be optimized given as list
+ Can be passed as list of names or list of tq variables
+ initial_values: dict:
+ Initial values for the optimization, passed as dictionary
+ with the variable names as keys.
+ Alternatively `zero`, `random` or a single number are accepted
+ maxiter:
+ maximum number of iterations
+ kwargs:
+ further keyword arguments for the actual minimization functions
+ can also be called directly as tq.minimize_modulename
+ e.g. tq.minimize_scipy
+ See their documentation for more details
+
+ example: gradient keyword:
+ gradient (Default Value: None):
+ instructions for gradient compilation
+ can be a dictionary of tequila objectives representing the gradients
+ or a string/dictionary giving instructions for numerical gradients
+ examples are
+ gradient = '2-point'
+ gradient = {'method':'2-point', 'stepsize': 1.e-4}
+ gradient = {'method':Callable, 'stepsize': 1.e-4}
+ see optimizer_base.py for method examples
+
+ gradient = None: analytical gradients are compiled
+
+
+ Returns
+ -------
+
+ """
+
+ ovtmp=objective.extract_variables()
+ fast_return=False
+ ifovtmpisNoneorlen(ovtmp)==0:
+ returnOptimizerResults(energy=float(simulate(objective,*args,**kwargs)),variables={},history=OptimizerHistory())
+
+ fork,vinINSTALLED_OPTIMIZERS.items():
+ ifmethod.lower()inv.methodsormethod.upper()inv.methods:
+ returnv.minimize(
+ objective=objective,
+ method=method,
+ variables=variables,
+ initial_values=initial_values,
+ maxiter=maxiter,
+ *args,**kwargs)
+
+ raiseTequilaOptimizerException(
+ "Could not find optimization method {} in tequila optimizers. You might miss dependencies")
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/optimizers/optimizer_base.html b/docs/sphinx/_modules/tequila_code/optimizers/optimizer_base.html
new file mode 100644
index 0000000..a91dc7d
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/optimizers/optimizer_base.html
@@ -0,0 +1,1063 @@
+
+
+
+
+
+
+
+ tequila_code.optimizers.optimizer_base — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+@dataclass
+classOptimizerHistory:
+"""
+ A class representing the history of optimizers over time. Has a variety of convenience functions attached to it.
+ """
+
+ @property
+ defiterations(self):
+ ifself.energiesisNone:
+ return0
+ else:
+ returnlen(self.energies)
+
+ # history of all true iterations (epochs)
+ energies:typing.List[numbers.Real]=field(default_factory=list)
+ gradients:typing.List[typing.Dict[str,numbers.Real]]=field(default_factory=list)
+ angles:typing.List[typing.Dict[str,numbers.Number]]=field(default_factory=list)
+
+ # history of all function evaluations
+ energy_calls:typing.List[numbers.Real]=field(default_factory=list)
+ gradient_calls:typing.List[typing.Dict[str,numbers.Real]]=field(default_factory=list)
+ angles_calls:typing.List[typing.Dict[str,numbers.Number]]=field(default_factory=list)
+
+ # backward comp.
+ @property
+ defenergies_calls(self):
+ returnself.energy_calls
+ @property
+ defenergies_evaluations(self):
+ returnself.energy_calls
+
+ def__add__(self,other):
+"""
+ magic method for convenient combination of history objects.
+ """
+ result=OptimizerHistory()
+ result.energies=self.energies+other.energies
+ result.gradients=self.gradients+other.gradients
+ result.angles=self.angles+other.angles
+ returnresult
+
+ def__iadd__(self,other):
+"""
+ magic method for convenient in place combination of history objects.
+ """
+ self.energies+=other.energies
+ self.gradients+=other.gradients
+ self.angles+=other.angles
+ returnself
+
+
+[docs]
+ defextract_energies(self,*args,**kwargs)->typing.Dict[numbers.Integral,numbers.Real]:
+"""
+ convenience function to get the energies back as a dictionary.
+ """
+ return{i:efori,einenumerate(self.energies)}
+
+
+
+[docs]
+ defextract_gradients(self,key:str)->typing.Dict[numbers.Integral,numbers.Real]:
+"""
+ convenience function to get the gradients of some variable out of the history.
+ Parameters
+ ----------
+ key: str:
+ the name of the variable whose gradients are sought
+
+ Returns
+ -------
+ dict:
+ a dictionary, representing the gradient of variable 'key' over time.
+ """
+ gradients={}
+ fori,dinenumerate(self.gradients):
+ ifkeyind:
+ gradients[i]=d[assign_variable(key)]
+ returngradients
+
+
+
+[docs]
+ defextract_angles(self,key:str)->typing.Dict[numbers.Integral,numbers.Real]:
+"""
+ convenience function to get the value of some variable out of the history.
+
+ Parameters
+ ----------
+ key: str:
+ name of the variable whose values are sought
+
+ Returns
+ -------
+ dict:
+ a dictionary, representing the value of variable 'key' over time.
+ """
+ angles={}
+ fori,dinenumerate(self.angles):
+ ifkeyind:
+ angles[i]=d[assign_variable(key)]
+ returnangles
+[docs]
+classOptimizer:
+
+"""
+ The base optimizer class, from which other optimizers inherit.
+
+
+ Attributes
+ ----------
+
+ backend:
+ The quantum backend to use (None means autopick)
+ maxiter:
+ Maximum number of iterations to perform.
+ silent:
+ whether or not to print during call or on init.
+ samples:
+ number of samples to call objectives with during call.
+ print_level:
+ Allow customization of printout in derived classes, is set to 0 if silent==True.
+ save_history:
+ whether or not to save history.
+ history:
+ a history object, saving information during optimization.
+ noise:
+ what noise (e.g, a NoiseModel) to apply to simulations during optimization.
+ device:
+ the device that sampling (real or emulated) should be performed on.
+
+
+ Methods
+ -------
+ reset_history:
+ reset the optimizer history.
+ initialize_variables:
+ convenience: format variables of an objective and segregrate actives from passives.
+ compile_objective:
+ convenience: compile an objective.
+ compile_gradient:
+ convenience: build and compile (i.e render callable) the gradient of an objective.
+ compile_hessian:
+ convenience: build and compile (i.e render callable) the hessian of an objective.
+
+ """
+ def__init__(self,backend:str=None,
+ maxiter:int=None,
+ samples:int=None,
+ device:str=None,
+ noise=None,
+ save_history:bool=True,
+ silent:typing.Union[bool,int]=False,
+ print_level:int=99,*args,**kwargs):
+
+"""
+ initialize an optimizer.
+
+ Parameters
+ ----------
+ backend: str, optional:
+ a quantum backend to use. None means autopick.
+ maxiter: int, optional:
+ maximum number of iterations to performed.
+ Note: overwrites attribute of same name to 100, not None, if default.
+ samples: int, optional:
+ number of samples to simulate measurement of objectives with.
+ Default: none, i.e full wavefunction simulation.
+ device: optional:
+ changeable type. The device on which to perform (or, simulate performing) actual quantum computation.
+ Default None will use the basic, un-restricted simulators of backend.
+ noise: optional:
+ NoiseModel object or str 'device', being either a custom noisemodel or the instruction to use that of
+ the emulated device.
+ Default value none means: simulate without any noise.
+ save_history: bool: Default = True:
+ whether or not to save history during optimization. Defaults to true.
+ silent: bool: Default = False:
+ whether or not to be verbose during iterations of optimization.
+ False indicates verbosity.
+ print_level: int: Default = 99:
+ The degree of verbosity during print. Meaningless on in base.
+ args
+ kwargs
+ """
+ ifbackendisNone:
+ self.backend=pick_backend(backend,samples=samples,noise=noise,device=device)
+ else:
+ self.backend=backend
+
+ ifmaxiterisNone:
+ self.maxiter=100
+ else:
+ self.maxiter=maxiter
+
+ ifsilentisNone:
+ self.silent=False
+ else:
+ self.silent=silent
+
+ ifprint_levelisNone:
+ self.print_level=99
+ else:
+ self.print_level=print_level
+
+ ifsilent:
+ self.print_level=0
+
+ self.samples=samples
+ self.save_history=save_history
+ ifsave_history:
+ self.history=OptimizerHistory()
+ else:
+ self.history=None
+
+ self.noise=noise
+ self.device=device
+ self.args=args
+ self.kwargs=kwargs
+
+
+[docs]
+ defreset_history(self):
+"""
+ replace self.history with a blank history.
+
+ Returns
+ -------
+ None
+ """
+ self.history=OptimizerHistory()
+
+
+ def__call__(self,objective:Objective,
+ variables:typing.List[Variable],
+ initial_values:typing.Dict[Variable,numbers.Real]=None,
+ *args,
+ **kwargs)->OptimizerResults:
+"""
+ Optimize some objective with the optimizer.
+
+ Parameters
+ ----------
+ objective: Objective:
+ The objective to optimize.
+ variables: list:
+ which variables to optimize over.
+ initial_values: dict, optional:
+ a starting point at which to begin optimization; a dict of variable, number pairs.
+ args
+ kwargs
+
+ Returns
+ -------
+ OptimizerResults instance with "energy" "history" and "variables" as attributes
+ see inheritors for more details.
+ """
+ raiseTequilaOptimizerException("Tried to call BaseClass of Optimizer")
+
+
+[docs]
+ definitialize_variables(self,objective,initial_values,variables):
+"""
+ Convenience function to format the variables of some objective recieved in calls to optimzers.
+
+ Parameters
+ ----------
+ objective: Objective:
+ the objective being optimized.
+ initial_values: dict or string:
+ initial values for the variables of objective, as a dictionary.
+ if string: can be `zero` or `random`
+ if callable: custom function that initializes when keys are passed
+ if None: random initialization between 0 and 2pi (not recommended)
+ variables: list:
+ the variables being optimized over.
+
+ Returns
+ -------
+ tuple:
+ active_angles, a dict of those variables being optimized.
+ passive_angles, a dict of those variables NOT being optimized.
+ variables: formatted list of the variables being optimized.
+ """
+ # bring into right format
+ variables=format_variable_list(variables)
+ all_variables=objective.extract_variables()
+ ifvariablesisNone:
+ variables=all_variables
+ ifinitial_valuesisNone:
+ initial_values={k:numpy.random.uniform(0,2*numpy.pi)forkinall_variables}
+ elifhasattr(initial_values,"lower"):
+ ifinitial_values.lower()=="zero":
+ initial_values={k:0.0forkinall_variables}
+ elif"zero"ininitial_values.lower():
+ scale=0.1
+ if"scale"ininitial_values.lower():
+ # pass as: near_zero_scale=0.1_...
+ scale=float(initial_values.split("scale")[1].split("_")[0].split("=")[1])
+ initial_values={k:numpy.random.normal(loc=0.0,scale=scale)forkinall_variables}
+ elifinitial_values.lower()=="random":
+ initial_values={k:numpy.random.uniform(0.0,4*numpy.pi)forkinall_variables}
+ elif"random"ininitial_values.lower():
+ scale=2*numpy.pi
+ loc=0.0
+ if"scale"ininitial_values.lower():
+ scale=float(initial_values.split("scale")[1].split("_")[0].split("=")[1])
+ if"loc"ininitial_values.lower():
+ loc=float(initial_values.split("loc")[1].split("_")[0].split("=")[1])
+ initial_values={k:numpy.random.normal(loc=loc,scale=scale)forkinall_variables}
+ else:
+ raiseTequilaOptimizerException("unknown initialization instruction: {}".format(initial_values))
+ elifcallable(initial_values):
+ initial_values={k:initial_values(k)forkinall_variables}
+ elifisinstance(initial_values,numbers.Number):
+ initial_values={k:initial_valuesforkinall_variables}
+ else:
+ # autocomplete initial values, warn if you did
+ detected=False
+ forkinall_variables:
+ ifknotininitial_values:
+ initial_values[k]=0.0
+ detected=True
+ ifdetectedandnotself.silent:
+ warnings.warn("initial_variables given but not complete: Autocompleted with zeroes",TequilaWarning)
+ initial_values=format_variable_dictionary(initial_values)
+
+ active_angles={}
+ forvinvariables:
+ active_angles[v]=initial_values[v]
+
+ passive_angles={}
+ fork,vininitial_values.items():
+ ifknotinactive_angles.keys():
+ passive_angles[k]=v
+ returnactive_angles,passive_angles,variables
+
+
+
+[docs]
+ defcompile_objective(self,objective:Objective,*args,**kwargs):
+"""
+ convenience function to wrap over compile; for use by inheritors.
+ Parameters
+ ----------
+ objective: Objective:
+ an objective to compile.
+ args
+ kwargs
+
+ Returns
+ -------
+ Objective:
+ a compiled Objective. Types vary.
+ """
+ returncompile(objective=objective,
+ samples=self.samples,
+ backend=self.backend,
+ device=self.device,
+ noise=self.noise,
+ *args,**kwargs)
+
+
+
+[docs]
+ defcompile_gradient(self,objective:Objective,
+ variables:typing.List[Variable],
+ gradient=None,
+ *args,**kwargs)->typing.Tuple[
+ typing.Dict,typing.Dict]:
+"""
+ convenience function to compile gradient objects and relavant types. For use by inheritors.
+
+ Parameters
+ ----------
+ objective: Objective:
+ the objective whose gradient is to be calculated.
+ variables: list:
+ the variables to take gradients with resepct to.
+ gradient, optional:
+ special argument to change what structure is used to calculate the gradient, like numerical, or QNG.
+ Default: use regular, analytic gradients.
+ args
+ kwargs
+
+ Returns
+ -------
+ tuple:
+ both the uncompiled and compiled gradients of objective, w.r.t variables.
+ """
+ ifgradientisNone:
+ dO={k:grad(objective=objective,variable=k,*args,**kwargs)forkinvariables}
+ compiled_grad={k:self.compile_objective(objective=dO[k],*args,**kwargs)forkinvariables}
+
+ elifisinstance(gradient,dict)orhasattr(gradient,"items"):
+ ifall([isinstance(x,Objective)forxingradient.values()]):
+ dO=gradient
+ compiled_grad={k:self.compile_objective(objective=dO[k],*args,**kwargs)forkinvariables}
+ elif'method'ingradientandgradient['method']=='standard_spsa':
+ dO=None
+ compiled=self.compile_objective(objective=objective)
+ compiled_grad=_SPSAGrad(objective=compiled,variables=variables,**gradient)
+ else:
+ dO=None
+ compiled=self.compile_objective(objective=objective)
+ compiled_grad={k:_NumGrad(objective=compiled,variable=k,**gradient)forkinvariables}
+ else:
+ raiseTequilaOptimizerException(
+ "unknown gradient instruction of type {} : {}".format(type(gradient),gradient))
+
+ returndO,compiled_grad
+
+
+
+[docs]
+ defcompile_hessian(self,
+ variables:typing.List[Variable],
+ grad_obj:typing.Dict[Variable,Objective],
+ comp_grad_obj:typing.Dict[Variable,Objective],
+ hessian:dict=None,
+ *args,
+ **kwargs)->tuple:
+"""
+ convenience function to compile hessians for optimizers which require it.
+ Parameters
+ ----------
+ variables:
+ the variables of the hessian.
+ grad_obj:
+ the gradient object, to be differentiated once more
+ comp_grad_obj:
+ the compiled gradient object, used for further compilation of the hessian.
+ hessian: optional:
+ extra information to modulate compilation of the hessian.
+ args
+ kwargs
+
+ Returns
+ -------
+ tuple:
+ uncompiled and compiled hessian objects, in that order
+ """
+ dO=grad_obj
+ cdO=comp_grad_obj
+
+ ifhessianisNone:
+ ifdOisNone:
+ raiseTequilaOptimizerException("Can not combine analytical Hessian with numerical Gradient\n"
+ "hessian instruction was: {}".format(hessian))
+
+ compiled_hessian={}
+ ddO={}
+ forkinvariables:
+ dOk=dO[k]
+ forlinvariables:
+ ddO[(k,l)]=grad(objective=dOk,variable=l)
+ compiled_hessian[(k,l)]=self.compile_objective(ddO[(k,l)])
+ ddO[(l,k)]=ddO[(k,l)]
+ compiled_hessian[(l,k)]=compiled_hessian[(k,l)]
+
+ elifisinstance(hessian,dict):
+ ifall([isinstance(x,Objective)forxinhessian.values()]):
+ ddO=hessian
+ compiled_hessian={k:self.compile_objective(objective=ddO[k],*args,**kwargs)forkin
+ hessian.keys()}
+ else:
+ ddO=None
+ compiled_hessian={}
+ forkinvariables:
+ forlinvariables:
+ compiled_hessian[(k,l)]=_NumGrad(objective=cdO[k],variable=l,**hessian)
+ compiled_hessian[(l,k)]=_NumGrad(objective=cdO[l],variable=k,**hessian)
+ else:
+ raiseTequilaOptimizerException("unknown hessian instruction: {}".format(hessian))
+
+ returnddO,compiled_hessian
+
+
+
+class_NumGrad:
+""" Numerical Gradient object.
+
+ Should not be used outside of optimizers.
+ Can't interact with other tequila structures.
+
+ Attributes
+ ----------
+
+ objective:
+ the objective whose gradient is to be approximated.
+ variable:
+ the variable with respect to which the gradient is taken.
+ stepsize:
+ the size of the small constant for shifting.
+ method: how to approximate the gradient.
+
+
+ Methods
+ -------
+ symmetric_two_point_stencil:
+ get gradient by point + shift, point - shift
+ forward_two_point_stencil:
+ get gradient by point + shift, point.
+ backward_two_point_stencil:
+ get gradient by point, point -shift
+ count_expectaionvalues:
+ convenience; call the count_expectationvalues method of objective
+
+ """
+
+ def__init__(self,objective,variable,stepsize,method=None):
+"""
+
+ Parameters
+ ----------
+ objective: Objective:
+ the objective whose gradient is to be approximated.
+ variable:
+ the variable the gradient of objective with respect to which is taken.
+ stepsize:
+ the small shift by which to displace variable around a point.
+ method:
+ the method by which to approximate the gradient.
+ """
+ self.objective=objective
+ self.variable=variable
+ self.stepsize=stepsize
+ ifmethodisNoneormethod=="2-point":
+ self.method=self.symmetric_two_point_stencil
+ elifmethodisNoneormethod=="2-point-forward":
+ self.method=self.forward_two_point_stencil
+ elifmethodisNoneormethod=="2-point-backward":
+ self.method=self.backward_two_point_stencil
+ else:
+ self.method=method
+
+ @staticmethod
+ defsymmetric_two_point_stencil(obj,vars,key,step,*args,**kwargs):
+"""
+ calculate objective gradient by symmetric shifts about a point.
+ Parameters
+ ----------
+ obj: Objective:
+ objective to call.
+ vars:
+ variables to feed to the objective.
+ key:
+ which variable to shift, i.e, which variable's gradient is being called.
+ step:
+ the size of the shift; a small float.
+ args
+ kwargs
+
+ Returns
+ -------
+ float:
+ the approximated gradient of obj w.r.t var at point vars as a float.
+
+ """
+ left=copy.deepcopy(vars)
+ left[key]+=step/2
+ right=copy.deepcopy(vars)
+ right[key]-=step/2
+ return1.0/step*(obj(left,*args,**kwargs)-obj(right,*args,**kwargs))
+
+ @staticmethod
+ defforward_two_point_stencil(obj,vars,key,step,*args,**kwargs):
+"""
+ calculate objective gradient by asymmetric upward shfit relative to some point.
+ Parameters
+ ----------
+ obj: Objective:
+ objective to call.
+ vars:
+ variables to feed to the objective.
+ key:
+ which variable to shift, i.e, which variable's gradient is being called.
+ step:
+ the size of the shift; a small float.
+ args
+ kwargs
+
+ Returns
+ -------
+ float:
+ the approximated gradient of obj w.r.t var at point vars as a float.
+
+ """
+
+ left=copy.deepcopy(vars)
+ left[key]+=step
+ right=copy.deepcopy(vars)
+ return1.0/step*(obj(left,*args,**kwargs)-obj(right,*args,**kwargs))
+
+ @staticmethod
+ defbackward_two_point_stencil(obj,vars,key,step,*args,**kwargs):
+"""
+ calculate objective gradient by asymmetric downward shfit relative to some point.
+ Parameters
+ ----------
+ obj: Objective:
+ objective to call.
+ vars:
+ variables to feed to the objective.
+ key:
+ which variable to shift, i.e, which variable's gradient is being called.
+ step:
+ the size of the shift; a small float.
+ args
+ kwargs
+
+ Returns
+ -------
+ the approximated gradient of obj w.r.t var at point vars as a float.
+
+ """
+
+ left=copy.deepcopy(vars)
+ right=copy.deepcopy(vars)
+ right[key]-=step
+ return1.0/step*(obj(left,*args,**kwargs)-obj(right,*args,**kwargs))
+
+ def__call__(self,variables,*args,**kwargs):
+"""
+ convenience function to call self.method, e.g one of the staticmethods of this class.
+
+ Parameters
+ ----------
+ variables:
+ the variables constitutive of the point at which numerical gradients of self.objective are to be taken
+ args
+ kwargs
+
+ Returns
+ -------
+ type:
+ generally, float, the result of the numerical gradient.
+ """
+ returnself.method(self.objective,variables,self.variable,self.stepsize,*args,**kwargs)
+
+ defcount_expectationvalues(self,*args,**kwargs):
+"""
+ how many expectationvalues are in self.objective?
+ Parameters
+ ----------
+ args
+ kwargs
+
+ Returns
+ -------
+ int:
+ how many expectationvalues are in self.objective
+ """
+ returnself.objective.count_expectationvalues(*args,**kwargs)
+
+class_SPSAGrad(_NumGrad):
+""" Simultaneous Perturbation Stochastic Approximation Gradient object.
+
+ Should not be used outside of optimizers.
+ Can't interact with other tequila structures.
+
+ Attributes
+ ----------
+
+ objective:
+ the objective whose gradient is to be approximated.
+ variables:
+ the variables with respect to which the gradient is taken.
+ stepsize:
+ the size of the small constant for shifting.
+
+ """
+
+ def__init__(self,objective,variables,stepsize,gamma=None,method=None):
+"""
+
+ Parameters
+ ----------
+ objective: Objective:
+ the objective whose gradient is to be approximated.
+ variables:
+ the variables the gradient of objective with respect to which is taken.
+ stepsize:
+ the small shift by which to displace variable around a point.
+ nextIndex:
+ Integer indicating the next index of the list stepsize to use
+ if(nextIndex == -1) stepsize is a float
+ """
+ self.objective=objective
+ self.variables=variables
+ self.gamma=gamma
+
+ ifisinstance(stepsize,list):
+ self.nextIndex=0
+ elifgamma!=None:
+ self.nextIndex="adjust"
+ else:
+ self.nextIndex=-1
+ self.stepsize=stepsize
+ ifmethodisNoneormethod=="standard_spsa":
+ self.method=self.standard_spsa
+ else:
+ self.method=method
+
+ @staticmethod
+ defstandard_spsa(obj,vars,keys,step,*args,**kwargs):
+"""
+ calculate objective gradient using standar spsa.
+ Parameters
+ ----------
+ obj: Objective:
+ objective to call.
+ vars:
+ variables to feed to the objective.
+ key:
+ which variables to shift, i.e, which variable's gradient is being called.
+ step:
+ the size of the shift; a small float.
+ args
+ kwargs
+
+ Returns
+ -------
+ the approximated gradient of obj w.r.t var at point vars as a float.
+
+ """
+ dim=len(keys)
+ perturbation_vector=choices([-1,1],k=dim)
+ left=copy.deepcopy(vars)
+ right=copy.deepcopy(vars)
+ fori,keyinenumerate(keys):
+ left[key]+=perturbation_vector[i]*step
+ right[key]-=perturbation_vector[i]*step
+ numerator=obj(left,*args,**kwargs)-obj(right,*args,**kwargs)
+ gradient=list()
+ foriinrange(dim):
+ gradientComponent=numerator/(2*step*perturbation_vector[i])
+ gradient.append(gradientComponent)
+ returngradient
+
+ def__call__(self,variables,iteration=1,*args,**kwargs):
+"""
+ convenience function to call self.method, e.g one of the staticmethods of this class.
+
+ Parameters
+ ----------
+ variables:
+ the variables constitutive of the point at which numerical gradients of self.objective are to be taken
+ args
+ kwargs
+
+ Returns
+ -------
+ type:
+ generally, float, the result of the numerical gradient.
+ """
+ if(self.nextIndex!=-1andself.nextIndex!="adjust"):
+ stepsize=self.stepsize[self.nextIndex]
+ if(self.nextIndex!=len(self.stepsize)-1):
+ self.nextIndex+=1
+ elif(self.nextIndex==-1):
+ stepsize=self.stepsize
+ else:
+ stepsize=self.stepsize/(iteration**self.gamma)
+
+ returnself.method(self.objective,variables,self.variables,stepsize,*args,**kwargs)
+
+ defcalibrated_lr(self,lr,initial_value,max_iter,*args,**kwargs):
+"""
+ Calculates a calibrated learning rate for spsa
+ Parameters
+ ----------
+ lr:
+ learning rate (a variable in spsa related papers)
+ initial_value:
+ the initial values of the variables used in the optimization
+ max_iter:
+ number of iteration used for the calibration
+ args
+ kwargs
+
+ Returns
+ -------
+ type:
+ float: the learning rate calibrated
+ """
+ dim=len(initial_value)
+ delta=0
+ if(self.nextIndex!=-1andself.nextIndex!="adjust"):
+ stepsize=self.stepsize[0]
+ else:
+ stepsize=self.stepsize
+
+ foriinrange(max_iter):
+ perturbation_vector=choices([-1,1],k=dim)
+ left=copy.deepcopy(initial_value)
+ right=copy.deepcopy(initial_value)
+ forj,vinenumerate(initial_value):
+ left[v]+=perturbation_vector[j]*stepsize
+ right[v]-=perturbation_vector[j]*stepsize
+ numeratorLeft=self.objective(left,*args,**kwargs)
+ numeratorRight=self.objective(right,*args,**kwargs)
+ delta+=numpy.absolute(numeratorRight-numeratorLeft)/max_iter
+ returnlr*2*stepsize/delta
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/optimizers/optimizer_gd.html b/docs/sphinx/_modules/tequila_code/optimizers/optimizer_gd.html
new file mode 100644
index 0000000..c229dec
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/optimizers/optimizer_gd.html
@@ -0,0 +1,1146 @@
+
+
+
+
+
+
+
+ tequila_code.optimizers.optimizer_gd — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+classOptimizerGD(Optimizer):
+"""
+ The gradient descent optimizer for tequila.
+
+ OptimizerGD allows for two modalities: it can either function as a 'stepper', simply calculating updated
+ parameter values for a given object; or it can be called to perform an entire optimization. The former
+ is used to accomplish the latter, and can give users a more fine-grained control of the optimization.
+ See Optimizer for details on inherited attributes or methods; there are several.
+
+
+ Attributes
+ ---------
+ f:
+ function which performs an optimization step.
+ gradient_lookup:
+ dictionary mapping object ids as strings to said object's callable gradient
+ active_key_lookup:
+ dictionary mapping object ids as strings to said object's active keys, itself a dict, of variables to optimize.
+ moments_lookup:
+ dictionary mapping object ids as strings to said object's current stored moments; a pair of lists of floats,
+ namely running tallies of gradient momenta. said momenta are used to SCALE or REDIRECT gradient descent steps.
+ moments_trajectory:
+ dictionary mapping object ids as strings to said object's momenta at ALL steps; that is, a list of all
+ the moments of a given object, in order.
+ step_lookup:
+ dictionary mapping object ids as strings to an int; how many optimization steps have been performed for
+ a given object. Relevant only to the Adam optimizer.
+ diis:
+ Dictionary of parameters for the DIIS accelerator.
+ lr:
+ a float or list of floats. Hyperparameter: The learning rate (unscaled) to be used in each update;
+ in some literature, called a step size.
+ alpha:
+ a float. Hyperparameter: used to adjust the learning rate each iteration using the formula: lr := original_lr / (iteration ** alpha)
+ Default: None. If not specify alpha or lr given as a list: lr will not be adjusted
+ gamma:
+ a float. Hyperparameter: used to adjust the step of the gradient for spsa method in each iteration
+ following: c := original_c / (iteration ** gamma)
+ Default value: None. If not specify gamma or c given as a list: c will not be adjusted
+ beta:
+ a float. Hyperparameter: scales (perhaps nonlinearly) all first moment terms in any relavant method.
+ rho:
+ a float. Hyperparameter: scales (perhaps nonlinearly) all second moment terms in any relavant method.
+ in some literature, may be referred to as 'beta_2'.
+ c:
+ a float or list of floats. Hyperparameter: The step rate used in the spsa gradient.
+ If it is a list, the steprate will change each iteration until the last item of the list is reached.
+ epsilon:
+ a float. Hyperparameter: used to prevent division by zero in some methods.
+ tol:
+ a float. If specified, __call__ aborts when the difference in energies between two steps is smaller than tol.
+ calibrate_lr:
+ a boolean. It specifies to calibrate lr value for spsa method.
+ iteration:
+ a integer. It indicates the number of the iteration being runned.
+
+
+ Methods
+ -------
+ prepare:
+ perform all necessary compilation and registration of a given objective. Must be called before step
+ is used on the given optimizer.
+ step:
+ perform a single optimization step on a compiled objective, starting from a given point.
+ reset_stepper:
+ wipe all stored information about all prepared objectives.
+ reset_momenta:
+ reset all moment information about all prepared objectives, but do not erase compiled gradients.
+ reset_momenta_for:
+ reset all moment information about a given objective, but do not erase compiled gradients.
+
+
+ """
+
+[docs]
+ @classmethod
+ defavailable_methods(cls):
+""":return: All tested available methods"""
+ return['adam','adagrad','adamax','nadam','sgd','momentum','nesterov','rmsprop','rmsprop-nesterov','spsa']
+
+
+
+
+[docs]
+ @classmethod
+ defavailable_diis(cls):
+""":return: All tested methods that can be diis accelerated"""
+ return['sgd']
+
+
+ def__init__(self,maxiter=100,
+ method='sgd',
+ tol:numbers.Real=None,
+ lr:typing.Union[numbers.Real,typing.List[numbers.Real]]=0.1,
+ alpha:numbers.Real=None,
+ gamma:numbers.Real=None,
+ beta:numbers.Real=0.9,
+ rho:numbers.Real=0.999,
+ c:typing.Union[numbers.Real,typing.List[numbers.Real]]=0.2,
+ epsilon:numbers.Real=1.0*10**(-7),
+ diis:typing.Optional[dict]=None,
+ backend=None,
+ samples=None,
+ device=None,
+ noise=None,
+ silent=True,
+ calibrate_lr:bool=False,
+ **kwargs):
+
+"""
+
+ Parameters
+ ----------
+ maxiter: int: Default = 100:
+ maximum number of iterations to perform, if using __call__ method.
+ method: str: Default = 'sgd':
+ string specifying which of the available methods to use for optimization. if not specified,
+ then unmodified, stochastic gradient descent will be used.
+ tol: numbers.Real, optional:
+ if specified a tolerance that specifies when to deem that an optimization has converged.
+ If None: no convergence criterion specified; __call__ runs till maxiter is reached. Must be positive, >0.
+ lr: numbers.Real or list of numbers.Real: Default = 0.1:
+ the learning rate to use. Rescales all steps; used by every optimizer.
+ Default value is 0.1; chosen by fiat.
+ alpha:
+ a float. Hyperparameter: used to adjust the learning rate each iteration using the formula: lr := original_lr / (iteration ** alpha)
+ Default value: None. If not specify alpha or lr given as a list: lr will not be adjusted
+ gamma:
+ a float. Hyperparameter: used to adjust the step of the gradient for spsa method in each iteration
+ following: c := original_c / (iteration ** gamma)
+ Default value: None. If not specify gamma or c given as a list: c will not be adjusted
+ beta: numbers.Real: Default = 0.9
+ rescaling parameter for first moments in a number of methods. Must obey 0<beta<1.
+ Default value suggested by original adam paper.
+ rho: numbers.Real: Default = 0.999
+ rescaling parameter for second moments in a number of methods. Must obey 0<beta<1.
+ Default value suggested by original adam paper.
+ c: numbers.Real or list of numbers.Real: Default = 0.2
+ stepsize for the gradient of the spsa method
+ epsilon: numbers.Real: Default = 10^-7:
+ a float for prevention of division by zero in methods like adam. Must be positive.
+ Default value suggested by original adam paper.
+ diis: dict, optional:
+ Dictionary of parameters for DIIS accelerator.
+ backend: str, optional:
+ a quantum backend to use. None means autopick.
+ samples: int, optional:
+ number of samples to simulate measurement of objectives with.
+ Default: none, i.e full wavefunction simulation.
+ device: optional:
+ changeable type. The device on which to perform (or, simulate performing) actual quantum computation.
+ Default None will use the basic, un-restricted simulators of backend.
+ noise: optional:
+ NoiseModel object or str 'device', being either a custom noisemodel or the instruction to use that of
+ the emulated device.
+ Default value none means: simulate without any noise.
+ silent: bool: Default = False:
+ suppresses printout during calls if True.
+ calibrate_lr: bool: Default = False
+ It specifies to calibrate lr value for spsa method.
+ kwargs
+ """
+
+ super().__init__(maxiter=maxiter,samples=samples,device=device,
+ backend=backend,silent=silent,
+ noise=noise,
+ **kwargs)
+ method_dict={
+ 'adam':self._adam,
+ 'adagrad':self._adagrad,
+ 'adamax':self._adamax,
+ 'nadam':self._nadam,
+ 'sgd':self._sgd,
+ 'momentum':self._momentum,
+ 'nesterov':self._nesterov,
+ 'rmsprop':self._rms,
+ 'rmsprop-nesterov':self._rms_nesterov,
+ 'spsa':self._spsa
+ }
+
+ self.f=method_dict[method.lower()]
+ self.gradient_lookup={}
+ self.active_key_lookup={}
+ self.moments_lookup={}
+ self.moments_trajectory={}
+ self.step_lookup={}
+ ### scaling parameters. lr is learning rate.
+ ### beta rescales first moments. rho rescales second moments. epsilon is for division stability.
+ self.lr=lr
+ self.alpha=alpha
+ self.gamma=gamma
+ self.beta=beta
+ self.rho=rho
+ self.c=c
+ self.epsilon=epsilon
+ self.calibrate_lr=calibrate_lr
+ # DIIS quantities
+ ifdiisisTrue:
+ # Use default parameters
+ diis={}
+
+ ifdiisisNone:
+ # DOn't do DIIS
+ self.__diis=None
+ eliftype(diis)isdict:
+ # User parameters
+ ifmethod.lower()inOptimizerGD.available_diis():
+ self.__diis=DIIS(**diis)
+ else:
+ raiseAttributeError("DIIS not compatible with method %s"%method)
+ else:
+ raiseTypeError("Type of DIIS is not dict")
+
+ if(isinstance(lr,list)):
+ self.nextLRIndex=0
+ foriinlr:
+ assert(i>.0)
+ else:
+ self.nextLRIndex=-1
+ assert(lr>.0)
+
+ assertall([k>.0forkin[beta,rho,epsilon]])
+ self.tol=tol
+ ifself.tolisnotNone:
+ self.tol=abs(float(tol))
+
+ def__call__(self,objective:Objective,
+ maxiter:int=None,
+ initial_values:typing.Dict[Variable,numbers.Real]=None,
+ variables:typing.List[Variable]=None,
+ reset_history:bool=True,
+ method_options:dict=None,
+ gradient=None,
+ *args,**kwargs)->GDResults:
+
+"""
+ perform a gradient descent optimization of an objective.
+
+ Parameters
+ ----------
+ objective: Objective:
+ the objective to optimize.
+ maxiter: int, optional:
+ Overrides the optimizer to specify maximum number of iterations to perform.
+ Default value: use the maxiter supplied to __init__.
+ initial_values: dict, optional:
+ initial point at which to begin optimization.
+ Default None: will be chosen randomly.
+ variables: list, optional:
+ which variables to optimize. Note that all variables not to be optimized must be specified in initial_values
+ Default: optimize all variables of objective.
+ reset_history: bool: Default = True:
+ whether or not to wipe the self.history object.
+ method_options: dict, optional:
+ dummy keyword to play well with tq.minimize. Does nothing.
+ gradient: optional:
+ how to calculate gradients. if str '2-point', will use 2-point numerical gradients;
+ if str 'qng' will use the default qng optimizer. Other more complex options possible.
+ args
+ kwargs
+
+ Returns
+ -------
+ GDResults
+ all the results of optimization.
+ """
+
+
+ ifself.save_historyandreset_history:
+ self.reset_history()
+
+ self.iteration=1
+
+ active_angles,passive_angles,variables=self.initialize_variables(objective,initial_values,variables)
+ v={**active_angles,**passive_angles}
+
+ comp=self.prepare(objective=objective,initial_values=v,variables=variables,gradient=gradient)
+ ### prefactor. Early stopping, initialization, etc. handled here
+
+ ifmaxiterisNone:
+ maxiter=self.maxiter
+
+ ### the actual algorithm acts here:
+ e=comp(v,samples=self.samples)
+ self.history.energies.append(e)
+ self.history.angles.append(v)
+ best=e
+ best_angles=v
+ v=self.step(comp,v)
+ last=e
+
+ ifnotself.silent:
+ print("iter. <O> Δ<O> max(d<O>) rms(d<O>)")
+
+ forstepinrange(1,maxiter):
+ comment=""
+ e=comp(v,samples=self.samples)
+ self.history.energies.append(e)
+ self.history.angles.append(v)
+ ### saving best performance
+ ife<best:
+ best=e
+ best_angles=v
+
+ ifself.tol!=None:
+ ifnumpy.abs(e-last)<=self.tol:
+ ifnotself.silent:
+ print('delta f smaller than tolerance {}. Stopping optimization.'.format(str(self.tol)))
+ break
+
+ ### get new parameters with self.step!
+ vn=self.step(comp,v)
+
+ # From http://vergil.chemistry.gatech.edu/notes/diis/node3.html
+ ifself.__diis:
+ self.__diis.push(
+ numpy.array([vn[k]forkinactive_angles]),
+ numpy.array([vn[k]-v[k]forkinactive_angles]))
+
+ new=self.__diis.update()
+ ifnewisnotNone:
+ self.reset_momenta()
+ comment="DIIS"
+ fori,kinenumerate(active_angles):
+ vn[k]=new[i]
+
+
+ ifnotself.silent:
+ self.__dx=numpy.asarray(self.__dx)
+ print("%3i%+15.8f%+7.2e%7.3e%7.3e%s"
+ %(step,
+ e,
+ e-last,
+ numpy.max([abs(x)forxinself.__dx]),
+ numpy.sqrt(numpy.average(self.__dx**2)),
+ comment))
+
+
+ last=e
+ v=vn
+ self.iteration+=1
+ E_final,angles_final=best,best_angles
+ returnGDResults(energy=E_final,variables=format_variable_dictionary(angles_final),history=self.history,
+ moments=self.moments_trajectory[id(comp)],num_iteration=self.iteration)
+
+
+[docs]
+ defprepare(self,objective:Objective,initial_values:dict=None,
+ variables:list=None,gradient=None):
+"""
+ perform all initialization for an objective, register it with lookup tables, and return it compiled.
+ MUST be called before step is used.
+
+ Parameters
+ ----------
+ objective: Objective:
+ the objective to ready for optimization.
+ initial_values: dict, optional:
+ the initial values of to prepare the optimizer with.
+ Default: choose randomly.
+ variables: list, optional:
+ which variables to optimize over, and hence prepare gradients for.
+ Default value: optimize over all variables in objective.
+ gradient: optional:
+ extra keyword; information used to compile alternate gradients.
+ Default: prepare the standard, analytical gradient.
+
+ Returns
+ -------
+ Objective:
+ compiled version of objective.
+ """
+ objective=objective.contract()
+ active_angles,passive_angles,variables=self.initialize_variables(objective,initial_values,variables)
+ comp=self.compile_objective(objective=objective)
+ forargincomp.args:
+ ifhasattr(arg,'U'):
+ ifarg.U.deviceisnotNone:
+ # don't retrieve computer 100 times; pyquil errors out if this happens!
+ self.device=arg.U.device
+ break
+
+ if(self.f==self._spsa):
+ gradient={"method":"standard_spsa","stepsize":self.c,"gamma":self.gamma}
+
+ compile_gradient=True
+ dE=None
+ ifisinstance(gradient,str):
+ ifgradient.lower()=='qng':
+ compile_gradient=False
+
+ combos=get_qng_combos(objective,initial_values=initial_values,backend=self.backend,
+ device=self.device,
+ samples=self.samples,noise=self.noise,
+ )
+ dE=QNGVector(combos)
+ else:
+ gradient={"method":gradient,"stepsize":1.e-4}
+
+ elifisinstance(gradient,dict):
+ ifgradient['method']=='qng':
+ func=gradient['function']
+ compile_gradient=False
+ combos=get_qng_combos(objective,func=func,initial_values=initial_values,backend=self.backend,
+ device=self.device,
+ samples=self.samples,noise=self.noise)
+ dE=QNGVector(combos)
+
+ ifcompile_gradient:
+ grad_obj,comp_grad_obj=self.compile_gradient(objective=objective,variables=variables,gradient=gradient)
+ spsa=isinstance(gradient,dict)and"method"ingradientandisinstance(gradient["method"],str)and"spsa"ingradient["method"].lower()
+ ifspsa:
+ dE=comp_grad_obj
+ if(self.calibrate_lr):
+ self.lr=dE.calibrated_lr(self.lr,initial_values,50,samples=self.samples)
+ else:
+ dE=CallableVector([comp_grad_obj[k]forkincomp_grad_obj.keys()])
+
+ ostring=id(comp)
+ ifnotself.silent:
+ print(self)
+ print("{:15} : {} expectationvalues".format("Objective",objective.count_expectationvalues()))
+ ifcompile_gradient:
+ ifnotspsa:
+ counts=[x.count_expectationvalues()forxincomp_grad_obj.values()]
+ print("{:15} : {} expectationvalues".format("Gradient",sum(counts)))
+ print("{:15} : {}".format("gradient instr",gradient))
+ print("{:15} : {}".format("active variables",len(active_angles)))
+
+ vec_len=len(active_angles)
+ first=numpy.zeros(vec_len)
+ second=numpy.zeros(vec_len)
+
+ self.gradient_lookup[ostring]=dE
+ self.active_key_lookup[ostring]=active_angles.keys()
+ self.moments_lookup[ostring]=(first,second)
+ self.moments_trajectory[ostring]=[(first,second)]
+ self.step_lookup[ostring]=0
+ returncomp
+
+
+
+[docs]
+ defstep(self,objective:Objective,parameters:typing.Dict[Variable,numbers.Real])-> \
+ typing.Dict[Variable,numbers.Real]:
+"""
+ perform a single optimization step and return suggested parameters.
+ Parameters
+ ----------
+ objective: Objective:
+ the compiled objective, to perform an optimization step for. MUST be one returned by prepare.
+ parameters: dict:
+ the parameters to use in performing the optimization step.
+
+ Returns
+ -------
+ dict
+ dict of new suggested parameters.
+ """
+ s=id(objective)
+ try:
+ gradients=self.gradient_lookup[s]
+ active_keys=self.active_key_lookup[s]
+ last_moment=self.moments_lookup[s]
+ adam_step=self.step_lookup[s]
+ except:
+ raiseTequilaException(
+ 'Could not retrieve necessary information. Please use the prepare function before optimizing!')
+ new,moments,grads=self.f(step=adam_step,
+ gradients=gradients,
+ active_keys=active_keys,
+ moments=last_moment,
+ v=parameters,
+ iteration=self.iteration)
+ back={**parameters}
+ forkinnew.keys():
+ back[k]=new[k]
+ save_grad={}
+ self.moments_lookup[s]=moments
+ self.moments_trajectory[s].append(moments)
+ ifself.save_history:
+ fori,kinenumerate(active_keys):
+ save_grad[k]=grads[i]
+ self.history.gradients.append(save_grad)
+ self.step_lookup[s]+=1
+ self.__dx=grads# most recent gradient
+ returnback
+
+
+
+[docs]
+ defreset_stepper(self):
+"""
+ reset all information about all prepared objectives.
+ Returns
+ -------
+ None
+ """
+ self.moments_trajectory={}
+ self.moments_lookup={}
+ self.step_lookup={}
+ self.gradient_lookup={}
+ self.reset_history()
+
+
+
+[docs]
+ defreset_momenta(self):
+"""
+ reset moment information about all prepared objectives.
+ Returns
+ -------
+ None
+ """
+ forkinself.moments_lookup.keys():
+ m=self.moments_lookup[k]
+ vlen=len(m[0])
+ first=numpy.zeros(vlen)
+ second=numpy.zeros(vlen)
+ self.moments_lookup[k]=(first,second)
+ self.moments_trajectory[k]=[(first,second)]
+ self.step_lookup[k]=0
+
+
+
+[docs]
+ defreset_momenta_for(self,objective:Objective):
+"""
+ reset moment information about a specific objective.
+ Parameters
+ ----------
+ objective: Objective:
+ the objective whose information should be reset.
+
+ Returns
+ -------
+ None
+ """
+ k=id(objective)
+ try:
+ m=self.moments_lookup[k]
+ vlen=len(m[0])
+ first=numpy.zeros(vlen)
+ second=numpy.zeros(vlen)
+ self.moments_lookup[k]=(first,second)
+ self.moments_trajectory[k]=[(first,second)]
+ self.step_lookup[k]=0
+ except:
+ print('found no compiled objective with id {} in lookup. Did you pass the correct object?'.format(k))
+[docs]
+ defnextLearningRate(self):
+""" Return the learning rate to use
+
+ Returns
+ -------
+ float representing the learning rate to use
+ """
+ if(self.nextLRIndex==-1):
+ if(self.alpha!=None):
+ returnself.lr/(self.iteration**self.alpha)
+ returnself.lr
+ else:
+ if(self.nextLRIndex!=len(self.lr)-1):
+ self.nextLRIndex+=1
+ returnself.lr[self.nextLRIndex-1]
+ else:
+ returnself.lr[self.nextLRIndex]
+
+
+
+
+[docs]
+classDIIS:
+ def__init__(self:'DIIS',
+ ndiis:int=8,
+ min_vectors:int=3,
+ tol:float=5e-2,
+ drop:str='error',
+ )->None:
+"""DIIS accelerator for gradient descent methods.
+
+ Setup a DIIS accelerator. Every gradient step, the optimizer should
+ call the push() method to update the DIIS internal list of error
+ vectors (i.e. gradients) and parameter vectors. A DIIS parameter
+ vector can be requested using the update() method. update() returns
+ None if DIIS is not yet active.
+
+ DIIS activates when max(error) falls below tol and the number of
+ push() has been called more than min_vectors. When the number of DIIS
+ vectors exceed ndiis, older vectors are dropped according to,
+ - Age, if drop == 'first'
+ - Error magnitude if drop == 'error' (the default).
+
+ Note: DIIS only works when the optimizer is fairly close to the sought
+ value. If initiated too far, the DIIS iteration will often start
+ oscillating wildly and generally not converge at all. However, if DIIS
+ is initiated close to the true solution, the acceleration can be
+ massive, and will often yields the last few sig figs way faster than
+ GD on its own.
+
+
+ Parameters
+ ----------
+ ndiis: int:
+ Maximum number of vectors to use in DIIS calculations.
+ min_vectors: int:
+ Minimum number of vectors before DIIS iteration starts.
+ tol: float:
+ DIIS iteration activates when |d<O>| < tol.
+ drop: string:
+ Strategy for dropping old vectors. One of {'error', 'first'}.
+
+ Returns
+ -------
+ None
+
+ """
+ self.ndiis=ndiis
+ self.min_vectors=min_vectors
+ self.tol=tol
+ self.error=[]
+ self.P=[]
+
+ ifdrop=='error':
+ self.drop=self.drop_error
+ elifdrop=='first':
+ self.drop=self.drop_first
+ else:
+ raiseNotImplementedError("Drop type %s not implemented"%drop)
+
+
+[docs]
+ defdrop_first(self:'DIIS',
+ p:typing.Sequence[numpy.ndarray],
+ e:typing.Sequence[numpy.ndarray]
+ )->typing.Tuple[typing.List[numpy.ndarray],typing.List[numpy.ndarray]]:
+"""Return P,E with the first element removed."""
+ returnp[1:],e[1:]
+
+
+
+[docs]
+ defdrop_error(self:'DIIS',
+ p:typing.Sequence[numpy.ndarray],
+ e:typing.Sequence[numpy.ndarray]
+ )->typing.Tuple[typing.List[numpy.ndarray],typing.List[numpy.ndarray]]:
+"""Return P,E with the largest magnitude error vector removed."""
+ i=numpy.argmax([v.dot(v)forvine])
+ returnp[:i]+p[i+1:],e[:i]+e[i+1:]
+[docs]
+ defdo_diis(self:'DIIS')->bool:
+"""Return with DIIS should be performed."""
+ iflen(self.error)<self.min_vectors:
+ # No point in DIIS with less than 2 vectors!
+ returnFalse
+
+ ifmax(numpy.abs(self.error[-1]))>self.tol:
+ returnFalse
+
+ returnTrue
+
+
+
+[docs]
+ defupdate(self:'DIIS')->typing.Optional[numpy.ndarray]:
+"""Get update parameter from DIIS iteration, or None if DIIS is not doable."""
+ # Check if we should do DIIS
+ ifnotself.do_diis():
+ returnNone
+
+ # Making the B matrix
+ N=len(self.error)
+ B=numpy.zeros((N+1,N+1))
+ foriinrange(N):
+ forjinrange(i,N):
+ B[i,j]=self.error[i].dot(self.error[j])
+ B[j,i]=B[i,j]
+
+ B[N,:]=-1
+ B[:,N]=-1
+ B[N,N]=0
+
+ # Making the K vector
+ K=numpy.zeros((N+1,))
+ K[-1]=-1.0
+
+ # Solve DIIS for great convergence!
+ try:
+ diis_v,res,rank,s=numpy.linalg.lstsq(B,K,rcond=None)
+ exceptnumpy.linalg.LinAlgError:
+ self.reset()
+ returnNone
+
+ new=diis_v[:-1].dot(self.P)
+ returnnew
+
+
+
+
+
+[docs]
+defminimize(objective:Objective,
+ lr:typing.Union[float,typing.List[float]]=0.1,
+ method='sgd',
+ initial_values:typing.Dict[typing.Hashable,numbers.Real]=None,
+ variables:typing.List[typing.Hashable]=None,
+ gradient:str=None,
+ samples:int=None,
+ maxiter:int=100,
+ diis:int=None,
+ backend:str=None,
+ noise:NoiseModel=None,
+ device:str=None,
+ tol:float=None,
+ silent:bool=False,
+ save_history:bool=True,
+ alpha:float=None,
+ gamma:float=None,
+ beta:float=0.9,
+ rho:float=0.999,
+ c:typing.Union[float,typing.List[float]]=0.2,
+ epsilon:float=1.*10**(-7),
+ calibrate_lr:bool=False,
+ *args,
+ **kwargs)->GDResults:
+
+""" Initialize and call the GD optimizer.
+ Parameters
+ ----------
+ objective: Objective :
+ The tequila objective to optimize
+ lr: float or list of floats >0:
+ the learning rate. Default 0.1.
+ alpha: float >0:
+ scaling factor to adjust learning rate each iteration. default None
+ gamma: float >0:
+ scaling facto to adjust step for gradient in spsa method. default None
+ beta: float >0:
+ scaling factor for first moments. default 0.9
+ rho: float >0:
+ scaling factor for second moments. default 0.999
+ c: float or list of floats:
+ stepsize for the gradient of the spsa method
+ epsilon: float>0:
+ small float for stability of division. default 10^-7
+ method: string: Default = 'sgd'
+ which variation on Gradient Descent to use. Options include 'sgd','adam','nesterov','adagrad','rmsprop', etc.
+ initial_values: typing.Dict[typing.Hashable, numbers.Real], optional:
+ Initial values as dictionary of Hashable types (variable keys) and floating point numbers. If given None,
+ they will all be set to zero
+ variables: typing.List[typing.Hashable], optional:
+ List of Variables to optimize
+ gradient: optional:
+ the gradient to use. If None, calculated in the usual way. if str='qng', then the qng is calculated.
+ If a dictionary of objectives, those objectives are used. If another dictionary,
+ an attempt will be made to interpret that dictionary to get, say, numerical gradients.
+ samples: int, optional:
+ samples/shots to take in every run of the quantum circuits (None activates full wavefunction simulation)
+ maxiter: int : Default = 100:
+ the maximum number of iterations to run.
+ diis: int, optional:
+ Number of iteration before starting DIIS acceleration.
+ backend: str, optional:
+ Simulation backend which will be automatically chosen if set to None
+ noise: NoiseModel, optional:
+ a NoiseModel to apply to all expectation values in the objective.
+ device: optional:
+ the device from which to (potentially, simulatedly) sample all quantum circuits employed in optimization.
+ tol: float : Default = 10^-4
+ Convergence tolerance for optimization; if abs(delta f) smaller than tol, stop.
+ silent: bool : Default = False:
+ No printout if True
+ save_history: bool: Default = True:
+ Save the history throughout the optimization
+ calibrate_lr: bool: Default = False:
+ Calibrates the value of the learning rate
+
+ Note
+ ----
+
+ optional kwargs may include beta, beta2, and rho, parameters which affect
+ (but do not need to be altered) the various method algorithms.
+
+ Returns
+ -------
+ GDResults:
+ the results of an optimization.
+
+ """
+ ifisinstance(gradient,dict)orhasattr(gradient,"items"):
+ ifall([isinstance(x,Objective)forxingradient.values()]):
+ gradient=format_variable_dictionary(gradient)
+ optimizer=OptimizerGD(save_history=save_history,
+ method=method,
+ lr=lr,
+ alpha=alpha,
+ gamma=gamma,
+ beta=beta,
+ rho=rho,
+ c=c,
+ tol=tol,
+ diis=diis,
+ epsilon=epsilon,
+ samples=samples,backend=backend,
+ device=device,
+ noise=noise,
+ maxiter=maxiter,
+ silent=silent,
+ calibrate_lr=calibrate_lr)
+ returnoptimizer(objective=objective,
+ maxiter=maxiter,
+ gradient=gradient,
+ initial_values=initial_values,
+ variables=variables,*args,**kwargs)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/optimizers/optimizer_scipy.html b/docs/sphinx/_modules/tequila_code/optimizers/optimizer_scipy.html
new file mode 100644
index 0000000..0d17db4
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/optimizers/optimizer_scipy.html
@@ -0,0 +1,548 @@
+
+
+
+
+
+
+
+ tequila_code.optimizers.optimizer_scipy — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+classOptimizerSciPy(Optimizer):
+"""
+ Class wrapping over the scipy optimizer for use by Tequila.
+
+ Attributes
+ ----------
+ method:
+ The scipy optimization method passed as string.
+ tol:
+ See scipy documentation for the method you picked
+ method_options:
+ See scipy documentation for the method you picked
+ method_bounds:
+ See scipy documentation for the method you picked
+ method_constraints:
+ See scipy documentation for the method you picked
+ silent:
+ if False, the optimizer prints out all evaluated energies
+ """
+ gradient_free_methods=['NELDER-MEAD','COBYLA','POWELL','SLSQP']
+ gradient_based_methods=['L-BFGS-B','BFGS','CG','TNC']
+ hessian_based_methods=["TRUST-KRYLOV","NEWTON-CG","DOGLEG","TRUST-NCG","TRUST-EXACT","TRUST-CONSTR"]
+
+
+[docs]
+ @classmethod
+ defavailable_methods(cls):
+""":return: All tested available methods"""
+ returncls.gradient_free_methods+cls.gradient_based_methods+cls.hessian_based_methods
+
+
+ def__init__(self,method:str="L-BFGS-B",
+ tol:numbers.Real=None,
+ method_options=None,
+ method_bounds=None,
+ method_constraints=None,
+ **kwargs):
+"""
+ Parameters
+ ----------
+ method: str: Default = 'L-BFGS-B':
+ The scipy optimization method passed as string.
+ tol: float, optional:
+ See scipy documentation for the method you picked
+ method_options: optional:
+ See scipy documentation for the method you picked
+ method_bounds: optional:
+ See scipy documentation for the method you picked
+ method_constraints: optional:
+ See scipy documentation for the method you picked
+ silent: bool:
+ if False the optimizer prints out all evaluated energies
+ """
+ super().__init__(**kwargs)
+ ifhasattr(method,"upper"):
+ self.method=method.upper()
+ else:
+ self.method=method
+ self.tol=tol
+ self.method_options=method_options
+
+ ifmethod_boundsisnotNone:
+ method_bounds={assign_variable(k):vfork,vinmethod_bounds.items()}
+ self.method_bounds=method_bounds
+
+ ifmethod_optionsisNone:
+ self.method_options={'maxiter':self.maxiter}
+ else:
+ self.method_options=method_options
+ if'maxiter'notinmethod_options:
+ self.method_options['maxiter']=self.maxiter
+
+ self.method_options['disp']=self.print_level>0
+
+ ifmethod_constraintsisNone:
+ self.method_constraints=()
+ else:
+ self.method_constraints=method_constraints
+
+ def__call__(self,objective:Objective,
+ variables:typing.List[Variable]=None,
+ initial_values:typing.Dict[Variable,numbers.Real]=None,
+ gradient:typing.Dict[Variable,Objective]=None,
+ hessian:typing.Dict[typing.Tuple[Variable,Variable],Objective]=None,
+ reset_history:bool=True,
+ *args,
+ **kwargs)->SciPyResults:
+
+"""
+ Perform optimization using scipy optimizers.
+
+ Parameters
+ ----------
+ objective: Objective:
+ the objective to optimize.
+ variables: list, optional:
+ the variables of objective to optimize. If None: optimize all.
+ initial_values: dict, optional:
+ a starting point from which to begin optimization. Will be generated if None.
+ gradient: optional:
+ Information or object used to calculate the gradient of objective. Defaults to None: get analytically.
+ hessian: optional:
+ Information or object used to calculate the hessian of objective. Defaults to None: get analytically.
+ reset_history: bool: Default = True:
+ whether or not to reset all history before optimizing.
+ args
+ kwargs
+
+ Returns
+ -------
+ ScipyReturnType:
+ the results of optimization.
+ """
+ objective=objective.contract()
+ infostring="{:15} : {}\n".format("Method",self.method)
+ infostring+="{:15} : {} expectationvalues\n".format("Objective",objective.count_expectationvalues())
+
+ ifgradientisnotNone:
+ infostring+="{:15} : {}\n".format("grad instr",gradient)
+ ifhessianisnotNone:
+ infostring+="{:15} : {}\n".format("hess_instr",hessian)
+
+ ifself.save_historyandreset_history:
+ self.reset_history()
+
+ active_angles,passive_angles,variables=self.initialize_variables(objective,initial_values,variables)
+
+ # Transform the initial value directory into (ordered) arrays
+ param_keys,param_values=zip(*active_angles.items())
+ param_values=numpy.array(param_values)
+
+ # process and initialize scipy bounds
+ bounds=None
+ ifself.method_boundsisnotNone:
+ bounds={k:Noneforkinactive_angles}
+ fork,vinself.method_bounds.items():
+ ifkinbounds:
+ bounds[k]=v
+ infostring+="{:15} : {}\n".format("bounds",self.method_bounds)
+ names,bounds=zip(*bounds.items())
+ assert(names==param_keys)# make sure the bounds are not shuffled
+
+ # do the compilation here to avoid costly recompilation during the optimization
+ compiled_objective=self.compile_objective(objective=objective,*args,**kwargs)
+ E=_EvalContainer(objective=compiled_objective,
+ param_keys=param_keys,
+ samples=self.samples,
+ passive_angles=passive_angles,
+ save_history=self.save_history,
+ print_level=self.print_level)
+
+ compile_gradient=self.methodin(self.gradient_based_methods+self.hessian_based_methods)
+ compile_hessian=self.methodinself.hessian_based_methods
+
+ dE=None
+ ddE=None
+ # detect if numerical gradients shall be used
+ # switch off compiling if so
+ ifisinstance(gradient,str):
+ ifgradient.lower()=='qng':
+ compile_gradient=False
+ ifcompile_hessian:
+ raiseTequilaException('Sorry, QNG and hessian not yet tested together.')
+
+ combos=get_qng_combos(objective,initial_values=initial_values,backend=self.backend,
+ samples=self.samples,noise=self.noise)
+ dE=_QngContainer(combos=combos,param_keys=param_keys,passive_angles=passive_angles)
+ infostring+="{:15} : QNG {}\n".format("gradient",dE)
+ else:
+ dE=gradient
+ compile_gradient=False
+ ifcompile_hessian:
+ compile_hessian=False
+ ifhessianisNone:
+ hessian=gradient
+ infostring+="{:15} : scipy numerical {}\n".format("gradient",dE)
+ infostring+="{:15} : scipy numerical {}\n".format("hessian",ddE)
+
+ ifisinstance(gradient,dict)and"method"ingradient:
+ ifgradient['method']=='qng':
+ func=gradient['function']
+ compile_gradient=False
+ ifcompile_hessian:
+ raiseTequilaException('Sorry, QNG and hessian not yet tested together.')
+
+ combos=get_qng_combos(objective,func=func,initial_values=initial_values,backend=self.backend,
+ samples=self.samples,noise=self.noise)
+ dE=_QngContainer(combos=combos,param_keys=param_keys,passive_angles=passive_angles)
+ infostring+="{:15} : QNG {}\n".format("gradient",dE)
+
+ ifisinstance(hessian,str):
+ ddE=hessian
+ compile_hessian=False
+
+ ifcompile_gradient:
+ grad_obj,comp_grad_obj=self.compile_gradient(objective=objective,variables=variables,gradient=gradient,*args,**kwargs)
+ expvals=sum([o.count_expectationvalues()foroincomp_grad_obj.values()])
+ infostring+="{:15} : {} expectationvalues\n".format("gradient",expvals)
+ dE=_GradContainer(objective=comp_grad_obj,
+ param_keys=param_keys,
+ samples=self.samples,
+ passive_angles=passive_angles,
+ save_history=self.save_history,
+ print_level=self.print_level)
+ ifcompile_hessian:
+ hess_obj,comp_hess_obj=self.compile_hessian(variables=variables,
+ hessian=hessian,
+ grad_obj=grad_obj,
+ comp_grad_obj=comp_grad_obj,*args,**kwargs)
+ expvals=sum([o.count_expectationvalues()foroincomp_hess_obj.values()])
+ infostring+="{:15} : {} expectationvalues\n".format("hessian",expvals)
+ ddE=_HessContainer(objective=comp_hess_obj,
+ param_keys=param_keys,
+ samples=self.samples,
+ passive_angles=passive_angles,
+ save_history=self.save_history,
+ print_level=self.print_level)
+ ifself.print_level>0:
+ print(self)
+ print(infostring)
+ print("{:15} : {}\n".format("active variables",len(active_angles)))
+
+ Es=[]
+
+ optimizer_instance=self
+ classSciPyCallback:
+ energies=[]
+ gradients=[]
+ hessians=[]
+ angles=[]
+ real_iterations=0
+
+ def__call__(self,*args,**kwargs):
+ self.energies.append(E.history[-1])
+ self.angles.append(E.history_angles[-1])
+ ifdEisnotNoneandnotisinstance(dE,str):
+ self.gradients.append(dE.history[-1])
+ ifddEisnotNoneandnotisinstance(ddE,str):
+ self.hessians.append(ddE.history[-1])
+ self.real_iterations+=1
+ if'callback'inoptimizer_instance.kwargs:
+ optimizer_instance.kwargs['callback'](E.history_angles[-1])
+
+ callback=SciPyCallback()
+ res=scipy.optimize.minimize(E,x0=param_values,jac=dE,hess=ddE,
+ args=(Es,),
+ method=self.method,tol=self.tol,
+ bounds=bounds,
+ constraints=self.method_constraints,
+ options=self.method_options,
+ callback=callback)
+
+ # failsafe since callback is not implemented everywhere
+ ifcallback.real_iterations==0:
+ real_iterations=range(len(E.history))
+
+ ifself.save_history:
+ self.history.energies=callback.energies
+ self.history.energy_calls=E.history
+ self.history.angles=callback.angles
+ self.history.angles_calls=E.history_angles
+ self.history.gradients=callback.gradients
+ self.history.hessians=callback.hessians
+ ifdEisnotNoneandnotisinstance(dE,str):
+ self.history.gradient_calls=dE.history
+ ifddEisnotNoneandnotisinstance(ddE,str):
+ self.history.hessian_calls=ddE.history
+
+ # some methods like "cobyla" do not support callback functions
+ iflen(self.history.energies)==0:
+ self.history.energies=E.history
+ self.history.angles=E.history_angles
+
+ # some scipy methods always give back the last value and not the minimum (e.g. cobyla)
+ ea=sorted(zip(E.history,E.history_angles),key=lambdax:x[0])
+ E_final=ea[0][0]
+ angles_final=ea[0][1]#dict((param_keys[i], res.x[i]) for i in range(len(param_keys)))
+ angles_final={**angles_final,**passive_angles}
+
+ returnSciPyResults(energy=E_final,history=self.history,variables=format_variable_dictionary(angles_final),scipy_result=res)
+
+
+
+
+[docs]
+defavailable_methods(energy=True,gradient=True,hessian=True)->typing.List[str]:
+"""Convenience
+ Parameters
+ ----------
+ energy :
+ (Default value = True)
+ gradient :
+ (Default value = True)
+ hessian :
+ (Default value = True)
+
+ Returns
+ -------
+ Available methods of the scipy optimizer, a list of strings.
+
+ """
+ methods=[]
+ ifenergy:
+ methods+=OptimizerSciPy.gradient_free_methods
+ ifgradient:
+ methods+=OptimizerSciPy.gradient_based_methods
+ ifhessian:
+ methods+=OptimizerSciPy.hessian_based_methods
+ returnmethods
+
+
+
+
+[docs]
+defminimize(objective:Objective,
+ gradient:typing.Union[str,typing.Dict[Variable,Objective]]=None,
+ hessian:typing.Union[str,typing.Dict[typing.Tuple[Variable,Variable],Objective]]=None,
+ initial_values:typing.Dict[typing.Hashable,numbers.Real]=None,
+ variables:typing.List[typing.Hashable]=None,
+ samples:int=None,
+ maxiter:int=100,
+ backend:str=None,
+ backend_options:dict=None,
+ noise:NoiseModel=None,
+ device:str=None,
+ method:str="BFGS",
+ tol:float=1.e-3,
+ method_options:dict=None,
+ method_bounds:typing.Dict[typing.Hashable,numbers.Real]=None,
+ method_constraints=None,
+ silent:bool=False,
+ save_history:bool=True,
+ *args,
+ **kwargs)->SciPyResults:
+"""
+
+ Parameters
+ ----------
+ objective: Objective :
+ The tequila objective to optimize
+ gradient: typing.Union[str, typing.Dict[Variable, Objective], None] : Default value = None):
+ '2-point', 'cs' or '3-point' for numerical gradient evaluation (does not work in combination with all optimizers),
+ dictionary of variables and tequila objective to define own gradient,
+ None for automatic construction (default)
+ Other options include 'qng' to use the quantum natural gradient.
+ hessian: typing.Union[str, typing.Dict[Variable, Objective], None], optional:
+ '2-point', 'cs' or '3-point' for numerical gradient evaluation (does not work in combination with all optimizers),
+ dictionary (keys:tuple of variables, values:tequila objective) to define own gradient,
+ None for automatic construction (default)
+ initial_values: typing.Dict[typing.Hashable, numbers.Real], optional:
+ Initial values as dictionary of Hashable types (variable keys) and floating point numbers. If given None they will all be set to zero
+ variables: typing.List[typing.Hashable], optional:
+ List of Variables to optimize
+ samples: int, optional:
+ samples/shots to take in every run of the quantum circuits (None activates full wavefunction simulation)
+ maxiter: int : (Default value = 100):
+ max iters to use.
+ backend: str, optional:
+ Simulator backend, will be automatically chosen if set to None
+ backend_options: dict, optional:
+ Additional options for the backend
+ Will be unpacked and passed to the compiled objective in every call
+ noise: NoiseModel, optional:
+ a NoiseModel to apply to all expectation values in the objective.
+ method: str : (Default = "BFGS"):
+ Optimization method (see scipy documentation, or 'available methods')
+ tol: float : (Default = 1.e-3):
+ Convergence tolerance for optimization (see scipy documentation)
+ method_options: dict, optional:
+ Dictionary of options
+ (see scipy documentation)
+ method_bounds: typing.Dict[typing.Hashable, typing.Tuple[float, float]], optional:
+ bounds for the variables (see scipy documentation)
+ method_constraints: optional:
+ (see scipy documentation
+ silent: bool :
+ No printout if True
+ save_history: bool:
+ Save the history throughout the optimization
+
+ Returns
+ -------
+ SciPyReturnType:
+ the results of optimization
+ """
+ ifisinstance(gradient,dict)orhasattr(gradient,"items"):
+ ifall([isinstance(x,Objective)forxingradient.values()]):
+ gradient=format_variable_dictionary(gradient)
+ ifisinstance(hessian,dict)orhasattr(hessian,"items"):
+ ifall([isinstance(x,Objective)forxinhessian.values()]):
+ hessian={(assign_variable(k[0]),assign_variable([k[1]])):vfork,vinhessian.items()}
+ method_bounds=format_variable_dictionary(method_bounds)
+
+ # set defaults
+
+ optimizer=OptimizerSciPy(save_history=save_history,
+ maxiter=maxiter,
+ method=method,
+ method_options=method_options,
+ method_bounds=method_bounds,
+ method_constraints=method_constraints,
+ silent=silent,
+ backend=backend,
+ backend_options=backend_options,
+ device=device,
+ samples=samples,
+ noise=noise,
+ tol=tol,
+ *args,
+ **kwargs)
+ returnoptimizer(objective=objective,
+ gradient=gradient,
+ hessian=hessian,
+ initial_values=initial_values,
+ variables=variables,*args,**kwargs)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/quantumchemistry.html b/docs/sphinx/_modules/tequila_code/quantumchemistry.html
new file mode 100644
index 0000000..b952cee
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/quantumchemistry.html
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
+ tequila_code.quantumchemistry — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+defMolecule(geometry:str=None,
+ basis_set:str=None,
+ transformation:typing.Union[str,typing.Callable]=None,
+ orbital_type:str=None,
+ backend:str=None,
+ guess_wfn=None,
+ name:str=None,
+ *args,
+ **kwargs)->QuantumChemistryBase:
+"""
+
+ Parameters
+ ----------
+ geometry
+ molecular geometry as string or as filename (needs to be in xyz format with .xyz ending)
+ basis_set
+ quantum chemistry basis set (sto-3g, cc-pvdz, etc)
+ transformation
+ The Fermion to Qubit Transformation (jordan-wigner, bravyi-kitaev, bravyi-kitaev-tree and whatever OpenFermion supports)
+ backend
+ quantum chemistry backend (psi4, pyscf)
+ guess_wfn
+ pass down a psi4 guess wavefunction to start the scf cycle from
+ can also be a filename leading to a stored wavefunction
+ name
+ name of the molecule, if not given it's auto-deduced from the geometry
+ can also be done vice versa (i.e. geometry is then auto-deduced to name.xyz)
+ args
+ kwargs
+
+ Returns
+ -------
+ The Fermion to Qubit Transformation (jordan-wigner, bravyi-kitaev, bravyi-kitaev-tree and whatever OpenFermion supports)
+ """
+
+ # failsafe for common mistake
+ if"basis"inkwargs:
+ warnings.warn("called molecule with keyword \"basis={0}\" converting it to \"basis_set={0}\"".format(kwargs["basis"]),TequilaWarning)
+ ifbasis_setisnotNone:
+ warnings.warn("did not convert as \"basis_set={}\" was already given".format(basis_set),TequilaWarning)
+ basis_set=kwargs["basis"]
+
+ keyvals={}
+ fork,vinkwargs.items():
+ ifkinParametersQC.__dict__.keys():
+ keyvals[k]=v
+
+ if"parameters"inkwargs:
+ parameters=kwargs["parameters"]
+ kwargs.pop("parameters")
+ else:
+ parameters=ParametersQC(name=name,geometry=geometry,basis_set=basis_set,multiplicity=1,**keyvals)
+
+ integrals_provided=all([keyinkwargsforkeyin["one_body_integrals","two_body_integrals"]])
+ ifintegrals_providedandbackendisNone:
+ backend="base"
+
+ ifbackendisNone:
+ ifbasis_setisNoneorbasis_set.lower()in["madness","mra","pno"]:
+ backend="madness"
+ basis_set="mra"
+ parameters.basis_set=basis_set
+ iforbital_typeisnotNoneandorbital_type.lower()notin["pno","mra-pno"]:
+ warnings.warn("only PNOs supported as orbital_type without basis set. Setting to pno - You gave={}".format(orbital_type),TequilaWarning)
+ orbital_type="pno"
+ else:
+ iforbital_typeisnotNoneandorbital_type.lower()notin["hf","native"]:
+ warnings.warn("only hf and native supported as orbital_type with basis-set. Setting to hf - You gave={}".format(orbital_type),TequilaWarning)
+ orbital_type="hf"
+ iforbital_typeisNone:
+ orbital_type="hf"
+
+ if"psi4"inINSTALLED_QCHEMISTRY_BACKENDS:
+ backend="psi4"
+ elif"pyscf"inINSTALLED_QCHEMISTRY_BACKENDS:
+ backend="pyscf"
+ else:
+ raiseException("No quantum chemistry backends installed on your system")
+
+ elifbackend=="base":
+ ifnotintegrals_provided:
+ raiseException("No quantum chemistry backends installed on your system\n"
+ "To use the base functionality you need to pass the following tensors via keyword\n"
+ "one_body_integrals, two_body_integrals\n")
+ else:
+ backend="base"
+
+ ifbackendnotinSUPPORTED_QCHEMISTRY_BACKENDS:
+ raiseException(str(backend)+" is not (yet) supported by tequila")
+
+ ifbackendnotinINSTALLED_QCHEMISTRY_BACKENDS:
+ raiseException(str(backend)+" was not found on your system")
+
+ ifguess_wfnisnotNoneandbackend!='psi4':
+ raiseException("guess_wfn only works for psi4")
+
+ ifbasis_setisNoneandbackend.lower()notin["base","madness"]andnotintegrals_provided:
+ raiseException("no basis_set or integrals provided for backend={}".format(backend))
+ elifbasis_setisNone:
+ basis_set="custom"
+ parameters.basis_set=basis_set
+
+ returnINSTALLED_QCHEMISTRY_BACKENDS[backend.lower()](parameters=parameters,transformation=transformation,orbital_type=orbital_type,
+ guess_wfn=guess_wfn,*args,**kwargs)
+[docs]
+ defformat_excitation_indices(self,idx):
+"""
+ Consistent formatting of excitation indices
+ idx = [(p0,q0),(p1,q1),...,(pn,qn)]
+ sorted as: p0<p1<pn and pi<qi
+ :param idx: list of index tuples describing a single(!) fermionic excitation
+ :return: list of index tuples
+ """
+
+ idx=[tuple(sorted(x))forxinidx]
+ idx=sorted(idx,key=lambdax:x[0])
+ returnlist(idx)
+
+
+[docs]
+ defformat_excitation_variables(self,idx):
+"""
+ Consistent formatting of excitation variable
+ idx = [(p0,q0),(p1,q1),...,(pn,qn)]
+ sorted as: pi<qi and p0 < p1 < p2
+ :param idx: list of index tuples describing a single(!) fermionic excitation
+ :return: sign of the variable with re-ordered indices
+ """
+ sig=1
+ forpairinidx:
+ ifpair[1]>pair[0]:
+ sig*=-1
+ forpairinrange(len(idx)-1):
+ ifidx[pair+1][0]>idx[pair][0]:
+ sig*=-1
+ returnsig
+
+
+[docs]
+ defcCRy(self,target:int,dcontrol:typing.Union[list,int],control:typing.Union[list,int],
+ angle:typing.Union[Real,Variable,typing.Hashable],case:int=1)->QCircuit:
+'''
+ Compilation of CRy as on https://doi.org/10.1103/PhysRevA.102.062612
+ If not control passed, Ry returned
+ Parameters
+ ----------
+ case: if 1 employs eq. 12 from the paper, if 0 eq. 13
+ '''
+ ifcontrolisnotNoneandnotlen(control):
+ control=None
+ ifisinstance(dcontrol,int):
+ dcontrol=[dcontrol]
+ ifnotlen(dcontrol):
+ returncompile_circuit(gates.Ry(angle=angle,target=target,control=control))
+ else:
+ ifisinstance(angle,str):
+ angle=Variable(angle)
+ U=QCircuit()
+ aux=dcontrol[0]
+ ctr=deepcopy(dcontrol)
+ ctr.pop(0)
+ ifcase:
+ U+=self.cCRy(target=target,dcontrol=ctr,angle=angle/2,case=1,control=control)+gates.H(
+ aux)+gates.CNOT(target,aux)
+ U+=self.cCRy(target=target,dcontrol=ctr,angle=-angle/2,case=0,control=control)+gates.CNOT(
+ target,aux)+gates.H(aux)
+ else:
+ U+=gates.H(aux)+gates.CNOT(target,aux)+self.cCRy(target=target,dcontrol=ctr,angle=-angle/2,
+ case=0,control=control)
+ U+=gates.CNOT(target,aux)+gates.H(aux)+self.cCRy(target=target,dcontrol=ctr,angle=angle/2,
+ case=1,control=control)
+ returnU
+[docs]
+ defis_convertable_to_qubit_excitation(self):
+"""
+ spin-paired double excitations (both electrons occupy the same spatial orbital and are excited to another spatial orbital)
+ in the jordan-wigner representation are identical to 4-qubit excitations which can be compiled more efficient
+ this function hels to automatically detect those cases
+ Returns
+ -------
+
+ """
+ returnFalse
+ ifnotself.transformation.lower().strip("_")=="jordanwigner":returnFalse
+ ifnotlen(self.indices)==2:returnFalse
+ ifnotself.indices[0][0]//2==self.indices[1][0]//2:returnFalse
+ ifnotself.indices[0][1]//2==self.indices[1][1]//2:returnFalse
+ returnTrue
+
+
+
+
+
+[docs]
+defprepare_product_state(state:BitString)->QCircuit:
+"""Small convenience function
+
+ Parameters
+ ----------
+ state :
+ product state encoded into a bitstring
+ state: BitString :
+
+
+ Returns
+ -------
+ type
+ unitary circuit which prepares the product state
+
+ """
+ result=QCircuit()
+ fori,vinenumerate(state.array):
+ ifv==1:
+ result+=gates.X(target=i)
+ returnresult
+
+
+
+
+[docs]
+@dataclass
+classParametersQC:
+"""Specialization of ParametersHamiltonian"""
+ basis_set:str=None# Quantum chemistry basis set
+ geometry:str=None# geometry of the underlying molecule (units: Angstrom!),
+ # this can be a filename leading to an .xyz file or the geometry given as a string
+ description:str=""
+ multiplicity:int=1
+ charge:int=0
+ name:str=None
+ frozen_core:bool=True
+
+
+
+
+ def__post_init__(self,*args,**kwargs):
+
+ ifself.nameisNoneandself.geometryisNone:
+ raiseTequilaException(
+ "no geometry or name given to molecule\nprovide geometry=filename.xyz or geometry=`h 0.0 0.0 0.0\\n...`\nor name=whatever with file whatever.xyz being present")
+ # auto naming
+ ifself.nameisNone:
+ if".xyz"inself.geometry:
+ self.name=self.geometry.split(".xyz")[0]
+ ifself.descriptionisNone:
+ coord,description=self.read_xyz_from_file()
+ self.description=description
+ else:
+ atoms=self.get_atoms()
+ atom_names=sorted(list(set(atoms)),key=lambdax:self.get_atom_number(x),reverse=True)
+ ifself.nameisNone:
+ drop_ones=lambdax:""ifx==1elsex
+ self.name="".join(["{}{}".format(x,drop_ones(atoms.count(x)))forxinatom_names])
+ self.name=self.name.lower()
+
+ ifself.geometryisNone:
+ self.geometry=self.name+".xyz"
+
+ if".xyz"inself.geometryandnotos.path.isfile(self.geometry):
+ raiseTequilaException("could not find file for molecular coordinates {}".format(self.geometry))
+
+ @property
+ deffilename(self):
+""" """
+ return"{}_{}".format(self.name,self.basis_set)
+
+ @property
+ defmolecular_data_param(self)->dict:
+""":return: Give back all parameters for the MolecularData format from openfermion as dictionary"""
+ return{'basis':self.basis_set,'geometry':self.get_geometry(),'description':self.description,
+ 'charge':self.charge,'multiplicity':self.multiplicity,'filename':self.filename
+ }
+
+
+[docs]
+ @staticmethod
+ defformat_element_name(string):
+"""OpenFermion uses case sensitive hash tables for chemical elements
+ I.e. you need to name Lithium: 'Li' and 'li' or 'LI' will not work
+ this convenience function does the naming
+ :return: first letter converted to upper rest to lower
+
+ Parameters
+ ----------
+ string :
+
+
+ Returns
+ -------
+
+ """
+ assert(len(string)>0)
+ assert(isinstance(string,str))
+ fstring=string[0].upper()+string[1:].lower()
+ returnfstring
+
+
+
+[docs]
+ @staticmethod
+ defconvert_to_list(geometry):
+"""Convert a molecular structure given as a string into a list suitable for openfermion
+
+ Parameters
+ ----------
+ geometry :
+ a string specifying a mol. structure. E.g. geometry="h 0.0 0.0 0.0\n h 0.0 0.0 1.0"
+
+ Returns
+ -------
+ type
+ A list with the correct format for openfermion E.g return [ ['h',[0.0,0.0,0.0], [..]]
+
+ """
+ result=[]
+ # Remove blank lines
+ lines=[lforlingeometry.split("\n")ifl]
+
+ forlineinlines:
+ words=line.split()
+
+ # Pad coordinates
+ iflen(words)<4:
+ words+=[0.0]*(4-len(words))
+
+ try:
+ tmp=(ParametersQC.format_element_name(words[0]),
+ (float(words[1]),float(words[2]),float(words[3])))
+ result.append(tmp)
+ exceptValueError:
+ print("get_geometry list unknown line:\n ",line,"\n proceed with caution!")
+ returnresult
+[docs]
+ defget_geometry(self):
+"""Returns the geometry
+ If a xyz filename was given the file is read out
+ otherwise it is assumed that the geometry was given as string
+ which is then reformatted as a list usable as input for openfermion
+ :return: geometry as list
+ e.g. [(h,(0.0,0.0,0.35)),(h,(0.0,0.0,-0.35))]
+ Units: Angstrom!
+
+ Parameters
+ ----------
+
+ Returns
+ -------
+
+ """
+ ifself.geometry.split('.')[-1]=='xyz':
+ geomstring,comment=self.read_xyz_from_file(self.geometry)
+ ifself.description=='':
+ self.description=comment
+ returnself.convert_to_list(geomstring)
+ elifself.geometryisnotNone:
+ returnself.convert_to_list(self.geometry)
+ else:
+ raiseException("Parameters.qc.geometry is None")
+
+
+ def_verify_ordering_dirac(self,trials=100):
+ iflen(self.shape)!=4:
+ returnFalse
+ # dirac ordering: ijkl = <ij|kl> i.e 1212
+ # check for two_body symetries: <ij|kl> = <kj|il> , <il|kj>
+ elems=self.elems
+ n=self.shape[0]
+ for_inrange(trials):
+ idx=numpy.random.randint(0,n,4)
+ test1=numpy.isclose(elems[idx[0],idx[1],idx[2],idx[3]],elems[idx[2],idx[1],idx[0],idx[3]],atol=1.e-4)
+ test2=numpy.isclose(elems[idx[0],idx[1],idx[2],idx[3]],elems[idx[0],idx[3],idx[2],idx[1]],atol=1.e-4)
+ test3=numpy.isclose(elems[idx[0],idx[1],idx[2],idx[3]],elems[idx[2],idx[3],idx[0],idx[1]],atol=1.e-4)
+ ifnot(test1andtest2andtest3):
+ returnFalse
+
+ returnTrue
+
+ def_verify_ordering_mulliken(self,trials=100):
+ iflen(self.shape)!=4:
+ returnFalse
+ # mulliken ordering: ijkl = (ij|kl) i.e 1122
+ elems=self.elems
+ n=self.shape[0]
+ for_inrange(trials):
+ idx=numpy.random.randint(0,n,4)
+ test1=numpy.isclose(elems[idx[0],idx[1],idx[2],idx[3]],elems[idx[1],idx[0],idx[2],idx[3]],atol=1.e-4)
+ test2=numpy.isclose(elems[idx[0],idx[1],idx[2],idx[3]],elems[idx[0],idx[1],idx[3],idx[2]],atol=1.e-4)
+ test3=numpy.isclose(elems[idx[0],idx[1],idx[2],idx[3]],elems[idx[1],idx[0],idx[3],idx[2]],atol=1.e-4)
+ ifnot(test1andtest2andtest3):
+ returnFalse
+
+ returnTrue
+
+ def_verify_ordering_of(self,trials=100):
+ iflen(self.shape)!=4:
+ returnFalse
+ # openfermion ordering: ijkl = [ij|kl] i.e 1221
+ elems=self.elems
+ n=self.shape[0]
+ for_inrange(trials):
+ idx=numpy.random.randint(0,n,4)
+ test1=numpy.isclose(elems[idx[0],idx[1],idx[2],idx[3]],elems[idx[3],idx[1],idx[2],idx[0]],atol=1.e-4)
+ test2=numpy.isclose(elems[idx[0],idx[1],idx[2],idx[3]],elems[idx[0],idx[2],idx[1],idx[3]],atol=1.e-4)
+ test3=numpy.isclose(elems[idx[0],idx[1],idx[2],idx[3]],elems[idx[3],idx[2],idx[1],idx[0]],atol=1.e-4)
+ ifnot(test1andtest2andtest3):
+ returnFalse
+
+ returnTrue
+
+ def__init__(self,elems:numpy.ndarray=None,active_indices:list=None,ordering:str=None,
+ size_full:int=None,verify=False):
+"""
+ Parameters
+ ----------
+ elems: Tensor data as numpy array
+ active_indices: List of active indices in total ordering
+ ordering: Ordering scheme for two body tensors
+ "dirac" or "phys": <12|g|12>
+ .. math::
+ g_{pqrs} = \\int d1 d2 p(1)q(2) g(1,2) r(1)s(2)
+ "mulliken" or "chem": (11|g|22)
+ .. math::
+ g_{pqrs} = \\int d1 d2 p(1)r(2) g(1,2) q(1)s(2)
+ "openfermion":
+ .. math:: [12|g|21]
+ g_{gqprs} = \\int d1 d2 p(1)q(2) g(1,2) s(1)r(2)
+
+ size_full
+ """
+
+ # Set elements
+ self.elems=elems
+ # Active indices only as list of indices (e.g. spatial orbital indices), not as a dictionary of irreducible
+ # representations
+ ifactive_indicesisnotNone:
+ self.active_indices=active_indices
+ self._passive_indices=None
+ self._full_indices=None
+ self._indices_set:bool=False
+
+ # Determine order of tensor
+ # Assume, that tensor is entered in desired shape, not as flat array.
+ self.order=len(self.elems.shape)
+ # Can use size_full < self.elems.shape[0] -> 'full' space is to be considered a subspace as well
+ ifsize_fullisNone:
+ self._size_full=self.elems.shape[0]
+ else:
+ self._size_full=size_full
+ # 2-body tensors (<=> order 4) currently allow reordering
+ ifself.order==4:
+ iforderingisNone:
+ ordering=self.identify_ordering()
+ elifverify:
+ try:# some RDMs are really sloppy (depends on backend)
+ auto_ordering=self.identify_ordering()
+ ifauto_orderingisnotordering:
+ warnings.warn("Auto identified ordering of NBTensor does not match given ordering: {} vs {}".format(auto_ordering,ordering))
+ exceptExceptionasE:
+ warnings.warn("could not verify odering {}".format(ordering))
+ self.ordering=self.Ordering(ordering)
+ else:
+ iforderingisnotNone:
+ raiseException("Ordering only implemented for tensors of order 4 / 2-body tensors.")
+ self.ordering=None
+
+ @property
+ defshape(self,*args,**kwargs):
+ returnself.elems.shape
+
+
+[docs]
+ defsub_lists(self,idx_lists:list=None)->numpy.ndarray:
+"""
+ Get subspace of tensor by a set of index lists
+ according to hPQ.sub_lists(idx_lists=[p, q]) = [hPQ for P in p and Q in q]
+
+ This essentially is an implementation of a non-contiguous slicing using numpy.take
+
+ Parameters
+ ----------
+ idx_lists :
+ List of lists, each defining the desired subspace per axis
+ Size needs to match order of tensor, and lists successively correspond to axis=0,1,2,...,N
+
+ Returns
+ -------
+ out :
+ Sliced tensor as numpy.ndarray
+ """
+ # Check if index list has correct size
+ iflen(idx_lists)!=self.order:
+ raiseException("Need to pass an index list for each dimension!"+
+ " Length of idx_lists needs to match order of tensor.")
+
+ # Perform slicing via numpy.take
+ out=self.elems
+ foraxinrange(self.order):
+ ifidx_lists[ax]isnotNone:# None means, we want the full space in this direction
+ out=numpy.take(out,idx_lists[ax],axis=ax)
+
+ returnout
+
+
+
+[docs]
+ defset_index_lists(self):
+""" Set passive and full index lists based on class inputs """
+ tmp_size=self._size_full
+ ifself._size_fullisNone:
+ tmp_size=self.elems.shape[0]
+
+ self._passive_indices=[iforiinrange(tmp_size)
+ ifinotinself.active_indices]
+ self._full_indices=[iforiinrange(tmp_size)]
+
+
+
+[docs]
+ defsub_str(self,name:str)->numpy.ndarray:
+"""
+ Get subspace of tensor by a string
+ Currently is able to resolve an active space, named 'a', full space 'f', and the complement 'p' = 'f' - 'a'.
+ Full space in this context may also be smaller than actual tensor dimension.
+
+ The specification of active space in this context only allows to pick a set from a list of orbitals, and
+ is not able to resolve an active space from irreducible representations.
+
+ Example for one-body tensor:
+ hPQ.sub_lists(name='ap') = [hPQ for P in active_indices and Q in _passive_indices]
+
+ Parameters
+ ----------
+ name :
+ String specifying the desired subspace, elements need to be a (active), f (full), p (full - active)
+
+ Returns
+ -------
+ out :
+ Sliced tensor as numpy.ndarray
+ """
+ ifnotself._indices_set:
+ self.set_index_lists()
+ self._indices_set=True
+
+ ifnameisNone:
+ raiseException("No name specified.")
+ iflen(name)!=self.order:
+ raiseException("Name does not match order of the tensor.")
+ ifself.active_indicesisNone:
+ raiseException("Need to set an active space in order to call this function.")
+
+ idx_lists=[]
+ # Parse name as string of space indices
+ forcharinname:
+ ifchar.lower()=='a':
+ idx_lists.append(self.active_indices)
+ elifchar.lower()=='p':
+ idx_lists.append(self._passive_indices)
+ elifchar.lower()=='f':
+ ifself._size_fullisNone:
+ idx_lists.append(None)
+ else:
+ idx_lists.append(self._full_indices)
+ else:
+ raiseException("Need to specify a valid letter (a,p,f).")
+
+ out=self.sub_lists(idx_lists)
+
+ returnout
+
+
+
+[docs]
+ defreorder(self,to:str='of'):
+"""
+ Function to reorder tensors according to some convention.
+
+ Parameters
+ ----------
+ to :
+ Ordering scheme of choice.
+ 'openfermion', 'of' (default) :
+ openfermion - ordering, corresponds to integrals of the type
+ h^pq_rs = int p(1)* q(2)* O(1,2) r(2) s(1) (O(1,2)
+ with operators a^pq_rs = a^p a^q a_r a_s (a^p == a^dagger_p)
+ currently needed for dependencies on openfermion-library
+ 'chem', 'c' :
+ quantum chemistry ordering, collect particle terms,
+ more convenient for real-space methods
+ h^pq_rs = int p(1) q(1) O(1,2) r(2) s(2)
+ This is output by psi4
+ 'phys', 'p' :
+ typical physics ordering, integrals of type
+ h^pq_rs = int p(1)* q(2)* O(1,2) r(1) s(2)
+ with operators a^pq_rs = a^p a^q a_s a_r
+
+ Returns
+ -------
+ """
+ ifself.order!=4:
+ warnings.warn('Reordering currently only implemented for two-body tensors.')
+ returnself
+
+ to=self.Ordering(scheme=to)
+
+ ifself.ordering==to:
+ returnself
+ elifself.ordering.is_chem():
+ ifto.is_of():
+ self.elems=numpy.einsum("psqr -> pqrs",self.elems,optimize='greedy')
+ elifto.is_phys():
+ self.elems=numpy.einsum("prqs -> pqrs",self.elems,optimize='greedy')
+ elifself.ordering.is_of():
+ ifto.is_chem():
+ self.elems=numpy.einsum("pqrs -> psqr",self.elems,optimize='greedy')
+ elifto.is_phys():
+ self.elems=numpy.einsum("pqrs -> pqsr",self.elems,optimize='greedy')
+ elifself.ordering.is_phys():
+ ifto.is_chem():
+ self.elems=numpy.einsum("pqrs -> prqs",self.elems,optimize='greedy')
+ elifto.is_of():
+ self.elems=numpy.einsum("pqsr -> pqrs",self.elems,optimize='greedy')
+
+ self.ordering=to
+ returnself
+
+
+
+
+
+[docs]
+@dataclass
+classOrbitalData:
+ irrep:str=None# irrep of symmetry group (if assigned)
+ idx_irrep:int=None# index within the irrep
+ idx_total:int=None# index within the total set of orbitals
+ idx:int=None# index within the active space
+ energy:float=None# energy assigned to orbital
+ occ:float=None# occupation number assigned to orbital
+ pair:tuple=None# potential electron pair that the orbital is assigned to
+
+ def__post_init__(self):
+ # backward compatibility
+ ifself.pairisnotNoneandlen(self.pair)==1:
+ self.pair=(self.pair[0],self.pair[0])
+ self.occ=2.0# mark as reference
+
+ def__str__(self):
+ return"{"+"{}".format("".join(["{}:{}, ".format(k,v)fork,vinself.__dict__.items()ifvisnotNone])).rstrip().rstrip(",")+"}"
+
+
+
+[docs]
+classIntegralManager:
+"""
+ Manage Basis Integrals of Quantum Chemistry
+ All integrals are held in their original basis, the corresponding mo-coefficients have to be passed down
+ and are usually held by the QuantumChemistryBaseClass
+ """
+ _overlap_integrals:numpy.ndarray=None
+ _one_body_integrals:numpy.ndarray=None
+ _two_body_integrals:NBodyTensor=None
+ _constant_term:float=None
+ _basis_name:str="unknown"
+ _orbital_type:str="unknown"# e.g. "HF", "PNO", "native"
+ _orbital_coefficients:numpy.ndarray=None
+ _active_space:ActiveSpaceData=None
+ _orbitals:typing.List[OrbitalData]=None
+
+ def__init__(self,one_body_integrals,two_body_integrals,
+ basis_name="unknown",orbital_type="unknown",
+ constant_term=0.0,orbital_coefficients=None,active_space=None,overlap_integrals=None,orbitals=None,*args,**kwargs):
+ self._one_body_integrals=one_body_integrals
+ self._two_body_integrals=two_body_integrals
+ self._constant_term=constant_term
+ self._basis_name=basis_name
+ self._orbital_type=orbital_type
+
+ assertlen(self._one_body_integrals.shape)==2
+ assertlen(self._two_body_integrals.shape)==4
+ try:
+ two_body_integrals=two_body_integrals.reorder(to="chem")
+ exceptExceptionasE:
+ raiseTequilaException(
+ "{}\ntwo_body_integrals given in wrong format. Needs to be a tq.chemistry.NBodyTensor in chem ordering.\n{} with ordering={}".format(
+ str(E),str(type(two_body_integrals)),str(two_body_integrals.ordering)))
+
+ foriinrange(4):
+ assertself._one_body_integrals.shape[0]==self._two_body_integrals.elems.shape[i]
+ assertself._one_body_integrals.shape[0]==self._one_body_integrals.shape[1]
+
+ ifoverlap_integralsisNone:
+ overlap_integrals=numpy.eye(one_body_integrals.shape[0])
+ self._overlap_integrals=overlap_integrals
+ assertself._overlap_integrals.shape==self._one_body_integrals.shape
+
+ iforbital_coefficientsisNone:
+ # default are symmetrically orthogonalized orbitals in the given basis
+ orbital_coefficients=self.get_orthonormalized_orbital_coefficients()
+ self._orbital_coefficients=orbital_coefficients
+
+ iforbitalsisNone:
+ orbitals=[OrbitalData(idx_total=i,idx=i)foriinrange(one_body_integrals.shape[0])]
+
+ self._orbitals=orbitals
+ self.active_space=active_space
+
+
+[docs]
+ defget_orthonormalized_orbital_coefficients(self):
+"""
+ Computes orbitals in this basis that are orthonormal (through loewdin orthonormalization)
+
+ Returns
+ -------
+ coefficient matrix of orthonormalized orbitals
+ """
+ ifself.basis_is_orthogonal():
+ returnnumpy.eye(self._one_body_integrals.shape[0])
+
+ S=self._overlap_integrals
+ sv,U=numpy.linalg.eigh(S)
+ s=numpy.diag(numpy.asarray([1.0/numpy.sqrt(x)forxinsv]))
+ C=U.dot(s.dot(U.transpose()))
+ returnC
+
+
+ @property
+ defactive_orbitals(self):
+ return[self._orbitals[i]foriinself._active_space.active_orbitals]
+
+ @property
+ deforbitals(self):
+ returnself._orbitals
+
+ @property
+ defactive_space(self):
+ returnself._active_space
+
+ @active_space.setter
+ defactive_space(self,other):
+ self._active_space=other
+ forxinself._orbitals:
+ x.idx=None
+ forii,iinenumerate(other.active_orbitals):
+ self._orbitals[i].idx=ii
+
+ @property
+ defreference_orbitals(self):
+ return[self._orbitals[i]foriinself.active_space.reference_orbitals]
+
+ @property
+ defactive_reference_orbitals(self):
+ return[self._orbitals[i]foriinself.active_space.active_orbitalsifiinself.active_space.reference_orbitals]
+
+ @property
+ defoverlap_integrals(self):
+"""
+ Returns
+ -------
+ Overlap integrals in given basis (using basis functions, not molecular orbitals. No active space considered)
+ """
+ returnself._overlap_integrals
+
+ @property
+ defone_body_integrals(self):
+"""
+ Returns
+ -------
+ one_body integrals in given basis (using basis functions, not molecular orbitals. No active space considered)
+ """
+ returnself._one_body_integrals
+
+ @property
+ deftwo_body_integrals(self):
+"""
+ Returns
+ -------
+ two-body orbitals in given basis (using basis functions, not molecular orbitals. No active space considered)
+ ordering is "chem" i.e. Mulliken i.e. integrals_{abcd} = <ac|g|bd>
+ """
+ returnself._two_body_integrals
+
+ @property
+ defconstant_term(self):
+"""
+ Returns
+ -------
+ return constant term (usually nuclear repulsion). No active space considered
+ """
+ returnself._constant_term
+
+ @property
+ deforbital_coefficients(self):
+"""
+ second index is the orbital index, first the basis index
+ Returns
+ -------
+ orbital coefficient matrix C_{basis,orbital}
+ """
+ returnself._orbital_coefficients
+
+ @orbital_coefficients.setter
+ deforbital_coefficients(self,other):
+ self.verify_orbital_coefficients(orbital_coefficients=other)
+ self._orbital_coefficients=other
+ fori,xinenumerate(self._orbitals):
+ y=OrbitalData(idx=x.idx,idx_total=x.idx_total)
+ self._orbitals[i]=y
+
+
+[docs]
+ deftransform_to_native_orbitals(self):
+"""
+ Transform orbitals to orthonormal functions closest to the native basis
+ """
+ c=self.get_orthonormalized_orbital_coefficients()
+ self.orbital_coefficients=c
+ self._orbital_type="orthonormalized-{}-basis".format(self._basis_name)
+[docs]
+ deftransform_orbitals(self,U,name=None):
+"""
+ Transform orbitals
+ Parameters
+ ----------
+ U: second index is new orbital indes, first is old orbital index (summed over)
+
+ Returns
+ -------
+ updates the structure with new orbitals: c = cU
+ """
+ assertself.is_unitary(U)
+ self.orbital_coefficients=numpy.einsum("ix, xj -> ij",self.orbital_coefficients,U,optimize="greedy")
+ ifnameisNone:
+ self._orbital_type+="-transformed"
+ else:
+ self._orbital_type=name
+
+
+
+[docs]
+ defget_integrals(self,orbital_coefficients=None,ordering="openfermion",ignore_active_space=False,*args,**kwargs):
+"""
+ Get all molecular integrals in given orbital basis (determined by orbital_coefficients in self or the ones passed here)
+ active space is considered if not explicitly ignored
+ Parameters
+ ----------
+ orbital_coefficients: orbital coefficients in the given basis (first index is basis, second index is orbitals). Need to go over full basis (no active space)
+ ordering: ordering of the two-body integrals (default is openfermion)
+ ignore_active_space: ignore active space and give back full integrals
+
+ Returns
+ -------
+
+ """
+ iforbital_coefficientsisNone:
+ orbital_coefficients=self.orbital_coefficients
+
+ c=self.constant_term
+ h=self._get_transformed_one_body_integrals(orbital_coefficients=orbital_coefficients)
+ g=self._get_transformed_two_body_integrals(orbital_coefficients=orbital_coefficients,ordering=ordering)
+ ifnotignore_active_spaceandself._active_spaceisnotNone:
+
+ g=g.reorder(to="openfermion").elems
+
+ active_integrals=get_active_space_integrals(one_body_integrals=h,two_body_integrals=g,
+ occupied_indices=self._active_space.frozen_reference_orbitals,
+ active_indices=self._active_space.active_orbitals)
+
+ c=active_integrals[0]+c
+
+ h=active_integrals[1]
+ g=NBodyTensor(elems=active_integrals[2],ordering="openfermion")
+ g.reorder(to=ordering)
+ returnc,h,g
+[docs]
+ defverify_orbital_coefficients(self,orbital_coefficients,tolerance=1.e-5):
+"""
+ Verify if orbital coefficients are valid (i.e. if they define a orthonormal set of orbitals)
+ Parameters
+ ----------
+ orbital_coefficients: the orbital coefficients C_ij with i:basis and j:orbitals
+ tolerance
+
+ Returns
+ -------
+ True or False depending if the overlap matrix of the basis is transformed to a unit matrix
+
+ """
+ S=self.overlap_integrals
+ St=numpy.einsum("ix, xj -> ij",S,orbital_coefficients,optimize='greedy')
+ St=numpy.einsum("xj, xi -> ij",St,orbital_coefficients,optimize='greedy')
+ returnnumpy.linalg.norm(St-numpy.eye(S.shape[0]))<tolerance
Source code for tequila_code.quantumchemistry.encodings
+"""
+Collections of Fermion-to-Qubit encodings known to tequila
+Most are Interfaces to OpenFermion
+"""
+importabc
+
+fromtequilaimportTequilaException
+fromtequila.circuit.circuitimportQCircuit
+fromtequila.circuit.gatesimportX,CNOT
+fromtequila.hamiltonian.qubit_hamiltonianimportQubitHamiltonian
+importopenfermion
+importnumpy
+
+
+
+[docs]
+ defdo_transform(self,fermion_operator:openfermion.FermionOperator,*args,**kwargs)->openfermion.QubitOperator:
+ raiseException("{}::do_transform: called base class".format(type(self).__name__))
+
+
+
+[docs]
+ defmap_state(self,state:list,*args,**kwargs)->list:
+"""
+ Expects a state in spin-orbital ordering
+ Returns the corresponding qubit state in the class encoding
+ :param state:
+ basis-state as occupation number vector in spin orbitals
+ sorted as: [0_up, 0_down, 1_up, 1_down, ... N_up, N_down]
+ with N being the number of spatial orbitals
+ :return:
+ basis-state as qubit state in the corresponding mapping
+ """
+"""Does a really lazy workaround ... but it works
+ :return: Hartree-Fock Reference as binary-number
+
+ Parameters
+ ----------
+ reference_orbitals: list:
+ give list of doubly occupied orbitals
+ default is None which leads to automatic list of the
+ first n_electron/2 orbitals
+
+ Returns
+ -------
+
+ """
+ # default is a lazy workaround, but it workds
+ n_qubits=2*self.n_orbitals
+
+ spin_orbitals=sorted([ifori,xinenumerate(state)ifint(x)==1])
+
+ string="1.0 ["
+ foriinspin_orbitals:
+ string+=str(i)+"^ "
+ string+="]"
+
+ fop=openfermion.FermionOperator(string,1.0)
+ op=self(fop)
+ fromtequila.wavefunction.qubit_wavefunctionimportQubitWaveFunction
+ wfn=QubitWaveFunction.from_int(0,n_qubits=n_qubits)
+ wfn=wfn.apply_qubitoperator(operator=op)
+ assert(len(wfn.keys())==1)
+ key=list(wfn.keys())[0].array
+ returnkey
+
+
+
+[docs]
+ @abc.abstractmethod
+ defme_to_jw(self)->QCircuit:
+"""
+ This method needs to be implemented to enable default conversions via Jordan-Wigner
+ """
+ pass
+
+
+ # independent conversion methods, these are used for default conversions
+ # arXiv:1808.10402 IV. B. 2, Eq. 57
+ # original: https://doi.org/10.1063/1.4768229
+ def_jw_to_bk(self)->QCircuit:
+ U=QCircuit()# Constructs empty circuit
+
+ flipper=False
+ foriinrange(self.n_orbitals*2):
+ # even qubits only hold their own value
+ ifi%2==0:
+ continue
+
+ # sum always includes the last qubit
+ U+=CNOT(control=i-1,target=i)
+
+ # every second odd qubit ties together with the last odd qubit
+ ifflipper:
+ U+=CNOT(control=i-2,target=i)
+
+ flipper=notflipper
+
+ # we have now created the 4x4 blocks on the diagonal of this operators matrix
+
+ # every power of 2 connects to the last power of 2
+ # this corresponds to the last row in the recursive definitions being all 1s
+ x=numpy.log2(i+1)
+ ifx.is_integer()andx>=3:
+ x=int(x)
+ U+=CNOT(control=2**(x-1)-1,target=i)
+
+ returnU
+
+ def_hcb_to_jw(self):
+ U=QCircuit()
+ foriinrange(self.n_orbitals):
+ U+=X(target=self.down(i),control=self.up(i))
+ returnU
+
+ # Convenience Methods
+
+[docs]
+ defdo_transform(self,fermion_operator:openfermion.FermionOperator,*args,**kwargs)->openfermion.QubitOperator:
+ n_qubits=openfermion.count_qubits(fermion_operator)
+ ifn_qubits!=self.n_orbitals*2:
+ raiseException(
+ "BravyiKitaevFast transformation currently only possible for full Hamiltonians (no UCC generators).\nfermion_operator was {}".format(
+ fermion_operator))
+ op=openfermion.get_interaction_operator(fermion_operator)
+ returnopenfermion.bravyi_kitaev_fast(op)
+[docs]
+ defplot2cube(self,orbital,filename=None,*args,**kwargs):
+"""
+ plot orbitals to cube file (needs madtequila backend installed)
+ Parameters
+ ----------
+ method: orbital, the orbital index (starting from 0 on the active orbitals)
+ if you want to plot frozen orbitals you can hand in a Tequila Orbital structure with idx_total defined
+ filename: name of the cubefile (default: mra_orbital_X.cube where X is the total index of the active orbital)
+ args: further arguments for plot2cube
+ kwargs further keyword arguments for plot2cube
+
+ see here for more https://github.com/kottmanj/madness/tree/tequila/src/apps/plot
+ """
+
+ plot2cube=shutil.which("plot2cube")
+ ifplot2cubeisNone:
+ raiseTequilaMadnessException(
+ "can't plot to cube file. Couldn't find plot2cube executable.\n\nTry installing\n\t conda install madtequila -c kottmann\nand assure the version is >2.3")
+
+ ifhasattr(orbital,"idx"):
+ idx=orbital.idx
+ else:
+ idx=self.orbitals[orbital].idx_total
+
+ callist=[plot2cube,"file=mra_orbital_{}".format(idx)]
+
+ iffilenameisnotNone:
+ callist.append("outfile={}".format(filename))
+ fork,vinkwargs.items():
+ callist.append("{}={}".format(k,v))
+ forkinargs:
+ callist.append("{}".format(k))
+
+ importsubprocess
+ try:
+ withopen("plot2cube_{}.log".format(orbital),"w")aslogfile:
+ subprocess.call(callist,stdout=logfile)
+ except:
+ print("plotting failed ....")
+ print("see plot2cube_{}.log".format(orbital))
+
+
+ def__init__(self,parameters:ParametersQC,
+ transformation:typing.Union[str,typing.Callable]=None,
+ active_orbitals:list="auto",
+ executable:str=None,
+ n_pno:int=None,
+ n_virt:int=0,
+ keep_mad_files=False,
+ datadir=None,
+ *args,
+ **kwargs):
+
+ self.datadir=datadir
+
+ # see if MAD_ROOT_DIR is defined
+ self.madness_root_dir=os.environ.get("MAD_ROOT_DIR")
+ # see if the pno_integrals executable can be found
+ ifexecutableisNone:
+ executable=self.find_executable()
+ ifexecutableisNoneandself.madness_root_dirisnotNone:
+ warnings.warn("MAD_ROOT_DIR={} found\nbut couldn't find executable".format(self.madness_root_dir),
+ TequilaWarning)
+
+
+ else:
+ executable=shutil.which(executable)
+
+ self.executable=executable
+ self.n_pno=n_pno
+ self.n_virt=n_virt
+ self.kwargs=kwargs
+
+ # if no n_pno is given, look for MRA data (default)
+ name=parameters.name
+
+ # try to read in data in the following cases
+ # - no executable found
+ # - executable found but read in explicitly demanded through n_pno="read"
+ if(n_pnoisNoneandexecutableisNone)or(hasattr(n_pno,"lower")andn_pno.lower()=="read"):
+ h,g=self.read_tensors(name=name,datadir=datadir)
+ n_pno=None
+ else:
+ h="failed"
+ g="failed"
+
+ if(isinstance(h,str)and"failed"inh)or(isinstance(g,str)and"failed"ing):
+ status="found {}_htensor.npy={}\n".format(name,"failed"notinh)
+ status+="found {}_gtensor.npy={}\n".format(name,"failed"noting)
+ try:
+ # try to run madness
+ self.parameters=parameters
+ status+="madness="
+ madness_status=self.run_madness(*args,**kwargs)
+ ifint(madness_status)!=0:
+ warnings.warn("MADNESS did not terminate as expected! status = {}".format(status),TequilaWarning)
+ status+=str(madness_status)+"\n"
+ exceptExceptionasE:
+ status+=str(E)+"\n"
+
+ # will read the binary files, convert them and save them with the right name
+ h,g,pinfo=self.convert_madness_output_from_bin_to_npy(name=name,datadir=datadir)
+ status+="found {}_htensor.npy={}\n".format(name,"failed"notinh)
+ status+="found {}_gtensor.npy={}\n".format(name,"failed"noting)
+ status+="found {}_pnoinfo.txt={}\n".format(name,"failed"notinpinfo)
+ status+="h_tensor report:\n"
+ status+=str(h)
+ status+="g_tensor report:\n"
+ status+=str(g)
+ status+="pnoinfo report:\n"
+ status+=str(pinfo)
+
+ solution="Solution 1: Assuming precomputed files are available:\n provide {name}_gtensor.npy, {name}_htensor.npy and {name}_pnoinfo.txt\n and call the Molecule constructor with n_pno='read' keyword \n\nSolution 2: Try installing with conda\n conda install madtequila -c kottmann\n\nSolution 3: Install from source\n follow instructions on github.com/kottmanj/madness".format(
+ name=name)
+ ifself.executableisnotNone:
+ solution="madness executable was found, but calculation did not succeed, check {name}_pno_integrals.out for clues".format(
+ name=name)
+
+ if"failed"inhor"failed"ing:
+ raiseTequilaMadnessException("Could not initialize the madness interface\n"
+ "Status report is\n"
+ "{status}\n\n".format(status=status)+solution)
+ # get additional information from madness file
+ nuclear_repulsion=0.0
+ pairinfo=None
+ occinfo=None
+ path=parameters.name
+ ifdatadirisnotNone:
+ path="{}/{}".format(datadir,path)
+ fornamein[path+"_pnoinfo.txt",parameters.name+"_pnoinfo.txt","pnoinfo.txt"]:
+ try:
+ withopen(name,"r")asf:
+ forlineinf.readlines():
+ if"nuclear_repulsion"inline:
+ nuclear_repulsion=float(line.split("=")[1])
+ elif"pairinfo"inline:
+ pairinfo=line.split("=")[1].split(",")
+ pairinfo=[tuple([int(i)foriinx.split(".")])forxinpairinfo]
+ elif"occinfo"inline:
+ occinfo=line.split("=")[1].split(",")
+ occinfo=[float(x)forxinoccinfo]
+
+ ifpairinfoisnotNone:
+ break
+ except:
+ continue
+
+ ifpairinfoisNone:
+ raiseTequilaMadnessException("Pairinfo from madness calculation not found\nPlease provide pnoinfo.txt")
+
+ n_orbitals_total=h.shape[0]
+ if"n_orbitals"inkwargs:
+ # this would be the active orbitals
+ kwargs.pop("n_orbitals")
+
+ asserth.shape[1]==n_orbitals_total
+ assertsum(g.shape)==4*n_orbitals_total
+ assertlen(g.shape)==4
+ assertlen(h.shape)==2
+
+ g=NBodyTensor(elems=g,ordering='mulliken')
+
+ orbitals=[]
+ ifpairinfoisnotNone:
+ orbitals=[OrbitalData(idx_total=i,idx=i,pair=p,occ=occinfo[i])fori,pin
+ enumerate(pairinfo)]
+ reference_orbitals=[xforxinorbitalsifx.occ==2.0]
+ ifactive_orbitals=="auto":
+ not_active=[iforiinreference_orbitalsif
+ sum([1forxinorbitalsifi.idx_totalinx.pair])<2]
+ active_orbitals=[x.idx_totalforxinorbitalsifxnotinnot_active]
+
+ ifactive_orbitalsisnotNone:
+ i=0
+ forxinorbitals:
+ ifx.idx_totalinactive_orbitals:
+ orbitals[x.idx_total].idx=i
+ i+=1
+ else:
+ orbitals[x.idx_total].idx=None
+ else:
+ raiseTequilaMadnessException("No pairinfo given: madness interface needs a file moleculename_pnoinfo.txt")
+
+ # convert to indices only
+ # active space data will be set in baseclass constructor
+ reference_orbitals=[x.idx_totalforxinreference_orbitals]
+ super().__init__(parameters=parameters,
+ transformation=transformation,
+ active_orbitals=active_orbitals,
+ one_body_integrals=h,
+ two_body_integrals=g,
+ nuclear_repulsion=nuclear_repulsion,
+ n_orbitals=n_orbitals_total,
+ orbitals=orbitals,
+ reference_orbitals=reference_orbitals,
+ *args,
+ **kwargs)
+
+ # print warning if read data does not match expectations
+ ifn_pnoisnotNone:
+ nrefs=len(self.reference_orbitals)
+ ifn_pno+nrefs+n_virt!=self.n_orbitals:
+ warnings.warn(
+ "read in data has {} pnos/virtuals, but n_pno and n_virt where set to {} and {}".format(
+ self.n_orbitals-nrefs,n_pno,n_virt),TequilaWarning)
+
+ # delete *.bin files and pnoinfo.txt form madness calculation
+ ifnotkeep_mad_files:
+ self.cleanup(warn=False,delete_all_files=False)
+
+
+[docs]
+ defcompute_energy(self,method,*args,**kwargs):
+"""
+ Call classical methods over PySCF (needs to be installed) or
+ use as a shortcut to calculate quantum energies (see make_upccgsd_ansatz)
+
+ Parameters
+ ----------
+ method: method name
+ classical: HF, MP2, CCSD, CCSD(T), FCI
+ quantum: SPA-GASD (SPA can be dropped as well as letters in GASD)
+ examples: GSD is the same as UpCCGSD, SPA alone is equivalent to SPA-D
+ see make_upccgsd_ansatz of the this class for more information
+ args
+ kwargs
+
+ Returns
+ -------
+
+ """
+
+ ifany([xinmethod.upper()forxin["U","SPA","PNO","HCB"]]):
+ # simulate entirely in HCB representation if no singles are involved
+ if"S"notinmethod.upper().split("-")[-1]and"HCB"notinmethod.upper():
+ method="HCB-"+method
+ U=self.make_upccgsd_ansatz(name=method)
+ if"hcb"inmethod.lower():
+ H=self.make_hardcore_boson_hamiltonian()
+ else:
+ H=self.make_hamiltonian()
+ E=ExpectationValue(H=H,U=U)
+ fromtequilaimportminimize
+ returnminimize(objective=E,*args,**kwargs).energy
+ else:
+ returnsuper().compute_energy(method=method,*args,**kwargs)
+
+
+
+[docs]
+ deflocal_qubit_map(self,hcb=False,up_then_down=False):
+ # re-arrange orbitals to result in more local circuits
+ # does not make the circuit more local, but rather will show locality better in pictures
+ # transform circuits and Hamiltonians with this map
+ # H = H.map_qubits(qubit_map), U = U.map_qubits(qubit_map)
+ # hcb: same for the harcore_boson representation
+ ordered_qubits=[]
+ pairs=[iforiinrange(self.n_electrons//2)]
+ foriinpairs:
+ pnos=[i]+[a.idxforainself.get_pair_orbitals(i=i,j=i,exclude=i)]
+ ifhcb:
+ up=[iforiinpnos]
+ ordered_qubits+=up
+ else:
+ ifup_then_down:
+ up=[self.transformation.up(i)foriinpnos]
+ down=[self.transformation.down(i)foriinpnos]
+ ordered_qubits+=up+down
+ else:
+ foriinpnos:
+ ordered_qubits.append(self.transformation.up(i))
+ ordered_qubits.append(self.transformation.down(i))
+
+ qubit_map={x:ifori,xinenumerate(ordered_qubits)}
+ returnqubit_map
+
+
+
+[docs]
+ defmake_spa_ansatz(self,label=None,hcb=False):
+"""
+ Shortcut for convenience
+ Parameters
+ ----------
+ label:
+ label for the angles
+ hcb:
+ if True the circuit will not map from HCB to JW (or other encodings that might be supported in the future)
+ Returns
+ -------
+ Default SPA ansatz (equivalent to PNO-UpCCD with madness PNOs)
+
+ """
+ name="SPA-UpCCD"
+ ifhcband"HCB"notinname.upper():
+ name="HCB-"+name
+ returnself.make_upccgsd_ansatz(name=name,label=label)
+
+
+
+[docs]
+ defmake_upccgsd_ansatz(self,name="UpCCGSD",label=None,direct_compiling=None,order=None,neglect_z=None,
+ hcb_optimization=None,include_reference=True,*args,**kwargs):
+"""
+ Overwriting baseclass to allow names like : PNO-UpCCD etc
+ Parameters
+ ----------
+ label: label the variables of the ansatz ( variables will be labelled (indices, X, (label, layer) witch X=D/S)
+ direct_compiling: Directly compile the first layer (works only for transformation that implement the hcb_to_me function)
+ name: ansatz name (SPA-UpCCD or SPA-D, SPA-UpCCGD or SPA-GD, SPA-UpCCGSD or SPA-GSD, UpCCGSD or GSD ..., in general {HCB}-{SPA}-{Excitations}
+ if HCB is included in name: do not map from hard-core Boson to qubit encoding of this molecule
+ if SPA is included in name: Use the separable pair ansatz (excitations will be restricted to the PNO structure of the surrogate model)
+ Excitations: can be "S" (use only singles), "D" (use only doubles), "GSD" (generalized singles and doubles), "GASD" (approximate singles, neglecting Z terms in JW)
+ neglect_z: neglect all Z terms in singles excitations generators
+ order: repetition of layers, can be given over the name as well, the order needs to be the first in the name then (i.e. 2-UpCCGSD, 2-SPA-GSD, etc)
+ args
+ kwargs
+
+ Returns
+ -------
+
+ """
+ # check if the used qubit encoding has a hcb transformation
+ try:
+ have_hcb_trafo=self.transformation.hcb_to_me()isnotNone
+ except:
+ have_hcb_trafo=False
+ name=name.upper()
+
+ # Default Method
+ if"SPA"innameandname.split("-")[-1]in["SPA","HCB"]:
+ name+="-D"
+
+ excitations=name.split("-")[-1]
+
+ if"HCB"innameand"S"inexcitations:
+ raiseTequilaMadnessException("name={}, HCB + Singles can't be realized".format(name))
+
+ if"HCB"innameand"D"notinexcitations:
+ raisewarnings.warn("name={}, HCB without Doubles has no result ".format(name),TequilaWarning)
+
+ if"S"notinexcitationsand"D"notinexcitations:
+ raisewarnings.warn("name={}, neither singles nor doubles requested".format(name),TequilaWarning)
+
+ if"T"inexcitationsor"Q"inexcitations:
+ raisewarnings.warn("name={}, only singles and doubles supported".format(name),TequilaWarning)
+
+ if(have_hcb_trafoand"D"inexcitationsor"HCB"inname)andinclude_referenceandhcb_optimizationisNone:
+ hcb_optimization=True
+
+ ifhcb_optimizationanddirect_compilingisNone:
+ direct_compiling=True
+
+ if("A"inexcitations)andneglect_zisNone:
+ neglect_z=True
+ # spin adaption does not work with z neglected
+ if"spin_adapt_singles"notinkwargs:
+ kwargs["spin_adapt_singles"]=False
+ else:
+ neglect_z=False
+ # switch on by default
+ if"spin_adapt_singles"notinkwargs:
+ kwargs["spin_adapt_singles"]=True
+
+ ifdirect_compilingandnothave_hcb_trafoandnot"HCB"inname:
+ raiseTequilaMadnessException(
+ "direct_compiling={} demanded but no hcb_to_me in transformation={}\ntry transformation=\'ReorderedJordanWigner\' ".format(
+ direct_compiling,self.transformation))
+
+ name=name.upper()
+ iforderisNone:
+ try:
+ if"-"inname:
+ order=int(name.split("-")[0])
+ else:
+ order=1
+ except:
+ order=1
+
+ # first layer
+ U=QCircuit()
+ ifhcb_optimization:
+ if"D"inexcitations:
+ U=self.make_hardcore_boson_pno_upccd_ansatz(include_reference=include_reference,
+ direct_compiling=direct_compiling,
+ label=(label,0))
+ elifinclude_reference:
+ U=self.prepare_hardcore_boson_reference()
+
+ indices0=[k.name[0]forkinU.extract_variables()]
+ indices1=self.make_upccgsd_indices(label=label,name=name,exclude=indices0,*args,**kwargs)
+ if"D"inexcitations:
+ U+=self.make_hardcore_boson_upccgd_layer(indices=indices1,label=(label,0),*args,**kwargs)
+ indices=indices0+indices1
+ if"HCB"notinnameandlen(U.gates)>0:
+ U=self.hcb_to_me(U=U)
+ else:
+ assert"S"notinexcitations
+ if"S"inexcitations:
+ U+=self.make_upccgsd_singles(indices=indices,label=(label,0),neglect_z=neglect_z,*args,**kwargs)
+ else:
+ indices=self.make_upccgsd_indices(label=(label,0),name=name,*args,**kwargs)
+ ifinclude_reference:
+ U=self.prepare_reference()
+ U+=self.make_upccgsd_layer(indices=indices,include_singles="S"inexcitations,
+ include_doubles="D"inexcitations,label=(label,0),neglect_z=neglect_z,
+ *args,**kwargs)
+
+ iforder>1:
+ forlayerinrange(1,order):
+ indices=self.make_upccgsd_indices(label=(label,layer),name=name,*args,**kwargs)
+ if"HCB"inname:
+ U+=self.make_hardcore_boson_upccgd_layer(indices=indices,label=(label,layer),*args,**kwargs)
+ else:
+ U+=self.make_upccgsd_layer(indices=indices,include_singles="S"inexcitations,
+ include_doubles="D"inexcitations,label=(label,layer),
+ neglect_z=neglect_z,*args,**kwargs)
+ returnU
+[docs]
+ defperturbative_f12_correction(self,rdm1:numpy.ndarray=None,rdm2:numpy.ndarray=None,n_ri:int=None,
+ f12_filename:str="molecule_f12tensor.bin",**kwargs)->float:
+"""
+ Computes the spin-free [2]_R12 correction, needing only the 1- and 2-RDM of a reference method
+ Requires either 1-RDM, 2-RDM or information to compute them in kwargs
+
+ Parameters
+ ----------
+ rdm1 :
+ 1-electron reduced density matrix
+ rdm2 :
+ 2-electron reduced density matrix
+ gamma :
+ f12-exponent, for a correlation factor f_12 = -1/gamma * exp[-gamma*r_12]
+ n_ri :
+ dimensionality of RI-basis; if None, then the maximum available via tensors / basis-set is used
+ f12_filename :
+ when using madness_interface, <q|h|p> and <rs|1/r_12|pq> already available;
+ need to provide f12-tensor <rs|f_12|pq> as ".bin" from madness or ".npy", assuming Mulliken ordering
+ kwargs :
+ e.g. RDM-information via {"U": QCircuit, "variables": optimal angles}, needs to be passed if rdm1,rdm2 not
+ yet computed
+
+ Returns
+ -------
+ the f12 correction for the energy
+ """
+ from.f12_corrections._f12_correction_madnessimportExplicitCorrelationCorrectionMadness
+ correction=ExplicitCorrelationCorrectionMadness(mol=self,rdm1=rdm1,rdm2=rdm2,n_ri=n_ri,
+ f12_filename=f12_filename,**kwargs)
+
+ returncorrection.compute()
Source code for tequila_code.quantumchemistry.orbital_optimizer
+importnumpy
+importtyping
+importcopy
+importwarnings
+fromdataclassesimportdataclass,field
+
+fromtequilaimportQCircuit,ExpectationValue,minimize,TequilaWarning
+from.importQuantumChemistryBase,ParametersQC,NBodyTensor
+
+"""
+Small application that wraps a tequila VQE object and passes it to the PySCF CASSCF solver.
+This way we can conveniently optimize orbitals.
+This basically does what is described here in the context of orbital-optimized unitary coupled-cluster (oo-ucc): https://journals.aps.org/prresearch/pdf/10.1103/PhysRevResearch.2.033421
+Of course we don't have to use UCC circuits but can pass whatever we want as circuit, or pass a "vqe_solver" object.
+
+The Interface with the PySCF module follows the original PySCF article https://arxiv.org/abs/2002.12531 (see Fig.3)
+
+Currently this is a beta version (not extensively used in real life), so be careful when using it and please report issues on github :-)
+"""
+
+
+[docs]
+@dataclass
+classOptimizeOrbitalsResult:
+
+ old_molecule:QuantumChemistryBase=None# the old tequila molecule
+ molecule:QuantumChemistryBase=None# the new tequila molecule with transformed orbitals
+ mcscf_object:object=None# the pyscf mcscf object
+ mcscf_local_data:dict=None
+ mo_coeff=None# the optimized mo coefficients
+ energy:float=None# the optimized energy
+ iterations:int=0
+
+ def__call__(self,local_data,*args,**kwargs):
+ # use as callback
+ if"u"inlocal_data:
+ self.rotation_matrix=copy.deepcopy(local_data["u"])
+ self.mcscf_local_data=local_data
+ self.iterations+=1
+
+
+
+[docs]
+defoptimize_orbitals(molecule,circuit=None,vqe_solver=None,pyscf_arguments=None,silent=False,
+ vqe_solver_arguments=None,initial_guess=None,return_mcscf=False,use_hcb=False,molecule_factory=None,molecule_arguments=None,restrict_to_active_space=True,*args,**kwargs):
+"""
+
+ Parameters
+ ----------
+ molecule: The tequila molecule whose orbitals are to be optimized
+ circuit: The circuit that defines the ansatz to the wavefunction in the VQE
+ can be None, if a customized vqe_solver is passed that can construct a circuit
+ vqe_solver: The VQE solver (the default - vqe_solver=None - will take the given circuit and construct an expectationvalue out of molecule.make_hamiltonian and the given circuit)
+ A customized object can be passed that needs to be callable with the following signature: vqe_solver(H=H, circuit=self.circuit, molecule=molecule, **self.vqe_solver_arguments)
+ pyscf_arguments: Arguments for the MCSCF structure of PySCF, if None, the defaults are {"max_cycle_macro":10, "max_cycle_micro":3} (see here https://pyscf.org/pyscf_api_docs/pyscf.mcscf.html)
+ silent: silence printout
+ use_hcb: indicate if the circuit is in hardcore Boson encoding
+ vqe_solver_arguments: Optional arguments for a customized vqe_solver or the default solver
+ for the default solver: vqe_solver_arguments={"optimizer_arguments":A, "restrict_to_hcb":False} where A holds the kwargs for tq.minimize
+ restrict_to_hcb keyword controls if the standard (in whatever encoding the molecule structure has) Hamiltonian is constructed or the hardcore_boson hamiltonian
+ initial_guess: Initial guess for the MCSCF module of PySCF (Matrix of orbital rotation coefficients)
+ The default (None) is a unit matrix
+ predefined commands are
+ initial_guess="random"
+ initial_guess="random_loc=X_scale=Y" with X and Y being floats
+ This initialized a random guess using numpy.random.normal(loc=X, scale=Y) with X=0.0 and Y=0.1 as defaults
+ return_mcscf: return the PySCF MCSCF structure after optimization
+ molecule_arguments: arguments to pass to molecule_factory or default molecule constructor | only change if you know what you are doing
+ args: just here for convenience
+ kwargs: just here for conveniece
+
+ Returns
+ -------
+ Optimized Tequila Molecule
+ """
+
+ try:
+ frompyscfimportmcscf
+ from.importQuantumChemistryPySCF
+ exceptExceptionasexception:
+ raiseException("{}\noptimize_orbitals: Need pyscf to run (pip install pyscf)".format(str(exception)))
+
+ ifpyscf_argumentsisNone:
+ pyscf_arguments={"max_cycle_macro":10,"max_cycle_micro":3}
+ no=molecule.n_orbitals
+
+ ifnotisinstance(molecule,QuantumChemistryPySCF):
+ pyscf_molecule=QuantumChemistryPySCF.from_tequila(molecule=molecule,transformation=molecule.transformation)
+ else:
+ pyscf_molecule=molecule
+
+ mf=pyscf_molecule._get_hf()
+ result=OptimizeOrbitalsResult()
+ mc=mcscf.CASSCF(mf,pyscf_molecule.n_orbitals,pyscf_molecule.n_electrons)
+ mc.callback=result
+ c=pyscf_molecule.compute_constant_part()
+
+ ifcircuitisNoneandvqe_solverisNone:
+ raiseException("optimize_orbitals: Either provide a circuit or a callable vqe_solver")
+
+ ifuse_hcb:
+ ifvqe_solver_argumentsisNone:
+ vqe_solver_arguments={}
+ vqe_solver_arguments["restrict_to_hcb"]=True
+ # consistency check
+ n_qubits=len(circuit.qubits)
+ n_orbitals=molecule.n_orbitals
+ ifn_qubits>n_orbitals:
+ warnings.warn("Potential inconsistency in orbital optimization: use_hcb is switched on but we have\n n_qubits={} in the circuit\n n_orbital={} in the molecule\n".format(n_qubits,n_orbitals),TequilaWarning)
+
+ ifmolecule_argumentsisNone:
+ molecule_arguments={"parameters":pyscf_molecule.parameters,"transformation":molecule.transformation}
+
+ wrapper=PySCFVQEWrapper(molecule_arguments=molecule_arguments,n_electrons=pyscf_molecule.n_electrons,
+ const_part=c,circuit=circuit,vqe_solver_arguments=vqe_solver_arguments,silent=silent,
+ vqe_solver=vqe_solver,molecule_factory=molecule_factory,*args,**kwargs)
+ mc.fcisolver=wrapper
+ mc.internal_rotation=True
+ ifpyscf_argumentsisnotNone:
+ fork,vinpyscf_arguments.items():
+ ifhasattr(mc,str(k)):
+ setattr(mc,str(k),v)
+ else:
+ print("unknown arguments: {}".format(k))
+ ifnotsilent:
+ print("Optimizing Orbitals with PySCF and VQE Solver:")
+ print("{:25} : {}".format("pyscf_arguments",pyscf_arguments))
+ print(wrapper)
+ ifinitial_guessisnotNone:
+ ifhasattr(initial_guess,"lower"):
+ if"random"or"near_zero"ininitial_guess.lower():
+ scale=1.e-3
+ if"random"ininitial_guess.lower():
+ scale=1.0
+ loc=0.0
+ if"scale"inkwargs:
+ scale=float(initial_guess.split("scale")[1].split("_")[0].split("=")[1])
+ if"loc"inkwargs:
+ loc=float(initial_guess.split("loc")[1].split("_")[0].split("=")[1])
+ initial_guess=numpy.eye(no)+numpy.random.normal(scale=scale,loc=loc,size=no*no).reshape(no,no)
+ else:
+ raiseException("Unknown initial_guess={}".format(initial_guess.lower()))
+
+ assertlen(initial_guess.shape)==2
+ assertinitial_guess.shape[0]==no
+ assertinitial_guess.shape[1]==no
+ initial_guess=mcscf.project_init_guess(mc,initial_guess)
+ mc.kernel(mo_coeff=initial_guess)
+ else:
+ mc.kernel()
+ # make new molecule
+
+ mo_coeff=mc.mo_coeff
+ transformed_molecule=pyscf_molecule.transform_orbitals(orbital_coefficients=mo_coeff,name="optimized")
+ result.molecule=transformed_molecule
+ result.old_molecule=molecule
+ result.mo_coeff=mo_coeff
+ result.energy=mc.e_tot
+
+ ifreturn_mcscf:
+ result.mcscf_object=mc
+
+ returnresult
+
+
+
+[docs]
+@dataclass
+classPySCFVQEWrapper:
+"""
+ Wrapper for tequila VQE's to be compatible with PySCF orbital optimization
+ """
+
+ # needs initialization
+ n_electrons:int=None
+ molecule_arguments:dict=None
+
+ # internal data
+ rdm1:numpy.ndarray=None
+ rdm2:numpy.ndarray=None
+ one_body_integrals:numpy.ndarray=None
+ two_body_integrals:numpy.ndarray=None
+ history:list=field(default_factory=list)
+
+ # optional
+ const_part:float=0.0
+ silent:bool=False
+ vqe_solver:typing.Callable=None
+ circuit:QCircuit=None
+ vqe_solver_arguments:dict=field(default_factory=dict)
+ molecule_factory:typing.Callable=None
+
+
+[docs]
+ defreorder(self,M,ordering,to):
+ # convenience since we need to reorder
+ # all the time
+ M=NBodyTensor(elems=M,ordering=ordering)
+ M.reorder(to=to)
+ returnM.elems
+[docs]
+classQuantumChemistryPsi4(QuantumChemistryBase):
+
+ def_make_psi4_active_space_data(self,active_orbitals,reference=None):
+"""
+ Small helper function
+ Internal use only
+ Parameters
+ ----------
+ active_orbitals: dictionary :
+ dictionary with irreps as keys and a list of integers as values
+ i.e. occ = {"A1":[0,1], "A2":[0]}
+ means the occupied active space is made up of spatial orbitals
+ 0A1, 1A1 and 0A2
+ as list: Give a list of spatial orbital indices
+ i.e. occ = [0,1,3] means that spatial orbital 0, 1 and 3 are used
+ reference: (Default value=None)
+ List of orbitals which form the reference
+ Can be given in the same format as active_orbitals
+ If given as None then the first N_electron/2 orbitals are taken
+ and the corresponding active orbitals are removed
+
+ Returns
+ -------
+ Dataclass with active indices and reference indices (in spatial notation)
+
+ """
+
+ ifactive_orbitalsisNone:
+ returnNone
+
+ @dataclass
+ classActiveSpaceDataPsi4(ActiveSpaceData):
+
+ frozen_docc:list=None# frozen reference orbitals grouped by irrep (psi4 option, if None then the active space can not be represented by psi4)
+ frozen_uocc:list=None# frozen virtual orbtials grouped by irrep (psi4 option, if None then the active space can not be represented by psi4)
+
+ def__str__(self):
+ result=super().__str__()
+ result+="{key:15} : {value:15}\n".format(key="frozen_docc",value=str(self.frozen_docc))
+ result+="{key:15} : {value:15}\n".format(key="frozen_uocc",value=str(self.frozen_uocc))
+ returnresult
+
+ @property
+ defpsi4_representable(self):
+ standard_ref=[self.reference_orbitals[0]==0]+[
+ self.reference_orbitals[i]==self.reference_orbitals[i+1]-1foriin
+ range(len(self.reference_orbitals)-1)]
+ returnself.frozen_doccisnotNoneandself.frozen_uoccisnotNoneandall(standard_ref)
+
+ # transform irrep notation to absolute ints
+ active_idx=active_orbitals
+ ref_idx=reference
+ ifisinstance(active_orbitals,dict):
+ active_idx=[]
+ forkey,valueinactive_orbitals.items():
+ active_idx+=[self.orbitals_by_irrep[key.upper()][i].idx_totalforiinvalue]
+ elifactive_orbitalsisNone:
+ active_idx=[iforiinrange(self.n_orbitals)]
+
+ standard_ref=sorted([iforiinrange(self.n_electrons//2)])
+ ifisinstance(reference,dict):
+ ref_idx=[]
+ forkey,valueinreference.items():
+ orbitals=[self.orbitals_by_irrep[key.upper()][i]foriinvalue]
+ ref_idx+=[x.idx_totalforxinorbitals]
+ ref_idx=sorted(ref_idx)
+ elifreferenceisNone:
+ assert(self.n_electrons%2==0)
+ ref_idx=standard_ref
+
+ # determine if the active space can be represented by psi4
+ # reference needs to be the scf reference
+ # all frozen orbitals need to be without gaps since we can not freeze individual orbitals
+ frozen_docc=None
+ ifref_idx==standard_ref:
+ frozen_docc=[0]*self.nirrep
+ frozen_docc_all=[self.orbitals[i]foriinstandard_refifself.orbitals[i].idx_totalnotinactive_idx]
+ fori,irrepinenumerate(self.irreps):
+ sorted_array=sorted([x.idx_irrepforxinfrozen_docc_allifx.irrep.upper()==irrep.upper()])
+ frozen_docc[i]=len(sorted_array)
+ iflen(sorted_array)>0and(sorted_array[0]!=0andsorted_array[-1]!=len(sorted_array)-1):
+ frozen_docc=None
+ print("active space not CAS type: frozen_docc of {} ".format(irrep),sorted_array)
+ break
+
+ # and the same for the unoccupied ones
+ frozen_uocc=None
+ iffrozen_doccisnotNone:
+ frozen_uocc=[0]*self.nirrep
+ frozen_uocc_all=[oforoinself.orbitalsifo.idx_totalnotinactive_idx+ref_idx]
+ fori,irrepinenumerate(self.irreps):
+ ifirrepnotinself.orbitals_by_irrep:
+ continue
+ sorted_array=sorted([x.idx_irrepforxinfrozen_uocc_allifx.irrep.upper()==irrep.upper()])
+ frozen_uocc[i]=len(sorted_array)
+ last=self.orbitals_by_irrep[irrep][-1]
+ iflen(sorted_array)>0and(
+ sorted_array[-1]!=last.idx_irreporsorted_array[-1]!=sorted_array[0]+len(
+ sorted_array)-1):
+ frozen_uocc=None
+ break
+
+ returnActiveSpaceDataPsi4(active_orbitals=sorted(active_idx),
+ reference_orbitals=sorted(ref_idx),
+ frozen_docc=frozen_docc,
+ frozen_uocc=frozen_uocc)
+
+ def__init__(self,parameters:ParametersQC,
+ transformation:typing.Union[str,typing.Callable]=None,
+ active_orbitals=None,
+ reference_orbitals=None,
+ frozen_orbitals=None,
+ *args,
+ **kwargs):
+"""
+
+ Parameters
+ ----------
+ parameters
+ transformation
+ active_orbitals: dictionary :
+ dictionary with irreps as keys and a list of integers as values
+ i.e. occ = {"A1":[0,1], "A2":[0]}
+ means the occupied active space is made up of spatial orbitals
+ 0A1, 1A1 and 0A2
+ as list: Give a list of spatial orbital indices
+ i.e. occ = [0,1,3] means that spatial orbital 0, 1 and 3 are used
+ reference: (Default value=None)
+ List of orbitals which form the reference
+ Can be given in the same format as active_orbitals
+ If given as None then the first N_electron/2 orbitals are taken
+ and the corresponding active orbitals are removed
+ args
+ kwargs
+ """
+ psi4.core.clean()
+ psi4.core.clean_options()
+ psi4.core.clean_variables()
+
+ psi4.core.clean()
+ psi4.core.clean_options()
+ psi4.core.clean_variables()
+
+ self.psi4_mol=psi4.geometry(parameters.get_geometry_string())
+ psi4.activate(self.psi4_mol)
+ self._point_group=self.psi4_mol.point_group().symbol()
+ if"point_group"inkwargs:
+ self._point_group=kwargs["point_group"]
+
+ self.energies={}# history to avoid recomputation
+ self.logs={}# store full psi4 output
+
+ # psi4 active space will be formed later
+ super().__init__(parameters=parameters,transformation=transformation,active_orbitals=None,
+ reference_orbitals=reference_orbitals,frozen_orbitals=[],
+ *args,**kwargs)
+
+ oenergies=[]
+ foriinself.irreps:
+ oenergies+=[(i,j,x)forj,xinenumerate(self.orbital_energies(irrep=i))]
+
+ oenergies=sorted(oenergies,key=lambdax:x[2])
+
+ forxinself.orbitals:
+ x.irrep=oenergies[x.idx_total][0]
+ x.idx_irrep=oenergies[x.idx_total][1]
+ x.energy=oenergies[x.idx_total][2]
+
+ orbitals_by_irrep={o.irrep:[]foroinself.orbitals}
+ foroinself.orbitals:
+ orbitals_by_irrep[o.irrep]+=[o]
+
+ self.orbitals_by_irrep=orbitals_by_irrep
+ ifreference_orbitalsisNone:
+ reference_orbitals=[x.idx_totalforxinself.reference_orbitals]
+
+ ifactive_orbitalsisNone:
+ active_orbitals=[iforiinrange(self.n_orbitals)]
+
+ iffrozen_orbitalsisNoneandself.parameters.frozen_core:
+ frozen_orbitals=[iforiinrange(self.parameters.get_number_of_core_electrons()//2)]
+
+ deflist_to_irrep_dict(orbital_list):
+ # assume we have been given a list of orbitals with their total indices instead of a dictionary with irreps
+ active_dict={}
+ iforbital_listisNone:
+ returnactive_dict
+ forxinorbital_list:
+ orbital=self.orbitals[x]
+ iforbital.irrepnotinactive_dict:
+ active_dict[orbital.irrep]=[orbital.idx_irrep]
+ else:
+ active_dict[orbital.irrep]+=[orbital.idx_irrep]
+ returnactive_dict
+
+ ifnothasattr(active_orbitals,"keys"):
+ active_orbitals=list_to_irrep_dict(active_orbitals)
+
+ ifnothasattr(reference_orbitals,"keys"):
+ reference_orbitals=list_to_irrep_dict(reference_orbitals)
+
+ ifnothasattr(frozen_orbitals,"keys"):
+ frozen_orbitals=list_to_irrep_dict(frozen_orbitals)
+
+ # remove frozen-orbitals from active orbitals
+ fork,vinfrozen_orbitals.items():
+ forxinv:
+ ifkinactive_orbitalsandxinactive_orbitals[k]:
+ active_orbitals[k].remove(x)
+
+ self.integral_manager.active_space=self._make_psi4_active_space_data(active_orbitals=active_orbitals,
+ reference=reference_orbitals)
+ # need to recompute
+ # (psi4 won't take over active space information otherwise)
+ self.compute_energy(method="hf",recompute=True,*args,**kwargs)
+ self.ref_wfn=self.logs["hf"].wfn
+
+ self.transformation=self._initialize_transformation(transformation=transformation,*args,**kwargs)
+
+ self.kwargs=kwargs
+
+ @property
+ defpoint_group(self):
+ returnself._point_group
+
+ @property
+ defnirrep(self):
+ returnself.ref_wfn.nirrep()
+
+
+[docs]
+ deforbital_energies(self,irrep:typing.Union[int,str]=None,beta:bool=False,wfn=None):
+"""
+ Returns orbital energies of a given irrep
+ or all orbital energies of all irreps (default)
+
+ Parameters
+ ----------
+ irrep: int or str :
+ int: specify the irrep by number (in cotton ordering)
+ str: specify the irrep by name (like 'A1')
+ specify from which irrep you want the orbital energies
+ psi4 orders irreps in 'Cotton ordering'
+ http://www.psicode.org/psi4manual/master/psithonmol.html#table-irrepordering
+ beta: bool : (Default value=False)
+ get the beta electrons
+
+ Returns
+ -------
+ list or orbital energies
+ """
+ ifwfnisNone:
+ wfn=self.ref_wfn
+
+ ifhasattr(irrep,"upper"):
+ irrep=self.irreps.index(irrep.upper())
+
+ ifbeta:
+ tmp=psi4.driver.p4util.numpy_helper._to_array(wfn.epsilon_b(),dense=False)
+ else:
+ tmp=psi4.driver.p4util.numpy_helper._to_array(wfn.epsilon_a(),dense=False)
+
+ ifirrepisNoneorself.point_group.lower()=="c1":
+ result=[]
+ forxintmp:
+ result+=[x]
+ returnresult
+ else:
+ returntmp[irrep]
+[docs]
+ defcompute_rdms(self,U:QCircuit=None,variables:Variables=None,spin_free:bool=True,
+ get_rdm1:bool=True,get_rdm2:bool=True,psi4_method:str=None,
+ psi4_options:dict={},*args,**kwargs):
+"""
+ Same functionality as qc_base.compute_rdms (look there for more information),
+ plus the additional option to compute 1- and 2-RDM using psi4 by the keyword psi4_rdms
+
+ Parameters
+ ----------
+ U :
+ Quantum Circuit to achieve the desired state \\psi = U |0\\rangle, optional if psi4_rdms is set to True
+ variables :
+ If U is parametrized, then need to hand over a set of fixed variables
+ spin_free :
+ Set whether matrices should be spin-free (summation over spin) or defined by spin-orbitals
+ get_rdm1, get_rdm2 :
+ Set whether either one or both rdm1, rdm2 should be computed. If both are needed at some point,
+ it is recommended to compute them at once.
+ Note that whatever is specified in psi4_options has priority.
+ psi4_method:
+ Method to be run, currently only methods returning a CIWavefuntion are supported
+ (e.g. "detci" + ex_level in options, or "fci", "cisdt", "casscf", but NOT "cisd")
+ psi4_options:
+ Options to be handed over to psi4, containing e.g. excitation level of "detci"-method.
+ If "detci__opdm" for 1-RDM and "detci__tpdm" for 2-RDM are not included, the keywords get_rdm1, get_rdm2 are
+ used (if both are specified, prioritizing psi4_options).
+
+ Returns
+ -------
+ """
+ ifnotpsi4_method:
+ returnsuper().compute_rdms(U=U,variables=variables,spin_free=spin_free,
+ get_rdm1=get_rdm1,get_rdm2=get_rdm2,*args,**kwargs)
+ else:
+ # Get 1- and 2-particle reduced density matrix via Psi4 CISD computation
+ # If "cisd" is chosen, change to "detci" (default is excitation level 2 anyhow) to obtain a CIWavefunction
+ ifpsi4_method.lower()=="cisd":
+ print("Changed psi4_method from 'cisd' to 'detci' with ex_level=2 s.th. psi4 returns a CIWavefunction.")
+ psi4_method="detci"
+ # Set options if not handed over
+ psi4_options={k.lower():vfork,vinpsi4_options.items()}# set to lower-case for string comparison
+ if"detci__opdm"notinpsi4_options.keys():
+ psi4_options.update({"detci__opdm":get_rdm1})
+ if"detci__tpdm"notinpsi4_options.keys():
+ psi4_options.update({"detci__tpdm":get_rdm2})
+ ifpsi4_method.lower()=="detci"and"detci__ex_level"notinpsi4_options.keys():
+ psi4_options.update({"detci__ex_level":2})# use CISD as default
+ print(psi4_options)
+
+ # Compute and set matrices
+ self.compute_energy(psi4_method,options=psi4_options)
+ wfn=self.logs[psi4_method].wfn
+ ifpsi4_options["detci__opdm"]:
+ rdm1=psi4.driver.p4util.numpy_helper._to_array(wfn.get_opdm(-1,-1,"SUM",False),dense=True)
+ self._rdm1=rdm1
+ ifpsi4_options["detci__tpdm"]:
+ rdm2=psi4.driver.p4util.numpy_helper._to_array(wfn.get_tpdm("SUM",False),dense=True)
+ rdm2=NBodyTensor(elems=rdm2,ordering='chem')
+ rdm2.reorder(to='phys')# RDMs in physics ordering (cp. to NBodyTensor in qc_base.py)
+ rdm2=2*rdm2.elems# Factor 2 since psi4 normalizes 2-rdm by 1/2
+ self._rdm2=rdm2
+
+
+
+[docs]
+ defperturbative_f12_correction(self,rdm1:numpy.ndarray=None,rdm2:numpy.ndarray=None,gamma:float=1.4,
+ n_ri:int=None,cabs_type:str="active",cabs_options:dict=None,
+ **kwargs)->float:
+"""
+ Computes the spin-free [2]_R12 correction, needing only the 1- and 2-RDM of a reference method
+ Requires either 1-RDM, 2-RDM or information to compute them in kwargs
+
+ Parameters
+ ----------
+ rdm1 :
+ 1-electron reduced density matrix
+ rdm2 :
+ 2-electron reduced density matrix
+ gamma :
+ f12-exponent, for a correlation factor f_12 = -1/gamma * exp[-gamma*r_12]
+ n_ri :
+ dimensionality of RI-basis; if None, then the maximum available via tensors / basis-set is used
+ cabs_type :
+ - either "active" for using a given basis set as is as approximative CBS (complete basis set), and specify
+ OBS (orbital basis) by an active space
+ - or "cabs+" for CABS+-approach as in
+ Valeev, E. F. (2004). Improving on the resolution of the identity in linear R12 ab initio theories.
+ Chemical Physics Letters, 395(4–6), 190–195. https://doi.org/10.1016/j.cplett.2004.07.061
+ -> pass cabs_name in cabs_options
+ cabs_options :
+ dict, which needs at least {"cabs_name": some CABS basis set} if cabs_type=="cabs+"
+ kwargs :
+ e.g. RDM-information via {"U": QCircuit, "variables": optimal angles} if computation via VQE,
+ or {"rdm__psi4_method": some CI method, "rdm__psi4_options": dict with psi4 options} if computation via
+ psi4, compare to psi4_interface.compute_rdms
+ one of the above needs to be passed if rdm1,rdm2 not yet computed
+
+ Returns
+ -------
+ the f12 correction for the energy
+ """
+ from.f12_corrections._f12_correction_psi4importExplicitCorrelationCorrectionPsi4
+ correction=ExplicitCorrelationCorrectionPsi4(mol=self,rdm1=rdm1,rdm2=rdm2,gamma=gamma,
+ n_ri=n_ri,cabs_type=cabs_type,cabs_options=cabs_options,
+ **kwargs)
+
+ returncorrection.compute()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/quantumchemistry/pyscf_interface.html b/docs/sphinx/_modules/tequila_code/quantumchemistry/pyscf_interface.html
new file mode 100644
index 0000000..9b5d6e2
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/quantumchemistry/pyscf_interface.html
@@ -0,0 +1,298 @@
+
+
+
+
+
+
+
+ tequila_code.quantumchemistry.pyscf_interface — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source code for tequila_code.quantumchemistry.qc_base
+importcopy
+fromdataclassesimportdataclass
+fromtequilaimportTequilaException,BitString,TequilaWarning
+fromtequila.hamiltonianimportQubitHamiltonian
+
+fromtequila.hamiltonian.paulisimportSp,Sm,Zero
+
+fromtequila.circuitimportQCircuit,gates
+fromtequila.objective.objectiveimportVariable,Variables,ExpectationValue
+
+fromtequila.simulators.simulator_apiimportsimulate
+fromtequila.utilsimportto_float
+from.chemistry_toolsimportActiveSpaceData,FermionicGateImpl,prepare_product_state,ClosedShellAmplitudes, \
+ Amplitudes,ParametersQC,NBodyTensor,IntegralManager
+
+from.encodingsimportknown_encodings
+
+importtyping,numpy,numbers
+fromitertoolsimportproduct
+importtequila.grouping.fermionic_functionsasff
+
+
+try:
+ # if you are experiencing import errors you need to update openfermion
+ # required is version >= 1.0
+ # otherwise replace with from openfermion.hamiltonians import MolecularData
+ importopenfermion
+ fromopenfermion.chemimportMolecularData
+except:
+ try:
+ fromopenfermion.hamiltoniansimportMolecularData
+ exceptExceptionasE:
+ raiseException("{}\nIssue with Tequila Chemistry: Please update openfermion".format(str(E)))
+importwarnings
+OPTIMIZED_ORDERING="Optimized"
+
+[docs]
+classQuantumChemistryBase:
+"""
+ Base Class for tequila chemistry functionality
+ This is what is initialized with tq.Molecule(...)
+ We try to define all main methods here and only implemented specializations in the derived classes
+ Derived classes interface specific backends (e.g. Psi4, PySCF and Madness). See PACKAGE_interface.py for more
+ """
+
+ def__init__(self,parameters:ParametersQC,
+ transformation:typing.Union[str,typing.Callable]=None,
+ active_orbitals:list=None,
+ frozen_orbitals:list=None,
+ orbital_type:str=None,
+ reference_orbitals:list=None,
+ orbitals:list=None,
+ *args,
+ **kwargs):
+"""
+ Parameters
+ ----------
+ parameters: the quantum chemistry parameters handed over as instance of the ParametersQC class (see there for content)
+ transformation: the fermion to qubit transformation (default is JordanWigner). See encodings.py for supported encodings or to extend
+ active_orbitals: list of active orbitals (others will be frozen, if we have N-electrons then the first N//2 orbitals will be considered occpied when creating the active space)
+ frozen_orbitals: convenience (will be removed from list of active orbitals)
+ reference_orbitals: give list of orbitals that shall be considered occupied when creating a possible active space (default is the first N//2). The indices are expected to be total indices (including possible frozen orbitals in the counting)
+ orbitals: information about the orbitals (should be in OrbitalData format, can be a dictionary)
+ args
+ kwargs
+ """
+
+ self.parameters=parameters
+ n_electrons=parameters.n_electrons
+ if"n_electrons"inkwargs:
+ n_electrons=kwargs["n_electrons"]
+
+ ifreference_orbitalsisNone:
+ reference_orbitals=[iforiinrange(n_electrons//2)]
+ self._reference_orbitals=reference_orbitals
+
+ iforbital_typeisNone:
+ orbital_type="unknown"
+
+ # no frozen core with native orbitals (i.e. atomics)
+ overriding_freeze_instruction=orbital_typeisnotNoneandorbital_type.lower()=="native"
+ # determine frozen core automatically if set
+ # only if molecule is computed from scratch and not passed down from above
+ overriding_freeze_instruction=overriding_freeze_instructionorn_electrons!=parameters.n_electrons
+ overriding_freeze_instruction=overriding_freeze_instructionorfrozen_orbitalsisnotNone
+ ifnotoverriding_freeze_instructionandself.parameters.frozen_core:
+ n_core_electrons=self.parameters.get_number_of_core_electrons()
+ iffrozen_orbitalsisNone:
+ frozen_orbitals=[iforiinrange(n_core_electrons//2)]
+
+
+ # initialize integral manager
+ if"integral_manager"inkwargs:
+ self.integral_manager=kwargs["integral_manager"]
+ else:
+ self.integral_manager=self.initialize_integral_manager(active_orbitals=active_orbitals,
+ reference_orbitals=reference_orbitals,
+ orbitals=orbitals,frozen_orbitals=frozen_orbitals,orbital_type=orbital_type,basis_name=self.parameters.basis_set,*args,
+ **kwargs)
+
+ iforbital_typeisnotNoneandorbital_type.lower()=="native":
+ self.integral_manager.transform_to_native_orbitals()
+
+
+ self.transformation=self._initialize_transformation(transformation=transformation,*args,**kwargs)
+
+ self._rdm1=None
+ self._rdm2=None
+
+
+
+[docs]
+ defmake_excitation_generator(self,
+ indices:typing.Iterable[typing.Tuple[int,int]],
+ form:str=None,
+ remove_constant_term:bool=True)->QubitHamiltonian:
+"""
+ Notes
+ ----------
+ Creates the transformed hermitian generator of UCC type unitaries:
+ M(a^\dagger_{a_0} a_{i_0} a^\dagger{a_1}a_{i_1} ... - h.c.)
+ where the qubit map M depends is self.transformation
+
+ Parameters
+ ----------
+ indices : typing.Iterable[typing.Tuple[int, int]] :
+ List of tuples [(a_0, i_0), (a_1, i_1), ... ] - recommended format, in spin-orbital notation (alpha odd numbers, beta even numbers)
+ can also be given as one big list: [a_0, i_0, a_1, i_1 ...]
+ form : str : (Default value None):
+ Manipulate the generator to involution or projector
+ set form='involution' or 'projector'
+ the default is no manipulation which gives the standard fermionic excitation operator back
+ remove_constant_term: bool: (Default value True):
+ by default the constant term in the qubit operator is removed since it has no effect on the unitary it generates
+ if the unitary is controlled this might not be true!
+ Returns
+ -------
+ type
+ 1j*Transformed qubit excitation operator, depends on self.transformation
+ """
+
+ ifnotself.supports_ucc():
+ raiseTequilaException("Molecule with transformation {} does not support general UCC operations".format(self.transformation))
+
+ # check indices and convert to list of tuples if necessary
+ iflen(indices)==0:
+ raiseTequilaException("make_excitation_operator: no indices given")
+ elifnotisinstance(indices[0],typing.Iterable):
+ iflen(indices)%2!=0:
+ raiseTequilaException("make_excitation_generator: unexpected input format of indices\n"
+ "use list of tuples as [(a_0, i_0),(a_1, i_1) ...]\n"
+ "or list as [a_0, i_0, a_1, i_1, ... ]\n"
+ "you gave: {}".format(indices))
+ converted=[(indices[2*i],indices[2*i+1])foriinrange(len(indices)//2)]
+ else:
+ converted=indices
+
+ # convert everything to native python int
+ # otherwise openfermion will complain
+ converted=[(int(pair[0]),int(pair[1]))forpairinconverted]
+
+ # convert to openfermion input format
+ ofi=[]
+ dag=[]
+ forpairinconverted:
+ assert(len(pair)==2)
+ ofi+=[(int(pair[0]),1),
+ (int(pair[1]),0)]# openfermion does not take other types of integers like numpy.int64
+ dag+=[(int(pair[0]),0),(int(pair[1]),1)]
+
+ op=openfermion.FermionOperator(tuple(ofi),1.j)# 1j makes it hermitian
+ op+=openfermion.FermionOperator(tuple(reversed(dag)),-1.j)
+
+ ifisinstance(form,str)andform.lower()!='fermionic':
+ # indices for all the Na operators
+ Na=[xforpairinconvertedforxin[(pair[0],1),(pair[0],0)]]
+ # indices for all the Ma operators (Ma = 1 - Na)
+ Ma=[xforpairinconvertedforxin[(pair[0],0),(pair[0],1)]]
+ # indices for all the Ni operators
+ Ni=[xforpairinconvertedforxin[(pair[1],1),(pair[1],0)]]
+ # indices for all the Mi operators
+ Mi=[xforpairinconvertedforxin[(pair[1],0),(pair[1],1)]]
+
+ # can gaussianize as projector or as involution (last is default)
+ ifform.lower()=="p+":
+ op*=0.5
+ op+=openfermion.FermionOperator(Na+Mi,0.5)
+ op+=openfermion.FermionOperator(Ni+Ma,0.5)
+ elifform.lower()=="p-":
+ op*=0.5
+ op+=openfermion.FermionOperator(Na+Mi,-0.5)
+ op+=openfermion.FermionOperator(Ni+Ma,-0.5)
+
+ elifform.lower()=="g+":
+ op+=openfermion.FermionOperator([],1.0)# Just for clarity will be subtracted anyway
+ op+=openfermion.FermionOperator(Na+Mi,-1.0)
+ op+=openfermion.FermionOperator(Ni+Ma,-1.0)
+ elifform.lower()=="g-":
+ op+=openfermion.FermionOperator([],-1.0)# Just for clarity will be subtracted anyway
+ op+=openfermion.FermionOperator(Na+Mi,1.0)
+ op+=openfermion.FermionOperator(Ni+Ma,1.0)
+ elifform.lower()=="p0":
+ # P0: we only construct P0 and don't keep the original generator
+ op=openfermion.FermionOperator([],1.0)# Just for clarity will be subtracted anyway
+ op+=openfermion.FermionOperator(Na+Mi,-1.0)
+ op+=openfermion.FermionOperator(Ni+Ma,-1.0)
+ else:
+ raiseTequilaException(
+ "Unknown generator form {}, supported are G, P+, P-, G+, G- and P0".format(form))
+
+ qop=self.transformation(op)
+
+ # remove constant terms
+ # they have no effect in the unitary (if not controlled)
+ ifremove_constant_term:
+ qop.qubit_operator.terms[tuple()]=0.0
+
+ # check if the operator is hermitian and cast coefficients to floats
+ # in order to avoid trouble with the simulation backends
+ assertqop.is_hermitian()
+ fork,vinqop.qubit_operator.terms.items():
+ qop.qubit_operator.terms[k]=to_float(v)
+
+ qop=qop.simplify()
+
+ iflen(qop)==0:
+ warnings.warn("Excitation generator is a unit operator.\n"
+ "Non-standard transformations might not work with general fermionic operators\n"
+ "indices = "+str(indices),category=TequilaWarning)
+ returnqop
+
+
+
+[docs]
+ defmake_hardcore_boson_excitation_gate(self,indices,angle,control=None,assume_real=True,
+ compile_options="optimize"):
+"""
+ Make excitation generator in the hardcore-boson approximation (all electrons are forced to spin-pairs)
+ use only in combination with make_hardcore_boson_hamiltonian()
+
+ Parameters
+ ----------
+ indices
+ angle
+ control
+ assume_real
+ compile_options
+
+ Returns
+ -------
+
+ """
+ target=[]
+ forpairinindices:
+ assertlen(pair)==2
+ target+=[self.transformation.up(pair[0]),self.transformation.up(pair[1])]
+ ifself.transformation.up_then_down:
+ consistency=[x<self.n_orbitalsforxintarget]
+ else:
+ consistency=[x%2==0forxintarget]
+ ifnotall(consistency):
+ raiseTequilaException(
+ "make_hardcore_boson_excitation_gate: Inconsistencies in indices={} for encoding: {}".format(
+ indices,self.transformation))
+ returngates.QubitExcitation(angle=angle,target=target,assume_real=assume_real,control=control,
+ compile_options=compile_options)
+
+
+
+[docs]
+ defUR(self,i,j,angle=None,label=None,control=None,assume_real=True,*args,**kwargs):
+"""
+ Convenience function for orbital rotation circuit (rotating spatial orbital i and j) with standard naming of variables
+ See arXiv:2207.12421 Eq.6 for UR(0,1)
+ Parameters:
+ ----------
+ indices:
+ tuple of two spatial(!) orbital indices
+ angle:
+ Numeric or hashable type or tequila objective. Default is None and results
+ in automatic naming as ("R",i,j)
+ label:
+ can be passed instead of angle to have auto-naming with label ("R",i,j,label)
+ useful for repreating gates with individual variables
+ control:
+ List of possible control qubits
+ assume_real:
+ Assume that the wavefunction will always stay real.
+ Will reduce potential gradient costs by a factor of 2
+ """
+ i,j=self.format_excitation_indices([(i,j)])[0]
+ ifangleisNone:
+ iflabelisNone:
+ angle=Variable(name=("R",i,j))*numpy.pi
+ else:
+ angle=Variable(name=("R",i,j,label))*numpy.pi
+
+ circuit=self.make_excitation_gate(indices=[(2*i,2*j)],angle=angle,assume_real=assume_real,control=control,*args,**kwargs)
+ circuit+=self.make_excitation_gate(indices=[(2*i+1,2*j+1)],angle=angle,assume_real=assume_real,control=control,*args,**kwargs)
+ returncircuit
+
+
+
+[docs]
+ defUC(self,i,j,angle=None,label=None,control=None,assume_real=True,*args,**kwargs):
+"""
+ Convenience function for orbital correlator circuit (correlating spatial orbital i and j through a spin-paired double excitation) with standard naming of variables
+ See arXiv:2207.12421 Eq.22 for UC(1,2)
+
+ Parameters:
+ ----------
+ indices:
+ tuple of two spatial(!) orbital indices
+ angle:
+ Numeric or hashable type or tequila objective. Default is None and results
+ in automatic naming as ("R",i,j)
+ label:
+ can be passed instead of angle to have auto-naming with label ("R",i,j,label)
+ useful for repreating gates with individual variables
+ control:
+ List of possible control qubits
+ assume_real:
+ Assume that the wavefunction will always stay real.
+ Will reduce potential gradient costs by a factor of 2
+ """
+ i,j=self.format_excitation_indices([(i,j)])[0]
+ ifangleisNone:
+ iflabelisNone:
+ angle=Variable(name=("C",i,j))*numpy.pi
+ else:
+ angle=Variable(name=("C",i,j,label))*numpy.pi
+ if"jordanwigner"inself.transformation.name.lower()andnotself.transformation.up_then_down:
+ # for JW we can use the optimized form shown in arXiv:2207.12421 Eq.22
+ returngates.QubitExcitation(target=[2*i,2*j,2*i+1,2*j+1],angle=angle,control=control,assume_real=assume_real,*args,**kwargs)
+ else:
+ returnself.make_excitation_gate(indices=[(2*i,2*j),(2*i+1,2*j+1)],angle=angle,control=control,assume_real=assume_real,*args,**kwargs)
+[docs]
+ defmake_excitation_gate(self,indices,angle,control=None,assume_real=True,**kwargs):
+"""
+ Initialize a fermionic excitation gate defined as
+
+ .. math::
+ e^{-i\\frac{a}{2} G}
+ with generator defines by the indices [(p0,q0),(p1,q1),...]
+ .. math::
+ G = i(\\prod_{k} a_{p_k}^\\dagger a_{q_k} - h.c.)
+
+ Parameters
+ ----------
+ indices:
+ List of tuples that define the generator
+ angle:
+ Numeric or hashable type or tequila objective
+ control:
+ List of possible control qubits
+ assume_real:
+ Assume that the wavefunction will always stay real.
+ Will reduce potential gradient costs by a factor of 2
+ """
+
+ ifnotself.supports_ucc():
+ raiseTequilaException("Molecule with transformation {} does not support general UCC operations".format(self.transformation))
+
+ generator=self.make_excitation_generator(indices=indices,remove_constant_term=controlisNone)
+ p0=self.make_excitation_generator(indices=indices,form="P0",remove_constant_term=controlisNone)
+ ifself.transformation.up_then_down:
+ idx=[]
+ forpairinindices:
+ idx.append((pair[0]//2+(pair[0]%2)*self.n_orbitals,pair[1]//2+(pair[1]%2)*self.n_orbitals))
+ else:idx=indices
+ returnQCircuit.wrap_gate(
+ FermionicGateImpl(angle=angle,generator=generator,p0=p0,
+ transformation=type(self.transformation).__name__.lower(),indices=idx,
+ assume_real=assume_real,
+ control=control,**kwargs))
+
+
+
+[docs]
+ defmake_molecule(self,*args,**kwargs)->MolecularData:
+"""Creates a molecule in openfermion format by running psi4 and extracting the data
+ Will check for previous outputfiles before running
+ Will not recompute if a file was found
+
+ Parameters
+ ----------
+ parameters :
+ An instance of ParametersQC, which also holds an instance of ParametersPsi4 via parameters.psi4
+ The molecule will be saved in parameters.filename, if this file exists before the call the molecule will be imported from the file
+
+ Returns
+ -------
+ type
+ the molecule in openfermion.MolecularData format
+
+ """
+ molecule=MolecularData(**self.parameters.molecular_data_param)
+
+ do_compute=True
+ # try:
+ # import os
+ # if os.path.exists(self.parameters.filename):
+ # molecule.load()
+ # do_compute = False
+ # except OSError:
+ # do_compute = True
+
+ ifdo_compute:
+ molecule=self.do_make_molecule(*args,**kwargs)
+
+ #molecule.save()
+ returnmolecule
+
+
+
+[docs]
+ definitialize_integral_manager(self,*args,**kwargs):
+"""
+ Called by self.__init__() with args and kwargs passed through
+ Override this in derived class such that it returns an intitialized instance of the integral manager
+
+ In the BaseClass it is required to pass the following with kwargs on init:
+ - one_body_integrals as matrix
+ - two_body_integrals as NBTensor of numpy.ndarray (four indices, openfermion ordering)
+ - nuclear_repulsion (constant part of hamiltonian - optional)
+
+ Method sets:
+ - result of self.get_integrals()
+ """
+
+ n_electrons=self.parameters.n_electrons
+ if"n_electrons"inkwargs:
+ n_electrons=kwargs["n_electrons"]
+
+ assert("one_body_integrals"inkwargs)
+ assert("two_body_integrals"inkwargs)
+ one_body_integrals=kwargs["one_body_integrals"]
+ kwargs.pop("one_body_integrals")
+ two_body_integrals=kwargs["two_body_integrals"]
+ kwargs.pop("two_body_integrals")
+
+ ifnotisinstance(two_body_integrals,NBodyTensor):
+ # assuming two_body_integrals are given in openfermion ordering
+ ordering=None# will be auto-detected
+ if"ordering"inkwargs:
+ ordering=kwargs["ordering"]
+ kwargs.pop("ordering")# let's not confuse the IntegralManager
+ two_body_integrals=NBodyTensor(two_body_integrals,ordering=ordering)
+
+ two_body_integrals=two_body_integrals.reorder(to="chem")
+
+ constant_part=0.0
+ if"constant_term"inkwargs:
+ constant_part+=kwargs["constant_term"]
+ kwargs.pop("constant_term")
+ if"nuclear_repulsion"inkwargs:
+ constant_part+=kwargs["nuclear_repulsion"]
+ kwargs.pop("nuclear_repulsion")
+
+ if"active_space"notinkwargs:
+
+ active_orbitals=[iforiinrange(one_body_integrals.shape[0])]
+ if"active_orbitals"inkwargsandkwargs["active_orbitals"]isnotNone:
+ active_orbitals=kwargs["active_orbitals"]
+ if"frozen_orbitals"inkwargsandkwargs["frozen_orbitals"]isnotNone:
+ forfoinkwargs["frozen_orbitals"]:
+ iffoinactive_orbitals:
+ active_orbitals.remove(fo)
+
+ reference_orbitals=[iforiinrange(n_electrons//2)]
+ if"reference_orbitals"inkwargsandkwargs["reference_orbitals"]isnotNone:
+ reference_orbitals=kwargs["reference_orbitals"]
+
+ active_space=ActiveSpaceData(active_orbitals=sorted(active_orbitals),
+ reference_orbitals=sorted(reference_orbitals))
+ kwargs["active_space"]=active_space
+
+ if"basis_name"notinkwargs:
+ kwargs["basis_name"]=self.parameters.basis_set
+
+ manager=IntegralManager(one_body_integrals=one_body_integrals,two_body_integrals=two_body_integrals,
+ constant_term=constant_part,*args,**kwargs)
+
+ returnmanager
+
+
+
+[docs]
+ deftransform_orbitals(self,orbital_coefficients,ignore_active_space=False,name=None,*args,**kwargs):
+"""
+ Parameters
+ ----------
+ orbital_coefficients: second index is new orbital indes, first is old orbital index (summed over), indices are assumed to be defined on the active space
+ ignore_active_space: if true orbital_coefficients are not assumed to be given in the active space
+ name: str, name the new orbitals
+ args
+ kwargs
+
+ Returns
+ -------
+ New molecule with transformed orbitals
+ """
+
+ U=numpy.eye(self.integral_manager.orbital_coefficients.shape[0])
+ # mo_coeff by default only acts on the active space
+ active_indices=[o.idx_totalforoinself.integral_manager.active_orbitals]
+
+ ifignore_active_space:
+ U=orbital_coefficients
+ else:
+ forkk,kinenumerate(active_indices):
+ forll,linenumerate(active_indices):
+ U[k][l]=orbital_coefficients[kk][ll]
+
+ # can not be an instance of a specific backend (otherwise we get inconsistencies with classical methods in the backend)
+ integral_manager=copy.deepcopy(self.integral_manager)
+ integral_manager.transform_orbitals(U=U,name=name)
+ result=QuantumChemistryBase(parameters=self.parameters,integral_manager=integral_manager,transformation=self.transformation)
+ returnresult
+[docs]
+ defuse_native_orbitals(self,inplace=False):
+"""
+ Returns
+ -------
+ New molecule in the native (orthonormalized) basis given
+ e.g. for standard basis sets the orbitals are orthonormalized Gaussian Basis Functions
+ """
+ ifnotself.integral_manager.active_space_is_trivial():
+ warnings.warn("orthonormalize_basis_orbitals: active space is set and might lead to inconsistent behaviour",TequilaWarning)
+
+ # can not be an instance of a specific backend (otherwise we get inconsistencies with classical methods in the backend)
+ ifinplace:
+ self.integral_manager.transform_to_native_orbitals()
+ returnself
+ else:
+ integral_manager=copy.deepcopy(self.integral_manager)
+ integral_manager.transform_to_native_orbitals()
+ result=QuantumChemistryBase(parameters=self.parameters,integral_manager=integral_manager,transformation=self.transformation)
+ returnresult
+
+
+
+
+[docs]
+ defdo_make_molecule(self,*args,**kwargs):
+"""
+ Called by self.make_molecule with args and kwargs passed through
+ Override this in derived class if needed
+ """
+
+ asserthasattr(self,"integral_manager")andself.integral_managerisnotNone
+ constant_term,one_body_integrals,two_body_integrals=self.integral_manager.get_integrals(ordering="of")
+ two_body_integrals=two_body_integrals.reorder(to="of")
+
+ if("n_orbitals"inkwargs):
+ n_orbitals=kwargs["n_orbitals"]
+ else:
+ n_orbitals=one_body_integrals.shape[0]
+ foriin[0,1,2,3]:
+ assertn_orbitals==two_body_integrals.shape[i]
+
+ molecule=MolecularData(**self.parameters.molecular_data_param)
+
+ molecule.one_body_integrals=one_body_integrals
+ molecule.two_body_integrals=two_body_integrals.elems
+ molecule.nuclear_repulsion=constant_term
+ molecule.n_orbitals=n_orbitals
+ if"n_electrons"inkwargs:
+ molecule.n_electrons=kwargs["n_electrons"]
+ molecule.save()
+ returnmolecule
+
+
+ @property
+ deforbitals(self):
+ returnself.integral_manager.active_orbitals
+
+ @property
+ defreference_orbitals(self):
+ returnself.integral_manager.active_reference_orbitals
+
+ @property
+ defn_orbitals(self)->int:
+"""
+ Returns
+ -------
+ The number of active orbitals in this Molecule
+ """
+ returnlen(self.integral_manager.active_orbitals)
+
+ @property
+ defactive_space(self):
+ returnself.integral_manager.active_space
+
+ @property
+ defn_electrons(self)->int:
+"""
+ Returns
+ -------
+ The number of active electrons in this molecule
+ """
+ return2*len(self.integral_manager.active_reference_orbitals)
+
+
+[docs]
+ defmake_annihilation_op(self,orbital,coefficient=1.0):
+"""
+ Compute annihilation operator on spin-orbital in qubit representation
+ Spin-orbital order is always (up,down,up,down,...)
+ """
+ assertorbital<=self.n_orbitals*2
+ aop=openfermion.ops.FermionOperator(f'{orbital}',coefficient)
+ returnself.transformation(aop)
+
+
+
+[docs]
+ defmake_creation_op(self,orbital,coefficient=1.0):
+"""
+ Compute creation operator on spin-orbital in qubit representation
+ Spin-orbital order is always (up,down,up,down,...)
+ """
+ assertorbital<=self.n_orbitals*2
+ cop=openfermion.ops.FermionOperator(f'{orbital}^',coefficient)
+ returnself.transformation(cop)
+
+
+
+[docs]
+ defmake_number_op(self,orbital):
+"""
+ Compute number operator on spin-orbital in qubit representation
+ Spin-orbital order is always (up,down,up,down,...)
+ """
+ num_op=self.make_creation_op(orbital)*self.make_annihilation_op(orbital)
+ returnnum_op
+
+
+
+[docs]
+ defmake_sz_op(self):
+"""
+ Compute the spin_z operator of the molecule in qubit representation
+ """
+ sz=QubitHamiltonian()
+ foriinrange(0,self.n_orbitals*2,2):
+ one=0.5*self.make_creation_op(i)*self.make_annihilation_op(i)
+ two=0.5*self.make_creation_op(i+1)*self.make_annihilation_op(i+1)
+ sz+=(one-two)
+ returnsz
+
+
+
+[docs]
+ defmake_sp_op(self):
+"""
+ Compute the spin+ operator of the molecule in qubit representation
+ """
+ sp=QubitHamiltonian()
+ foriinrange(self.n_orbitals):
+ sp+=self.make_creation_op(i*2)*self.make_annihilation_op(i*2+1)
+ returnsp
+
+
+
+[docs]
+ defmake_sm_op(self):
+"""
+ Compute the spin- operator of the molecule in qubit representation
+ """
+ sm=QubitHamiltonian()
+ foriinrange(self.n_orbitals):
+ sm+=self.make_creation_op(i*2+1)*self.make_annihilation_op(i*2)
+ returnsm
+
+
+
+[docs]
+ defmake_s2_op(self):
+"""
+ Compute the spin^2 operator of the molecule in qubit representation
+ """
+ s2_op=self.make_sm_op()*self.make_sp_op()+self.make_sz_op()*(self.make_sz_op()+1)
+ returns2_op
+
+
+
+[docs]
+ defmake_hamiltonian(self,*args,**kwargs)->QubitHamiltonian:
+"""
+ Parameters
+ ----------
+ occupied_indices: will be auto-assigned according to specified active space. Can be overridden by passing specific lists (same as in open fermion)
+ active_indices: will be auto-assigned according to specified active space. Can be overridden by passing specific lists (same as in open fermion)
+
+ Returns
+ -------
+ Qubit Hamiltonian in the Fermion-to-Qubit transformation defined in self.parameters
+ """
+
+ # warnings for backward comp
+ if"active_indices"inkwargs:
+ warnings.warn(
+ "active space can't be changed in molecule. Will ignore active_orbitals passed to make_hamiltonian")
+
+ of_molecule=self.make_molecule()
+ fop=of_molecule.get_molecular_hamiltonian()
+ fop=openfermion.transforms.get_fermion_operator(fop)
+ try:
+ qop=self.transformation(fop)
+ exceptTypeError:
+ qop=self.transformation(openfermion.transforms.get_interaction_operator(fop))
+ qop.is_hermitian()
+ returnqop
+
+
+
+[docs]
+ defmake_hardcore_boson_hamiltonian(self,condensed=False):
+"""
+ Returns
+ -------
+ Hamiltonian in Hardcore-Boson approximation (electrons are forced into spin-pairs)
+ Indepdent of Fermion-to-Qubit mapping
+ condensed: always give Hamiltonian back from qubit 0 to N where N is the number of orbitals
+ if condensed=False then JordanWigner would give back the Hamiltonian defined on even qubits between 0 to 2N
+ """
+
+ # integrate with QubitEncoding at some point
+ n_orbitals=self.n_orbitals
+ c,obt,tbt=self.get_integrals()
+ h=numpy.zeros(shape=[n_orbitals]*2)
+ g=numpy.zeros(shape=[n_orbitals]*2)
+ forpinrange(n_orbitals):
+ h[p,p]+=2*obt[p,p]
+ forqinrange(n_orbitals):
+ h[p,q]+=+tbt.elems[p,p,q,q]
+ ifp!=q:
+ g[p,q]+=2*tbt.elems[p,q,q,p]-tbt.elems[p,q,p,q]
+
+ H=c
+ forpinrange(n_orbitals):
+ forqinrange(n_orbitals):
+ up=p
+ uq=q
+ H+=h[p,q]*Sm(up)*Sp(uq)+g[p,q]*Sm(up)*Sp(up)*Sm(uq)*Sp(uq)
+
+ ifnotself.transformation.up_then_downandnotcondensed:
+ alpha_map={k.idx:self.transformation.up(k.idx)forkinself.orbitals}
+ H=H.map_qubits(alpha_map)
+ returnH
+
+
+
+[docs]
+ defmake_molecular_hamiltonian(self,occupied_indices=None,active_indices=None):
+"""
+ Returns
+ -------
+ Create a MolecularHamiltonian as openfermion Class
+ (used internally here, not used in tequila)
+ """
+ returnself.make_molecule().get_molecular_hamiltonian(occupied_indices=occupied_indices,active_indices=active_indices)
+
+
+ def_reference_state(self):
+"""
+ used internally
+ gives back reference state occupation vector (in second quantization or JW notation)
+ transformation to current encoding is done in def prepare_reference
+ """
+ assertself.n_electrons%2==0
+ state=[0]*(self.n_orbitals*2)
+ foriinself.reference_orbitals:
+ state[2*i.idx]=1
+ state[2*i.idx+1]=1
+ returnstate
+
+
+[docs]
+ defprepare_reference(self,state=None,*args,**kwargs):
+"""
+ Returns
+ -------
+ A tequila circuit object which prepares the reference of this molecule in the chosen transformation
+ """
+ ifstateisNone:
+ state=self._reference_state()
+
+ reference_state=BitString.from_array(self.transformation.map_state(state=state))
+ U=prepare_product_state(reference_state)
+ # prevent trace out in direct wfn simulation
+ U.n_qubits=self.n_orbitals*2# adapt when tapered transformations work
+ returnU
+
+
+
+[docs]
+ defprepare_hardcore_boson_reference(self):
+"""
+ Prepare reference state in the Hardcore-Boson approximation (eqch qubit represents two spin-paired electrons)
+ Returns
+ -------
+ tq.QCircuit that prepares the HCB reference
+ """
+ U=gates.X(target=[self.transformation.up(i.idx)foriinself.reference_orbitals])
+ U.n_qubits=self.n_orbitals
+ returnU
+
+
+
+[docs]
+ defhcb_to_me(self,U=None,condensed=False):
+"""
+ Transform a circuit in the hardcore-boson encoding (HCB)
+ to the encoding of this molecule
+ HCB is supposed to be encoded on the first n_orbitals qubits
+ Parameters
+ ----------
+ U: HCB circuit (using the alpha qubits)
+ condensed: assume that incoming U is condensed (HCB on the first n_orbitals; and not, as for example in JW on the first n even orbitals)
+ Returns
+ -------
+
+ """
+ ifUisNone:
+ U=QCircuit()
+ else:
+ ups=[self.transformation.up(i.idx)foriinself.orbitals]
+ consistency=[xinupsforxinU.qubits]
+ ifnotall(consistency):
+ warnings.warn(
+ "hcb_to_me: given circuit is not defined on all first {} qubits. Is this a HCB circuit?".format(
+ self.n_orbitals))
+
+ # map to alpha qubits
+ ifcondensed:
+ alpha_map={k:self.transformation.up(k)forkinrange(self.n_orbitals)}
+ alpha_U=U.map_qubits(qubit_map=alpha_map)
+ else:
+ alpha_U=U
+
+ UX=self.transformation.hcb_to_me()
+ ifUXisNone:
+ raiseTequilaException(
+ "transformation={} has no hcb_to_me function implemented".format(self.transformation))
+ returnalpha_U+UX
+
+
+
+[docs]
+ defget_pair_specific_indices(self,
+ pair_info:str=None,
+ include_singles:bool=True,
+ general_excitations:bool=True)->list:
+"""
+ Assuming a pair-specific model, create a pair-specific index list
+ to be used in make_upccgsd_ansatz(indices = ... )
+ Excite from a set of references (i) to any pair coming from (i),
+ i.e. any (i,j)/(j,i). If general excitations are allowed, also
+ allow excitations from pairs to appendant pairs and reference.
+
+ Parameters
+ ----------
+ pair_info
+ file or list including information about pair structure
+ references single number, pair double
+ example: as file: "0,1,11,11,00,10" (hand over file name)
+ in file, skip first row assuming some text with information
+ as list:['0','1`','11','11','00','10']
+ ~> two reference orbitals 0 and 1,
+ then two orbitals from pair 11, one from 00, one mixed 10
+ include_singles
+ include single excitations
+ general_excitations
+ allow general excitations
+ Returns
+ -------
+ list of indices with pair-specific ansatz
+ """
+
+ ifpair_infoisNone:
+ raiseTequilaException("Need to provide some pair information.")
+ # If pair-information given on file, load (layout see above)
+ ifisinstance(pair_info,str):
+ pairs=numpy.loadtxt(pair_info,dtype=str,delimiter=",",skiprows=1)
+ elifisinstance(pair_info,list):
+ pairs=pair_info
+ elifnotisinstance(pair_info,list):
+ raiseTequilaException("Pair information needs to be contained in a list or filename.")
+
+ connect=[[]]*len(pairs)
+ # determine "connectivity"
+ generalized=0
+ foridx,pinenumerate(pairs):
+ iflen(p)==1:
+ connect[idx]=[iforiinrange(len(pairs))
+ if((len(pairs[i])==2)and(str(idx)inpairs[i]))]
+ elif(len(p)==2)andgeneral_excitations:
+ connect[idx]=[iforiinrange(len(pairs))
+ if(((p[0]inpairs[i])or(p[1]inpairs[i])orstr(i)inp)
+ andnot(i==idx))]
+ eliflen(p)>2:
+ raiseTequilaException("Invalid reference of pair id.")
+
+ # create generating indices from connectivity
+ indices=[]
+ fori,toinenumerate(connect):
+ forainto:
+ indices.append(((2*i,2*a),(2*i+1,2*a+1)))
+ ifinclude_singles:
+ indices.append(((2*i,2*a)))
+ indices.append(((2*i+1,2*a+1)))
+
+ returnindices
+
+
+
+[docs]
+ defformat_excitation_indices(self,idx):
+"""
+ Consistent formatting of excitation indices
+ idx = [(p0,q0),(p1,q1),...,(pn,qn)]
+ sorted as: p0<p1<pn and pi<qi
+ :param idx: list of index tuples describing a single(!) fermionic excitation
+ :return: tuple-list of index tuples
+ """
+
+ idx=[tuple(sorted(x))forxinidx]
+ idx=sorted(idx,key=lambdax:x[0])
+ returntuple(idx)
+[docs]
+ defmake_spa_ansatz(self,edges,hcb=False,use_units_of_pi=False,label=None,optimize=None,ladder=True):
+"""
+ Separable Pair Ansatz (SPA) for general molecules
+ see arxiv:
+ edges: a list of tuples that contain the orbital indices for the specific pairs
+ one example: edges=[(0,), (1,2,3), (4,5)] are three pairs, one with a single orbital [0], one with three orbitals [1,2,3] and one with two orbitals [4,5]
+ hcb: spa ansatz in the hcb (hardcore-boson) space without transforming to current transformation (e.g. JordanWigner), use this for example in combination with the self.make_hardcore_boson_hamiltonian() and see the article above for more info
+ use_units_of_pi: circuit angles in units of pi
+ label: label the variables in the circuit
+ optimize: optimize the circuit construction (see article). Results in shallow circuit from Ry and CNOT gates
+ ladder: if true the excitation pattern will be local. E.g. in the pair from orbitals (1,2,3) we will have the excitations 1->2 and 2->3, if set to false we will have standard coupled-cluster style excitations - in this case this would be 1->2 and 1->3
+ """
+ ifedgesisNone:
+ raiseTequilaException("SPA ansatz within a standard orbital basis needs edges. Please provide with the keyword edges.\nExample: edges=[(0,1,2),(3,4)] would correspond to two edges created from orbitals (0,1,2) and (3,4), note that orbitals can only be assigned to a single edge")
+
+ # sanity checks
+ # current SPA implementation needs even number of electrons
+ ifself.n_electrons%2!=0:
+ raiseTequilaException("need even number of electrons for SPA ansatz.\n{} active electrons".format(self.n_electrons))
+ # making sure that enough edges are assigned
+ n_edges=len(edges)
+ iflen(edges)!=self.n_electrons//2:
+ raiseTequilaException("number of edges need to be equal to number of active electrons//2\n{} edges given\n{} active electrons\nfrozen core is {}".format(len(edges),self.n_electrons,self.parameters.frozen_core))
+ # making sure that orbitals are uniquely assigned to edges
+ foredge_qubitsinedges:
+ forq1inedge_qubits:
+ foredge2inedges:
+ ifedge2==edge_qubits:
+ continue
+ elifq1inedge2:
+ raiseTequilaException("make_spa_ansatz: faulty list of edges, orbitals are overlapping e.g. orbital {} is in edge {} and edge {}".format(q1,edge_qubits,edge2))
+
+ # auto assign if the circuit construction is optimized
+ # depending on the current qubit encoding (if hcb_to_me is implemnented we can optimize)
+ ifoptimizeisNone:
+ try:
+ have_hcb_to_me=self.hcb_to_me()isnotNone
+ except:
+ have_hcb_to_me=False
+ ifhave_hcb_to_me:
+ optimize=True
+ else:
+ optimize=False
+
+ U=QCircuit()
+
+ # construction of the optimized circuit
+ ifoptimize:
+ # circuit in HCB representation
+ # depends a bit on the ordering of the spin-orbitals in the encoding
+ # here we transform it to the qubits representing the up-spins
+ # the hcb_to_me sequence will then transfer to the actual encoding later
+ foredge_orbitalsinedges:
+ edge_qubits=[self.transformation.up(i)foriinedge_orbitals]
+ U+=gates.X(edge_qubits[0])
+ iflen(edge_qubits)==1:
+ continue
+ foriinrange(1,len(edge_qubits)):
+ q1=edge_qubits[i]
+ c=edge_qubits[i-1]
+ ifnotladder:
+ c=edge_qubits[0]
+ angle=Variable(name=((edge_orbitals[i-1],edge_orbitals[i]),"D",label))
+ ifuse_units_of_pi:
+ angle=angle*numpy.pi
+ ifi-1==0:
+ U+=gates.Ry(angle=angle,target=q1,control=None)
+ else:
+ U+=gates.Ry(angle=angle,target=q1,control=c)
+ U+=gates.CNOT(q1,c)
+
+
+ ifnothcb:
+ U+=self.hcb_to_me()
+ else:
+ # construction of the non-optimized circuit (UpCCD with paired doubles according to edges)
+ ifhcb:
+ U=self.prepare_hardcore_boson_reference()
+ else:
+ U=self.prepare_reference()
+ # will only work if the first orbitals in the edges are the reference orbitals
+ sane=True
+ reference_orbitals=self.reference_orbitals
+ foredge_qubitsinedges:
+ ifself.orbitals[edge_qubits[0]]notinreference_orbitals:
+ sane=False
+ iflen(edge_qubits)>1:
+ forq1inedge_qubits[1:]:
+ ifself.orbitals[q1]inreference_orbitals:
+ sane=False
+ ifnotsane:
+ raiseTequilaException("Non-Optimized SPA (e.g. with encodings that are not JW) will only work if the first orbitals of all SPA edges are occupied reference orbitals and all others are not. You gave edges={} and reference_orbitals are {}".format(edges,reference_orbitals))
+
+ foredge_qubitsinedges:
+ previous=edge_qubits[0]
+ iflen(edge_qubits)>1:
+ forq1inedge_qubits[1:]:
+ c=previous
+ ifnotladder:
+ c=edge_qubits[0]
+ angle=Variable(name=((c,q1),"D",label))
+ ifuse_units_of_pi:
+ angle=angle*numpy.pi
+ ifhcb:
+ U+=self.make_hardcore_boson_excitation_gate(indices=[(q1,c)],angle=angle)
+ else:
+ U+=self.make_excitation_gate(indices=[(2*c,2*q1),(2*c+1,2*q1+1)],angle=angle)
+ previous=q1
+ returnU
+
+
+
+[docs]
+ defmake_ansatz(self,name:str,*args,**kwargs):
+"""
+ Automatically calls the right subroutines to construct ansatze implemented in tequila.chemistry
+ name: namne of the ansatz, examples are: UpCCGSD, UpCCD, SPA, UCCSD, SPA+UpCCD, SPA+GS
+ """
+ name=name.lower()
+ ifname.strip()=="":
+ returnQCircuit()
+
+ if"+"inname:
+ U=QCircuit()
+ subparts=name.split("+")
+ U=self.make_ansatz(name=subparts[0],*args,**kwargs)
+ # making sure the there are is no undesired behaviour in layers after +
+ # reference should not be included since we are not starting from |00...0> anymore
+ if"include_reference"inkwargs:
+ kwargs.pop("include_reference")
+ # hcb optimization can also not be used (in almost all cases)
+ if"hcb_optimization"inkwargs:
+ kwargs.pop("hcb_optimization")
+ # making sure that we have no repeating variable names
+ label=None
+ if"label"inkwargs:
+ label=kwargs["label"]
+ kwargs.pop("label")
+ fori,subpartinenumerate(subparts[1:]):
+ U+=self.make_ansatz(name=subpart,*args,label=(label,i),include_reference=False,hcb_optimization=False,**kwargs)
+ returnU
+
+ ifname=="uccsd":
+ returnself.make_uccsd_ansatz(*args,**kwargs)
+ elif"spa"inname.lower():
+ if"hcb"notinkwargs:
+ hcb=False
+ if"hcb"inname.lower():
+ hcb=True
+ kwargs["hcb"]=hcb
+ returnself.make_spa_ansatz(*args,**kwargs)
+ elif"d"innameor"s"inname:
+ returnself.make_upccgsd_ansatz(name=name,*args,**kwargs)
+ else:
+ raiseTequilaException("unknown ansatz with name={}".format(name))
+
+
+
+[docs]
+ defmake_upccgsd_ansatz(self,
+ include_reference:bool=True,
+ name:str="UpCCGSD",
+ label:str=None,
+ order:int=None,
+ assume_real:bool=True,
+ hcb_optimization:bool=None,
+ spin_adapt_singles:bool=True,
+ neglect_z:bool=False,
+ mix_sd:bool=False,
+ *args,**kwargs):
+"""
+ UpGCCSD Ansatz similar as described by Lee et. al.
+
+ Parameters
+ ----------
+ include_reference
+ include the HF reference state as initial state
+ indices
+ pass custom defined set of indices from which the ansatz will be created
+ List of tuples of tuples spin-indices e.g. [((2*p,2*q),(2*p+1,2*q+1)), ...]
+ label
+ An additional label that is set with the variables
+ default is None and no label will be set: variables names will be
+ (x, (p,q)) for x in range(order)
+ with a label the variables will be named
+ (label, (x, (p,q)))
+ order
+ Order of the ansatz (default is 1)
+ determines how often the ordering gets repeated
+ parameters of repeating layers are independent
+ assume_real
+ assume a real wavefunction (that is always the case if the reference state is real)
+ reduces potential gradient costs from 4 to 2
+ mix_sd
+ Changes the ordering from first all doubles and then all singles excitations (DDDDD....SSSS....) to
+ a mixed order (DS-DS-DS-DS-...) where one DS pair acts on the same MOs. Useful to consider when systems
+ with high electronic correlation and system high error associated with the no Trotterized UCC.
+ Returns
+ -------
+ UpGCCSD ansatz
+ """
+
+ name=name.upper()
+
+ if("A"inname)andneglect_zisNone:
+ neglect_z=True
+ else:
+ neglect_z=False
+
+ iforderisNone:
+ try:
+ if"-"inname:
+ order=int(name.split("-")[0])
+ else:
+ order=1
+ except:
+ order=1
+
+ indices=self.make_upccgsd_indices(key=name)
+
+ # check if the used qubit encoding has a hcb transformation
+ have_hcb_trafo=True
+ try:
+ ifself.transformation.hcb_to_me()isNone:
+ have_hcb_trafo=False
+ except:
+ have_hcb_trafo=False
+
+
+ # consistency checks for optimization
+ ifhave_hcb_trafoandhcb_optimizationisNoneandinclude_reference:
+ hcb_optimization=True
+ if"HCB"inname:
+ hcb_optimization=True
+ ifhcb_optimizationandnothave_hcb_trafoand"HCB"notinname:
+ raiseTequilaException(
+ "use_hcb={} but transformation={} has no \'hcb_to_me\' function. Try transformation=\'ReorderedJordanWigner\'".format(
+ hcb_optimization,self.transformation))
+ if"S"innameand"HCB"inname:
+ if"HCB"innameand"S"inname:
+ raiseException(
+ "name={}, Singles can't be realized without mapping back to the standard encoding leave S or HCB out of the name".format(
+ name))
+ ifhcb_optimizationandmix_sd:
+ raiseTequilaException("Mixed SD can not be employed together with HCB Optimization")
+ # convenience
+ S="S"inname.upper()
+ D="D"inname.upper()
+
+ # first layer
+ ifnothcb_optimization:
+ U=QCircuit()
+ ifinclude_reference:
+ U=self.prepare_reference()
+ U+=self.make_upccgsd_layer(include_singles=S,include_doubles=D,indices=indices,assume_real=assume_real,
+ label=(label,0),mix_sd=mix_sd,spin_adapt_singles=spin_adapt_singles,*args,
+ **kwargs)
+ else:
+ U=QCircuit()
+ ifinclude_reference:
+ U=self.prepare_hardcore_boson_reference()
+ ifD:
+ U+=self.make_hardcore_boson_upccgd_layer(indices=indices,assume_real=assume_real,label=(label,0),
+ *args,**kwargs)
+
+ if"HCB"notinnameand(include_referenceorD):
+ U=self.hcb_to_me(U=U)
+
+ ifS:
+ U+=self.make_upccgsd_singles(indices=indices,assume_real=assume_real,label=(label,0),
+ spin_adapt_singles=spin_adapt_singles,neglect_z=neglect_z,*args,
+ **kwargs)
+
+ forkinrange(1,order):
+ U+=self.make_upccgsd_layer(include_singles=S,include_doubles=D,indices=indices,label=(label,k),
+ spin_adapt_singles=spin_adapt_singles,neglect_z=neglect_z,mix_sd=mix_sd)
+
+ returnU
+[docs]
+ defmake_uccsd_ansatz(self,trotter_steps:int=1,
+ initial_amplitudes:typing.Union[str,Amplitudes,ClosedShellAmplitudes]=None,
+ include_reference_ansatz=True,
+ parametrized=True,
+ threshold=1.e-8,
+ add_singles=None,
+ screening=True,
+ *args,**kwargs)->QCircuit:
+"""
+
+ Parameters
+ ----------
+ initial_amplitudes :
+ initial amplitudes given as ManyBodyAmplitudes structure or as string
+ where 'mp2', 'cc2' or 'ccsd' are possible initializations
+ include_reference_ansatz :
+ Also do the reference ansatz (prepare closed-shell Hartree-Fock) (Default value = True)
+ parametrized :
+ Initialize with variables, otherwise with static numbers (Default value = True)
+ trotter_steps: int :
+
+ initial_amplitudes: typing.Union[str :
+
+ Amplitudes :
+
+ ClosedShellAmplitudes] :
+ (Default value = "cc2")
+
+ Returns
+ -------
+ type
+ Parametrized QCircuit
+
+ """
+
+ ifhasattr(initial_amplitudes,"lower"):
+ ifinitial_amplitudes.lower()=="mp2"andadd_singlesisNone:
+ add_singles=True
+ elifinitial_amplitudesisnotNoneandadd_singlesisnotNone:
+ warnings.warn("make_uccsd_anstatz: add_singles has no effect when explicit amplitudes are passed down",
+ TequilaWarning)
+ elifadd_singlesisNone:
+ add_singles=True
+
+ ifself.n_electrons%2!=0:
+ raiseTequilaException("make_uccsd_ansatz currently only for closed shell systems")
+
+ nocc=self.n_electrons//2
+ nvirt=self.n_orbitals-nocc
+
+ Uref=QCircuit()
+ ifinclude_reference_ansatz:
+ Uref=self.prepare_reference()
+
+ amplitudes=initial_amplitudes
+ ifhasattr(initial_amplitudes,"lower"):
+ ifinitial_amplitudes.lower()=="mp2":
+ amplitudes=self.compute_mp2_amplitudes()
+ elifinitial_amplitudes.lower()=="ccsd":
+ amplitudes=self.compute_ccsd_amplitudes()
+ else:
+ try:
+ amplitudes=self.compute_amplitudes(method=initial_amplitudes.lower())
+ exceptExceptionasexc:
+ raiseTequilaException(
+ "{}\nDon't know how to initialize \'{}\' amplitudes".format(exc,initial_amplitudes))
+ ifamplitudesisNone:
+ tia=None
+ ifadd_singles:tia=numpy.zeros(shape=[nocc,nvirt])
+ amplitudes=ClosedShellAmplitudes(
+ tIjAb=numpy.zeros(shape=[nocc,nocc,nvirt,nvirt]),
+ tIA=tia)
+ screening=False
+
+ closed_shell=isinstance(amplitudes,ClosedShellAmplitudes)
+ indices={}
+
+ ifnotscreening:
+ threshold=0.0
+
+ ifnotisinstance(amplitudes,dict):
+ amplitudes=amplitudes.make_parameter_dictionary(threshold=threshold,screening=screening)
+ amplitudes=dict(sorted(amplitudes.items(),key=lambdax:numpy.fabs(x[1]),reverse=True))
+ forkey,tinamplitudes.items():
+ assert(len(key)%2==0)
+ ifnotnumpy.isclose(t,0.0,atol=threshold)ornotscreening:
+ ifclosed_shell:
+
+ iflen(key)==2andadd_singles:
+ # singles
+ angle=2.0*t
+ ifparametrized:
+ angle=2.0*Variable(name=key)
+ idx_a=(2*key[0],2*key[1])
+ idx_b=(2*key[0]+1,2*key[1]+1)
+ indices[idx_a]=angle
+ indices[idx_b]=angle
+ else:
+ assertlen(key)==4
+ angle=2.0*t
+ ifparametrized:
+ angle=2.0*Variable(name=key)
+ idx_abab=(2*key[0]+1,2*key[1]+1,2*key[2],2*key[3])
+ indices[idx_abab]=angle
+ ifkey[0]!=key[2]andkey[1]!=key[3]:
+ idx_aaaa=(2*key[0],2*key[1],2*key[2],2*key[3])
+ idx_bbbb=(2*key[0]+1,2*key[1]+1,2*key[2]+1,2*key[3]+1)
+ partner=tuple([key[2],key[1],key[0],key[3]])
+ anglex=2.0*(t-amplitudes[partner])
+ ifparametrized:
+ anglex=2.0*(Variable(name=key)-Variable(partner))
+ indices[idx_aaaa]=anglex
+ indices[idx_bbbb]=anglex
+ else:
+ raiseException("only closed-shell supported, please assemble yourself .... sorry :-)")
+
+ UCCSD=QCircuit()
+ factor=1.0/trotter_steps
+ forstepinrange(trotter_steps):
+ foridx,angleinindices.items():
+ converted=[(idx[2*i],idx[2*i+1])foriinrange(len(idx)//2)]
+ UCCSD+=self.make_excitation_gate(indices=converted,angle=factor*angle)
+ ifhasattr(initial_amplitudes,
+ "lower")andinitial_amplitudes.lower()=="mp2"andparametrizedandadd_singles:
+ # mp2 has no singles, need to initialize them here (if not parametrized initializling as 0.0 makes no sense though)
+ UCCSD+=self.make_upccgsd_layer(indices="upccsd",include_singles=True,include_doubles=False)
+ returnUref+UCCSD
+
+
+
+[docs]
+ defcompute_amplitudes(self,method:str,*args,**kwargs):
+"""
+ Compute closed-shell CC amplitudes
+
+ Parameters
+ ----------
+ method :
+ coupled-cluster methods like cc2, ccsd, cc3, ccsd(t)
+ Success might depend on backend
+ got an extra function for MP2
+ *args :
+
+ **kwargs :
+
+
+ Returns
+ -------
+
+ """
+ raiseTequilaException("compute amplitudes: Needs to be overwritten by backend")
+
+
+
+[docs]
+ defcompute_energy(self,method,*args,**kwargs):
+"""
+ Call classical methods over PySCF (needs to be installed) or
+ use as a shortcut to calculate quantum energies (see make_upccgsd_ansatz)
+
+ Parameters
+ ----------
+ method: method name
+ classical: HF, MP2, CCSD, CCSD(T), FCI -- with pyscf
+ quantum: UpCCD, UpCCSD, UpCCGSD, k-UpCCGSD, UCCSD,
+ see make_upccgsd_ansatz of the this class for more information
+ args
+ kwargs: for quantum methods, keyword arguments for minimizer
+
+ Returns
+ -------
+
+ """
+ ifany([xinmethod.upper()forxin["U"]]):
+ # simulate entirely in HCB representation if no singles are involved
+ if"S"notinmethod.upper().split("-")[-1]and"HCB"notinmethod.upper():
+ method="HCB-"+method
+ U=self.make_ansatz(name=method)
+ if"hcb"inmethod.lower():
+ H=self.make_hardcore_boson_hamiltonian()
+ else:
+ H=self.make_hamiltonian()
+ E=ExpectationValue(H=H,U=U)
+ fromtequilaimportminimize
+ returnminimize(objective=E,*args,**kwargs).energy
+ else:
+ fromtequila.quantumchemistryimportINSTALLED_QCHEMISTRY_BACKENDS
+ if"pyscf"notinINSTALLED_QCHEMISTRY_BACKENDS:
+ raiseTequilaException(
+ "PySCF needs to be installed to compute {}/{}".format(method,self.parameters.basis_set))
+ else:
+ fromtequila.quantumchemistryimportQuantumChemistryPySCF
+ molx=QuantumChemistryPySCF.from_tequila(self)
+ returnmolx.compute_energy(method=method)
+[docs]
+ defis_closed_shell(self,verify=False):
+ cs=self.n_electrons%2==0
+ ifverifyandnotcs:
+ raiseTequilaException("not a closed shell molecule: having {} electrons".format(self.n_electrons))
+ returncs
+
+
+
+[docs]
+ defis_canonical(self,verify=False,fock_matrix=None):
+ canonical=True
+ iffock_matrixisNone:
+ fock_matrix=self.compute_fock_matrix()
+
+ is_diagonal=numpy.isclose(numpy.linalg.norm(fock_matrix-numpy.diag(numpy.diag(fock_matrix))),0.0,atol=1.e-4)
+
+ ifnotis_diagonal:
+ canonical=False
+
+ refo=self.reference_orbitals
+
+ ifrefo[0].idx!=0:
+ canonical=False
+ foriinrange(len(refo)-1):
+ ifrefo[i].idx_total+1!=refo[i+1].idx_total:
+ canonical=False
+
+ ifverifyandnotcanonical:
+ data={"reference_orbitals":refo,"fock_matrix":fock_matrix}
+ raiseTequilaException(
+ "orbitals are not canonical or can not be verified as such -> implemented method only works for standard orbitals (preferably from psi4)\n{}".format(data))
+ returncanonical
+
+
+ @property
+ defrdm1(self):
+"""
+ Returns RMD1 if computed with compute_rdms function before
+ """
+ ifself._rdm1isnotNone:
+ returnself._rdm1
+ else:
+ print("1-RDM has not been computed. Return None for 1-RDM.")
+ returnNone
+
+ @property
+ defrdm2(self):
+"""
+ Returns RMD2 if computed with compute_rdms function before
+ This is returned in Dirac (physics) notation by default (can be changed in compute_rdms with keyword)!
+ """
+ ifself._rdm2isnotNone:
+ returnself._rdm2
+ else:
+ print("2-RDM has not been computed. Return None for 2-RDM.")
+ returnNone
+
+
+[docs]
+ defcompute_rdms(self,U:QCircuit=None,variables:Variables=None,spin_free:bool=True,
+ get_rdm1:bool=True,get_rdm2:bool=True,ordering="dirac",use_hcb:bool=False,
+ rdm_trafo:QubitHamiltonian=None,evaluate=True):
+"""
+ Computes the one- and two-particle reduced density matrices (rdm1 and rdm2) given
+ a unitary U. This method uses the standard ordering in physics as denoted below.
+ Note, that the representation of the density matrices depends on the qubit transformation
+ used. The Jordan-Wigner encoding corresponds to 'classical' second quantized density
+ matrices in the occupation picture.
+
+ We only consider real orbitals and thus real-valued RDMs.
+ The matrices are set as private members _rdm1, _rdm2 and can be accessed via the properties rdm1, rdm2.
+
+ .. math :
+ \\text{rdm1: } \\gamma^p_q = \\langle \\psi | a^p a_q | \\psi \\rangle
+ = \\langle U 0 | a^p a_q | U 0 \\rangle
+ \\text{rdm2: } \\gamma^{pq}_{rs} = \\langle \\psi | a^p a^q a_s a_r | \\psi \\rangle
+ = \\langle U 0 | a^p a^q a_s a_r | U 0 \\rangle
+
+ Parameters
+ ----------
+ U :
+ Quantum Circuit to achieve the desired state \\psi = U |0\\rangle, non-optional
+ variables :
+ If U is parametrized, then need to hand over a set of fixed variables
+ spin_free :
+ Set whether matrices should be spin-free (summation over spin) or defined by spin-orbitals
+ get_rdm1, get_rdm2 :
+ Set whether either one or both rdm1, rdm2 should be computed. If both are needed at some point,
+ it is recommended to compute them at once.
+ rdm_trafo :
+ The rdm operators can be transformed, e.g., a^dagger_i a_j -> U^dagger a^dagger_i a_j U,
+ where U represents the transformation. The default is set to None, implying that U equas the identity.
+ evaluate :
+ if true, the tequila expectation values are evaluated directly via the tq.simulate command.
+ the protocol is optimized to avoid repetation of wavefunction simulation
+ if false, the rdms are returned as tq.QTensors
+ Returns
+ -------
+ """
+ # Check whether unitary circuit is not 0
+ ifUisNone:
+ raiseTequilaException('Need to specify a Quantum Circuit.')
+ # Check whether transformation is BKSF.
+ # Issue here: when a single operator acts only on a subset of qubits, BKSF might not yield the correct
+ # transformation, because it computes the number of qubits incorrectly in this case.
+ # A hotfix such as for symmetry_conserving_bravyi_kitaev would require deeper changes, thus omitted for now
+ iftype(self.transformation).__name__=="BravyiKitaevFast":
+ raiseTequilaException(
+ "The Bravyi-Kitaev-Superfast transformation does not support general FermionOperators yet.")
+ # Set up number of spin-orbitals and molecular orbitals respectively
+ n_SOs=2*self.n_orbitals
+ n_MOs=self.n_orbitals
+
+ # Check whether unitary circuit is not 0
+ ifUisNone:
+ raiseTequilaException('Need to specify a Quantum Circuit.')
+
+ def_get_hcb_op(op_tuple):
+'''Build the hardcore boson operators: b^\dagger_ib_j + h.c. in qubit encoding '''
+ if(len(op_tuple)==2):
+ return2*Sm(op_tuple[0][0])*Sp(op_tuple[1][0])
+ elif(len(op_tuple)==4):
+ if((op_tuple[0][0]==op_tuple[1][0])and(op_tuple[2][0]==op_tuple[3][0])):# iijj uddu+duud
+ returnSm(op_tuple[0][0])*Sp(op_tuple[2][0])+Sm(op_tuple[2][0])*Sp(op_tuple[0][0])
+ if((op_tuple[0][0]==op_tuple[2][0])and(op_tuple[1][0]==op_tuple[3][0])and(
+ op_tuple[0][0]!=op_tuple[1][0])and(op_tuple[2][0]!=op_tuple[3][0])):# ijij uuuu+dddd
+ return4*Sm(op_tuple[0][0])*Sm(op_tuple[1][0])*Sp(op_tuple[2][0])*Sp(op_tuple[3][0])
+ if((op_tuple[0][0]==op_tuple[3][0])and(op_tuple[1][0]==op_tuple[2][0])and(
+ op_tuple[0][0]!=op_tuple[1][0])and(op_tuple[2][0]!=op_tuple[3][0])):# ijji abba
+ return-2*Sm(op_tuple[0][0])*Sm(op_tuple[1][0])*Sp(op_tuple[2][0])*Sp(op_tuple[3][0])
+ else:
+ returnZero()
+
+ def_get_of_op(operator_tuple):
+""" Returns operator given by a operator tuple as OpenFermion - Fermion operator """
+ op=openfermion.FermionOperator(operator_tuple)
+ returnop
+
+ def_get_qop_hermitian(of_operator)->QubitHamiltonian:
+""" Returns Hermitian part of Fermion operator as QubitHamiltonian """
+ qop=self.transformation(of_operator)
+ # qop = QubitHamiltonian(self.transformation(of_operator))
+ real,imag=qop.split(hermitian=True)
+ ifreal:
+ returnreal
+ elifnotreal:
+ raiseTequilaException(
+ "Qubit Hamiltonian does not have a Hermitian part. Operator ={}".format(of_operator))
+
+ def_build_1bdy_operators_spinful()->list:
+""" Returns spinful one-body operators as a symmetry-reduced list of QubitHamiltonians """
+ # Exploit symmetry pq = qp
+ ops=[]
+ forpinrange(n_SOs):
+ forqinrange(p+1):
+ op_tuple=((p,1),(q,0))
+ op=_get_of_op(op_tuple)
+ ops+=[op]
+
+ returnops
+
+ def_build_2bdy_operators_spinful()->list:
+""" Returns spinful two-body operators as a symmetry-reduced list of QubitHamiltonians """
+ # Exploit symmetries pqrs = -pqsr = -qprs = qpsr
+ # and = rspq
+ ops=[]
+ forpinrange(n_SOs):
+ forqinrange(p):
+ forrinrange(n_SOs):
+ forsinrange(r):
+ ifp*n_SOs+q>=r*n_SOs+s:
+ op_tuple=((p,1),(q,1),(s,0),(r,0))
+ op=_get_of_op(op_tuple)
+ ops+=[op]
+
+ returnops
+
+ def_build_1bdy_operators_spinfree()->list:
+""" Returns spinfree one-body operators as a symmetry-reduced list of QubitHamiltonians """
+ # Exploit symmetry pq = qp (not changed by spin-summation)
+ ops=[]
+ forpinrange(n_MOs):
+ forqinrange(p+1):
+ # Spin aa
+ op_tuple=((2*p,1),(2*q,0))
+ op=_get_of_op(op_tuple)
+ # Spin bb
+ op_tuple=((2*p+1,1),(2*q+1,0))
+ op+=_get_of_op(op_tuple)
+ ops+=[op]
+
+ returnops
+
+ def_build_2bdy_operators_spinfree()->list:
+""" Returns spinfree two-body operators as a symmetry-reduced list of QubitHamiltonians """
+ # Exploit symmetries pqrs = qpsr (due to spin summation, '-pqsr = -qprs' drops out)
+ # and = rspq
+ ops=[]
+ forp,q,r,sinproduct(range(n_MOs),repeat=4):
+ ifp*n_MOs+q>=r*n_MOs+sand(p>=qorr>=s):
+ # Spin aaaa
+ op_tuple=((2*p,1),(2*q,1),(2*s,0),(2*r,0))if(p!=qandr!=s)else'0.0 []'
+ op=_get_of_op(op_tuple)
+ # Spin abab
+ op_tuple=((2*p,1),(2*q+1,1),(2*s+1,0),(2*r,0))if(
+ 2*p!=2*q+1and2*r!=2*s+1)else'0.0 []'
+ op+=_get_of_op(op_tuple)
+ # Spin baba
+ op_tuple=((2*p+1,1),(2*q,1),(2*s,0),(2*r+1,0))if(
+ 2*p+1!=2*qand2*r+1!=2*s)else'0.0 []'
+ op+=_get_of_op(op_tuple)
+ # Spin bbbb
+ op_tuple=((2*p+1,1),(2*q+1,1),(2*s+1,0),(2*r+1,0))if(
+ p!=qandr!=s)else'0.0 []'
+ op+=_get_of_op(op_tuple)
+ ops+=[op]
+ returnops
+
+ def_assemble_rdm1(evals,rdm1=None)->numpy.ndarray:
+"""
+ Returns spin-ful or spin-free one-particle RDM built by symmetry conditions
+ Same symmetry with or without spin, so we can use the same function
+ """
+ N=n_MOsifspin_freeelsen_SOs
+ ifrdm1isNone:
+ rdm1=numpy.zeros([N,N])
+ ctr:int=0
+ forpinrange(N):
+ forqinrange(p+1):
+ rdm1[p,q]=evals[ctr]
+ # Symmetry pq = qp
+ rdm1[q,p]=rdm1[p,q]
+ ctr+=1
+
+ returnrdm1
+
+ def_assemble_rdm2_spinful(evals,rdm2=None)->numpy.ndarray:
+""" Returns spin-ful two-particle RDM built by symmetry conditions """
+ ctr:int=0
+ ifrdm2isNone:
+ rdm2=numpy.zeros([n_SOs,n_SOs,n_SOs,n_SOs])
+ forpinrange(n_SOs):
+ forqinrange(p):
+ forrinrange(n_SOs):
+ forsinrange(r):
+ ifp*n_SOs+q>=r*n_SOs+s:
+ rdm2[p,q,r,s]=evals[ctr]
+ # Symmetry pqrs = rspq
+ rdm2[r,s,p,q]=rdm2[p,q,r,s]
+ ctr+=1
+
+ # Further permutational symmetries due to anticommutation relations
+ forpinrange(n_SOs):
+ forqinrange(p):
+ forrinrange(n_SOs):
+ forsinrange(r):
+ rdm2[p,q,s,r]=-1*rdm2[p,q,r,s]# pqrs = -pqsr
+ rdm2[q,p,r,s]=-1*rdm2[p,q,r,s]# pqrs = -qprs
+ rdm2[q,p,s,r]=rdm2[p,q,r,s]# pqrs = qpsr
+
+ returnrdm2
+
+ def_assemble_rdm2_spinfree(evals,rdm2=None)->numpy.ndarray:
+""" Returns spin-free two-particle RDM built by symmetry conditions """
+ ctr:int=0
+ ifrdm2isNone:
+ rdm2=numpy.zeros([n_MOs,n_MOs,n_MOs,n_MOs])
+ forp,q,r,sinproduct(range(n_MOs),repeat=4):
+ ifp*n_MOs+q>=r*n_MOs+sand(p>=qorr>=s):
+ rdm2[p,q,r,s]=evals[ctr]
+ # Symmetry pqrs = rspq
+ rdm2[r,s,p,q]=rdm2[p,q,r,s]
+ ctr+=1
+
+ # Further permutational symmetry: pqrs = qpsr
+ forp,q,r,sinproduct(range(n_MOs),repeat=4):
+ ifp>=qorr>=s:
+ rdm2[q,p,s,r]=rdm2[p,q,r,s]
+
+ returnrdm2
+
+ def_build_1bdy_operators_hcb()->list:
+""" Returns hcb one-body operators as a symmetry-reduced list of QubitHamiltonians """
+ # Exploit symmetry pq = qp (not changed by spin-summation)
+ ops=[]
+ forpinrange(n_MOs):
+ forqinrange(p+1):
+ if(p==q):
+ if(self.transformation.up_then_down):
+ op_tuple=((p,1),(p,0))
+ op=_get_hcb_op(op_tuple)
+ else:
+ op_tuple=((2*p,1),(2*p,0))
+ op=_get_hcb_op(op_tuple)
+ ops+=[op]
+ else:
+ ops+=[Zero()]
+ returnops
+
+ def_build_2bdy_operators_hcb()->list:
+""" Returns hcb two-body operators as a symmetry-reduced list of QubitHamiltonians """
+ # Exploit symmetries pqrs = qpsr (due to spin summation, '-pqsr = -qprs' drops out)
+ # and = rspq
+ ops=[]
+ scale=2
+ ifself.transformation.up_then_down:
+ scale=1
+ forp,q,r,sinproduct(range(n_MOs),repeat=4):
+ ifp*n_MOs+q>=r*n_MOs+sand(p>=qorr>=s):
+ # Spin abba+ baab allow p=q=r=s orb iijj
+ op_tuple=((scale*p,1),(scale*q,1),(scale*r,0),(scale*s,0))if(
+ p==qands==r)else'0.0 []'
+ op=_get_hcb_op(op_tuple)
+ # Spin abba+ baab dont allow p=q=r=s orb ijij
+ op_tuple=((scale*p,1),(scale*q,1),(scale*r,0),(scale*s,0))if(
+ p!=qandr!=sandp==rands==q)else'0.0 []'
+ op+=_get_hcb_op(op_tuple)
+ # Spin aaaa+ bbbb dont allow p=q=r=s orb ijji
+ op_tuple=((scale*p,1),(scale*q,1),(scale*r,0),(scale*s,0))if(
+ p!=qandr!=sandp==sandq==r)else'0.0 []'
+ op+=_get_hcb_op(op_tuple)
+ ops+=[op]
+ returnops
+
+ # Build operator lists
+ qops=[]
+ ifspin_freeandnotuse_hcb:
+ qops+=_build_1bdy_operators_spinfree()ifget_rdm1else[]
+ qops+=_build_2bdy_operators_spinfree()ifget_rdm2else[]
+ elifuse_hcb:
+ qops+=_build_1bdy_operators_hcb()ifget_rdm1else[]
+ qops+=_build_2bdy_operators_hcb()ifget_rdm2else[]
+ else:
+ ifuse_hcb:
+ raiseTequilaException(
+ "compute_rdms: spin_free={} and use_hcb={} are not compatible".format(spin_free,use_hcb))
+ qops+=_build_1bdy_operators_spinful()ifget_rdm1else[]
+ qops+=_build_2bdy_operators_spinful()ifget_rdm2else[]
+
+ # Transform operator lists to QubitHamiltonians
+ if(notuse_hcb):
+ qops=[_get_qop_hermitian(op)foropinqops]
+
+ # Compute expected values
+ rdm1=None
+ rdm2=None
+ fromtequilaimportQTensor
+ ifevaluate:
+ ifrdm_trafoisNone:
+ evals=simulate(ExpectationValue(H=qops,U=U,shape=[len(qops)]),variables=variables)
+ else:
+ qops=[rdm_trafo.dagger()*qops[i]*rdm_trafoforiinrange(len(qops))]
+ evals=simulate(ExpectationValue(H=qops,U=U,shape=[len(qops)]),variables=variables)
+ else:
+ ifrdm_trafoisNone:
+ evals=[ExpectationValue(H=x,U=U)forxinqops]
+ N=n_MOsifspin_freeelsen_SOs
+ rdm1=QTensor(shape=[N,N])
+ rdm2=QTensor(shape=[N,N,N,N])
+ else:
+ raiseTequilaException("compute_rdms: rdm_trafo was set but evaluate flag is False (not supported)")
+
+ # Assemble density matrices
+ # If self._rdm1, self._rdm2 exist, reset them if they are of the other spin-type
+ def_reset_rdm(rdm):
+ ifrdmisnotNone:
+ if(spin_freeoruse_hcb)andrdm.shape[0]!=n_MOs:
+ returnNone
+ ifnotspin_freeandrdm.shape[0]!=n_SOs:
+ returnNone
+ returnrdm
+
+ self._rdm1=_reset_rdm(self._rdm1)
+ self._rdm2=_reset_rdm(self._rdm2)
+ # Split expectation values in 1- and 2-particle expectation values
+ ifget_rdm1:
+ len_1=n_MOs*(n_MOs+1)//2if(spin_freeoruse_hcb)elsen_SOs*(n_SOs+1)//2
+ else:
+ len_1=0
+ evals_1,evals_2=evals[:len_1],evals[len_1:]
+ # Build matrices using the expectation values
+ self._rdm1=_assemble_rdm1(evals_1,rdm1=rdm1)ifget_rdm1elseself._rdm1
+ ifspin_freeoruse_hcb:
+ self._rdm2=_assemble_rdm2_spinfree(evals_2,rdm2=rdm2)ifget_rdm2elseself._rdm2
+ else:
+ self._rdm2=_assemble_rdm2_spinful(evals_2,rdm2=rdm2)ifget_rdm2elseself._rdm2
+
+ ifget_rdm2:
+ rdm2=NBodyTensor(elems=self.rdm2,ordering="dirac",verify=False)
+ rdm2.reorder(to=ordering)
+ rdm2=rdm2.elems
+ self._rdm2=rdm2
+
+ ifget_rdm1:
+ ifget_rdm2:
+ returnself.rdm1,self.rdm2
+ else:
+ returnself.rdm1
+ elifget_rdm2:
+ returnself.rdm2
+ else:
+ warnings.warn("compute_rdms called with instruction to not compute?",TequilaWarning)
+
+
+
+[docs]
+ defrdm_spinsum(self,sum_rdm1:bool=True,sum_rdm2:bool=True)->tuple:
+"""
+ Given the spin-ful 1- and 2-particle reduced density matrices, compute the spin-free RDMs by spin summation.
+
+ Parameters
+ ----------
+ sum_rdm1, sum_rdm2 :
+ If set to true, perform spin summation on rdm1, rdm2
+
+ Returns
+ -------
+ rdm1_spinsum, rdm2_spinsum :
+ The desired spin-free matrices
+ """
+ n_MOs=self.n_orbitals
+ rdm1_spinsum=None
+ rdm2_spinsum=None
+
+ # Spin summation on rdm1
+ ifsum_rdm1:
+ # Check whether spin-rdm2 exists
+ ifself._rdm1isNone:
+ raiseTequilaException("The spin-RDM for the 1-RDM does not exist!")
+ # Check whether existing rdm1 is in spin-orbital basis
+ ifself._rdm1.shape[0]!=2*n_MOs:
+ raiseTequilaException("The existing RDM needs to be in spin-orbital basis, it is already spin-free!")
+ # Do summation
+ rdm1_spinsum=numpy.zeros([n_MOs,n_MOs])
+ forpinrange(n_MOs):
+ forqinrange(p+1):
+ rdm1_spinsum[p,q]+=self._rdm1[2*p,2*q]
+ rdm1_spinsum[p,q]+=self._rdm1[2*p+1,2*q+1]
+ forpinrange(n_MOs):
+ forqinrange(p):
+ rdm1_spinsum[q,p]=rdm1_spinsum[p,q]
+
+ # Spin summation on rdm2
+ ifsum_rdm2:
+ # Check whether spin-rdm2 exists
+ ifself._rdm2isNone:
+ raiseTequilaException("The spin-RDM for the 2-RDM does not exist!")
+ # Check whether existing rdm2 is in spin-orbital basis
+ ifself._rdm2.shape[0]!=2*n_MOs:
+ raiseTequilaException("The existing RDM needs to be in spin-orbital basis, it is already spin-free!")
+ # Do summation
+ rdm2_spinsum=numpy.zeros([n_MOs,n_MOs,n_MOs,n_MOs])
+ forp,q,r,sinproduct(range(n_MOs),repeat=4):
+ rdm2_spinsum[p,q,r,s]+=self._rdm2[2*p,2*q,2*r,2*s]
+ rdm2_spinsum[p,q,r,s]+=self._rdm2[2*p+1,2*q,2*r+1,2*s]
+ rdm2_spinsum[p,q,r,s]+=self._rdm2[2*p,2*q+1,2*r,2*s+1]
+ rdm2_spinsum[p,q,r,s]+=self._rdm2[2*p+1,2*q+1,2*r+1,2*s+1]
+
+ returnrdm1_spinsum,rdm2_spinsum
+
+
+
+[docs]
+ defperturbative_f12_correction(self,rdm1:numpy.ndarray=None,rdm2:numpy.ndarray=None,
+ gamma:float=1.4,n_ri:int=None,
+ external_info:dict=None,**kwargs)->float:
+"""
+ Computes the spin-free [2]_R12 correction, needing only the 1- and 2-RDM of a reference method
+ Requires either 1-RDM, 2-RDM or information to compute them in kwargs
+
+ Parameters
+ ----------
+ rdm1 :
+ 1-electron reduced density matrix
+ rdm2 :
+ 2-electron reduced density matrix
+ gamma :
+ f12-exponent, for a correlation factor f_12 = -1/gamma * exp[-gamma*r_12]
+ n_ri :
+ dimensionality of RI-basis; specify only, if want to truncate available RI-basis
+ if None, then the maximum available via tensors / basis-set is used
+ must not be larger than size of available RI-basis, and not smaller than size of OBS
+ for n_ri==dim(OBS), the correction returns zero
+ external_info :
+ for usage in qc_base, need to provide information where to find one-body tensor f12-tensor <rs|f_12|pq>;
+ pass dictionary with {"f12_filename": where to find f12-tensor, "scheme": ordering scheme of tensor}
+ kwargs :
+ e.g. RDM-information via {"U": QCircuit, "variables": optimal angles}, needs to be passed if rdm1,rdm2 not
+ yet computed
+
+ Returns
+ -------
+ the f12 correction for the energy
+ """
+ from.f12_corrections._f12_correction_baseimportExplicitCorrelationCorrection
+ correction=ExplicitCorrelationCorrection(mol=self,rdm1=rdm1,rdm2=rdm2,gamma=gamma,
+ n_ri=n_ri,external_info=external_info,**kwargs)
+ returncorrection.compute()
+
+
+
+[docs]
+ defn_rotation(self,i,phi):
+'''
+ Creates a quantum circuit that applies a phase rotation based on phi to both components (up and down) of a given qubit.
+
+ Parameters:
+ - i (int): The index of the qubit to which the rotation will be applied.
+ - phi (float): The rotation angle. The actual rotation applied will be multiplied with -2 for both components.
+
+ Returns:
+ - QCircuit: A quantum circuit object containing the sequence of rotations applied to the up and down components of the specified qubit.
+ '''
+
+ # Generate number operators for the up and down components of the qubit.
+ n_up=self.make_number_op(2*i)
+ n_down=self.make_number_op(2*i+1)
+
+ # Start a new circuit and apply rotations to each component.
+ circuit=gates.GeneralizedRotation(generator=n_up,angle=-2*phi)
+ circuit+=gates.GeneralizedRotation(generator=n_down,angle=-2*phi)
+ returncircuit
+
+
+
+[docs]
+ defget_givens_circuit(self,unitary,tol=1e-12,ordering=OPTIMIZED_ORDERING):
+'''
+ Constructs a quantum circuit from a given real unitary matrix using Givens rotations.
+
+ This method decomposes a unitary matrix into a series of Givens and Rz (phase) rotations,
+ then constructs and returns a quantum circuit that implements this sequence of rotations.
+
+ Parameters:
+ - unitary (numpy.array): A real unitary matrix representing the transformation to implement.
+ - tol (float): A tolerance threshold below which matrix elements are considered zero.
+ - ordering (list of tuples or 'Optimized'): Custom ordering of indices for Givens rotations or 'Optimized' to generate them automatically.
+
+ Returns:
+ - QCircuit: A quantum circuit implementing the series of rotations decomposed from the unitary.
+ '''
+ # Decompose the unitary matrix into Givens and phase (Rz) rotations.
+ theta_list,phi_list=get_givens_decomposition(unitary,tol,ordering)
+
+ # Initialize an empty quantum circuit.
+ circuit=QCircuit()
+
+ # Add all Rz (phase) rotations to the circuit.
+ forphiinphi_list:
+ circuit+=self.n_rotation(phi[1],phi[0])
+
+ # Add all Givens rotations to the circuit.
+ forthetainreversed(theta_list):
+ circuit+=self.UR(theta[1],theta[2],theta[0]*2)
+
+ returncircuit
+[docs]
+defgivens_matrix(n,p,q,theta):
+'''
+ Construct a complex Givens rotation matrix of dimension n by theta between rows/columns p and q.
+ '''
+'''
+ Generates a Givens rotation matrix of size n x n to rotate by angle theta in the (p, q) plane. This matrix can be complex
+
+ Parameters:
+ - n (int): The size of the Givens rotation matrix.
+ - p (int): The first index for the rotation plane.
+ - q (int): The second index for the rotation plane.
+ - theta (float): The rotation angle.
+
+ Returns:
+ - numpy.array: The Givens rotation matrix.
+ '''
+ matrix=numpy.eye(n)# Matrix to hold complex numbers
+ cos_theta=numpy.cos(theta)
+ sin_theta=numpy.sin(theta)
+
+ # Directly assign cosine and sine without complex phase adjustment
+ matrix[p,p]=cos_theta
+ matrix[q,q]=cos_theta
+ matrix[p,q]=sin_theta
+ matrix[q,p]=-sin_theta
+
+ returnmatrix
+
+
+
+[docs]
+defget_givens_decomposition(unitary,tol=1e-12,ordering=OPTIMIZED_ORDERING,return_diagonal=False):
+'''
+ Decomposes a real unitary matrix into Givens rotations (theta) and Rz rotations (phi).
+
+ Parameters:
+ - unitary (numpy.array): A real unitary matrix to decompose. It cannot be complex.
+ - tol (float): Tolerance for considering matrix elements as zero. Elements with absolute value less than tol are treated as zero.
+ - ordering (list of tuples or 'Optimized'): Custom ordering of indices for Givens rotations or 'Optimized' to generate them automatically.
+ - return_diagonal (bool): If True, the function also returns the diagonal matrix as part of the output.
+
+ Returns:
+ - list: A list of tuples, each representing a Givens rotation. Each tuple contains the rotation angle theta and indices (i,j) of the rotation.
+ - list: A list of tuples, each representing an Rz rotation. Each tuple contains the rotation angle phi and the index (i) of the rotation.
+ - numpy.array (optional): The diagonal matrix after applying all Givens rotations, returned if return_diagonal is True.
+ '''
+ U=unitary# no need to copy as we don't modify the original
+ U[abs(U)<tol]=0# Zeroing out the small elements as per the tolerance level.
+ n=U.shape[0]
+
+ # Determine optimized ordering if specified.
+ ifordering==OPTIMIZED_ORDERING:
+ ordering=ff.depth_eff_order_mf(n)
+
+ theta_list=[]
+ phi_list=[]
+
+ defcalcTheta(U,c,r):
+'''Calculate and apply the Givens rotation for a specific matrix element.'''
+ t=numpy.arctan2(-U[r,c],U[r-1,c])
+ theta_list.append((t,r,r-1))
+ g=givens_matrix(n,r,r-1,t)
+ U=numpy.dot(g,U)
+
+ returnU
+
+ # Apply and store Givens rotations as per the given or computed ordering.
+ iforderingisNone:
+ forcinrange(n):
+ forrinrange(n-1,c,-1):
+ U=calcTheta(U,c,r)
+ else:
+ forr,cinordering:
+ U=calcTheta(U,c,r)
+
+ # Calculating the Rz rotations based on the phases of the diagonal elements.
+ # For real elements this means a 180 degree shift, i.e. a sign change.
+ foriinrange(n):
+ ph=numpy.angle(U[i,i])
+ phi_list.append((ph,i))
+
+ # Filtering out rotations without significance.
+ theta_list_new=[]
+ fori,thetainenumerate(theta_list):
+ ifabs(theta[0]%(2*numpy.pi))>tol:
+ theta_list_new.append(theta)
+
+ phi_list_new=[]
+ fori,phiinenumerate(phi_list):
+ ifabs(phi[0])>tol:
+ phi_list_new.append(phi)
+
+ ifreturn_diagonal:
+ # Optionally return the resulting diagonal
+ returntheta_list_new,phi_list_new,U
+ else:
+ returntheta_list_new,phi_list_new
+
+
+
+[docs]
+defreconstruct_matrix_from_givens(n,theta_list,phi_list,to_real_if_possible=True,tol=1e-12):
+'''
+ Reconstructs a matrix from given Givens rotations and Rz diagonal rotations.
+ This function is effectively an inverse of get_givens_decomposition, and therefore only works with data in the same format as its output.
+
+ Parameters:
+ - n (int): The size of the unitary matrix to be reconstructed.
+ - theta_list (list of tuples): Each tuple contains (angle, i, j) representing a Givens rotation of `angle` radians, applied to rows/columns `i` and `j`.
+ - phi_list (list of tuples): Each tuple contains (angle, i), representing an Rz rotation by `angle` radians applied to the `i`th diagonal element.
+ - to_real_if_possible (bool): If True, converts the matrix to real if its imaginary part is effectively zero.
+ - tol (float): The tolerance whether to swap a complex rotation for a sign change.
+
+ Returns:
+ - numpy.ndarray: The reconstructed complex or real matrix, depending on the `to_real_if_possible` flag and matrix composition.
+ '''
+ # Start with an identity matrix
+ reconstructed=numpy.eye(n,dtype=complex)
+
+ # Apply Rz rotations for diagonal elements
+ forphiinphi_list:
+ angle,i=phi
+ # Directly apply a sign flip if the rotation angle is π
+ ifnumpy.isclose(angle,numpy.pi,atol=tol):
+ reconstructed[i,i]*=-1
+ else:
+ reconstructed[i,i]*=numpy.exp(1j*angle)
+
+ # Apply Givens rotations in reverse order
+ forthetainreversed(theta_list):
+ angle,i,j=theta
+ g=givens_matrix(n,i,j,angle)
+ reconstructed=numpy.dot(g.conj().T,reconstructed)# Transpose of Givens matrix applied to the left
+
+ # Convert matrix to real if its imaginary part is negligible unless disabled via to_real_if_possible
+ ifto_real_if_possible:
+ # Directly apply a sign flip if the rotation angle is π
+ ifnumpy.all(reconstructed.imag==0):
+ # Convert to real by taking the real part
+ reconstructed=reconstructed.real
+
+ returnreconstructed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/simulators/simulator_api.html b/docs/sphinx/_modules/tequila_code/simulators/simulator_api.html
new file mode 100644
index 0000000..f535b56
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/simulators/simulator_api.html
@@ -0,0 +1,703 @@
+
+
+
+
+
+
+
+ tequila_code.simulators.simulator_api — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+defpick_backend(backend:str=None,samples:int=None,noise:NoiseModel=None,device=None,
+ exclude_symbolic:bool=True)->str:
+
+"""
+ choose, or verify, a backend for the user.
+ Parameters
+ ----------
+ backend: str, optional:
+ what backend to choose or verify. if None: choose for the user.
+ samples: int, optional:
+ if int and not None, choose (verify) a simulator which supports sampling.
+ noise: str or NoiseModel, optional:
+ if not None, choose (verify) a simulator supports the specified noise.
+ device: optional:
+ verify that a given backend supports the specified device. MUST specify backend, if not None.
+ if None: do not emulate or use real device.
+ exclude_symbolic: bool, optional:
+ whether or not to exclude the tequila debugging simulator from the available simulators, when choosing.
+
+ Returns
+ -------
+ str:
+ the name of the chosen (or verified) backend.
+ """
+
+ iflen(INSTALLED_SIMULATORS)==0:
+ raiseTequilaException("No simulators installed on your system")
+
+ ifbackendisNoneanddeviceisnotNone:
+ raiseTequilaException('device use requires backend specification!')
+
+ ifbackendisNone:
+ ifnoiseisNone:
+ ifsamplesisNone:
+ forfinSUPPORTED_BACKENDS:
+ iffinINSTALLED_SIMULATORS:
+ returnf
+ else:
+ forfinINSTALLED_SAMPLERS.keys():
+ returnf
+ else:
+ ifsamplesisNone:
+ raiseTequilaException(
+ "Noise requires sampling; please provide a positive, integer value for samples")
+ forfinSUPPORTED_NOISE_BACKENDS:
+ returnf
+ raiseTequilaException(
+ 'Could not find any installed sampler!')
+
+
+ ifhasattr(backend,"lower"):
+ backend=backend.lower()
+
+ ifbackend=="random":
+ ifdeviceisnotNone:
+ raiseTequilaException('cannot ask for a random backend and a specific device!')
+ fromnumpyimportrandomasrandom
+ importtime
+ state=random.RandomState(int(str(time.process_time()).split('.')[-1])%2**32)
+ ifsamplesisNone:
+ backend=state.choice(list(INSTALLED_SIMULATORS.keys()),1)[0]
+ else:
+ backend=state.choice(list(INSTALLED_SAMPLERS.keys()),1)[0]
+
+ ifexclude_symbolic:
+ while(backend=="symbolic"):
+ backend=state.choice(list(INSTALLED_SIMULATORS.keys()),1)[0]
+ returnbackend
+
+ ifbackendnotinSUPPORTED_BACKENDS:
+ raiseTequilaException("Backend {backend} not supported ".format(backend=backend))
+
+ elifnoiseisNoneandsamplesisNoneandbackendnotinINSTALLED_SIMULATORS.keys():
+ raiseTequilaException("Backend {backend} not installed ".format(backend=backend))
+ elifnoiseisNoneandsamplesisnotNoneandbackendnotinINSTALLED_SAMPLERS.keys():
+ raiseTequilaException("Backend {backend} not installed or sampling not supported".format(backend=backend))
+ elifnoiseisnotNoneandsamplesisnotNoneandbackendnotinINSTALLED_NOISE_SAMPLERS.keys():
+ raiseTequilaException(
+ "Backend {backend} not installed or else Noise has not been implemented".format(backend=backend))
+
+ returnbackend
+
+
+
+
+[docs]
+defcompile_objective(objective:typing.Union['Objective'],
+ variables:typing.Dict['Variable','RealNumber']=None,
+ backend:str=None,
+ samples:int=None,
+ device:str=None,
+ noise:NoiseModel=None,
+ *args,
+ **kwargs)->Objective:
+"""
+ compile an objective to render it callable and return it.
+ Parameters
+ ----------
+ objective: Objective:
+ the objective to compile
+ variables: dict, optional:
+ the variables to compile the objective with. Will autogenerate zeros for all variables if not supplied.
+ backend: str, optional:
+ the backend to compile the objective to.
+ samples: int, optional:
+ only matters if not None; compile the objective for sampling/verify backend can do so
+ device: optional:
+ the device on which the objective should (perhaps emulatedly) sample.
+ noise: str or NoiseModel, optional:
+ the noise to apply to all circuits in the objective.
+ args
+ kwargs
+
+ Returns
+ -------
+ Objective:
+ the compiled objective.
+ """
+
+ backend=pick_backend(backend=backend,samples=samples,noise=noise,device=device)
+
+ # dummy variables
+ ifvariablesisNone:
+ variables={k:0.0forkinobjective.extract_variables()}
+
+ ExpValueType=INSTALLED_SIMULATORS[pick_backend(backend=backend)].ExpValueType
+ all_compiled=True
+ # check if compiling is necessary
+ forarginobjective.args:
+ ifhasattr(arg,"U")andisinstance(arg,BackendExpectationValue):
+ ifnotisinstance(arg,ExpValueType):
+ warnings.warn(
+ "Looks like part the objective was already compiled for another backend.\nFound ExpectationValue of type {} and {}\n... proceeding with hybrid\n".format(
+ type(arg),ExpValueType),TequilaWarning)
+ elifhasattr(arg,"U")andnotisinstance(arg,BackendExpectationValue):
+ all_compiled=False
+
+ ifall_compiled:
+ returnobjective
+
+ argsets=objective.argsets
+ compiled_sets=[]
+ forargsetinargsets:
+ compiled_args=[]
+ # avoid double compilations
+ expectationvalues={}
+ forarginargset:
+ ifhasattr(arg,"H")andhasattr(arg,"U")andnotisinstance(arg,BackendExpectationValue):
+ ifargnotinexpectationvalues:
+ compiled_expval=ExpValueType(arg,variables=variables,noise=noise,device=device,*args,**kwargs)
+ expectationvalues[arg]=compiled_expval
+ else:
+ compiled_expval=expectationvalues[arg]
+ compiled_args.append(compiled_expval)
+ else:
+ compiled_args.append(arg)
+ compiled_sets.append(compiled_args)
+ ifisinstance(objective,Objective):
+ returntype(objective)(args=compiled_sets[0],transformation=objective.transformation)
+
+
+
+
+[docs]
+defcompile_circuit(abstract_circuit:'QCircuit',
+ variables:typing.Dict['Variable','RealNumber']=None,
+ backend:str=None,
+ samples:int=None,
+ noise:NoiseModel=None,
+ device:str=None,
+ *args,
+ **kwargs)->BackendCircuit:
+"""
+ compile a circuit to render it callable and return it.
+ Parameters
+ ----------
+ abstract_circuit: QCircuit:
+ the circuit to compile
+ variables: dict, optional:
+ the variables to compile the circuit with.
+ backend: str, optional:
+ the backend to compile the circuit to.
+ samples: int, optional:
+ only matters if not None; compile the circuit for sampling/verify backend can do so
+ device: optional:
+ the device on which the circuit should (perhaps emulatedly) sample.
+ noise: str or NoiseModel, optional:
+ the noise to apply to the circuit
+ args
+ kwargs
+
+ Returns
+ -------
+ BackendCircuit:
+ the compiled circuit.
+ """
+
+ CircType=INSTALLED_SIMULATORS[
+ pick_backend(backend=backend,samples=samples,noise=noise,device=device)].CircType
+
+ # dummy variables
+ ifvariablesisNone:
+ variables={k:0.0forkinabstract_circuit.extract_variables()}
+
+ ifhasattr(abstract_circuit,"simulate"):
+ ifnotisinstance(abstract_circuit,CircType):
+ abstract_circuit=abstract_circuit.abstract_circuit
+ warnings.warn(
+ "Looks like the circuit was already compiled for another backend.\nChanging from {} to {}\n".format(
+ type(abstract_circuit),CircType),TequilaWarning)
+ else:
+ returnabstract_circuit
+
+ returnCircType(abstract_circuit=abstract_circuit,variables=variables,noise=noise,device=device,*args,**kwargs)
+
+
+
+
+[docs]
+defsimulate(objective:typing.Union['Objective','QCircuit','QTensor'],
+ variables:Dict[Union[Variable,Hashable],RealNumber]=None,
+ samples:int=None,
+ backend:str=None,
+ noise:NoiseModel=None,
+ device:str=None,
+ *args,
+ **kwargs)->Union[RealNumber,'QubitWaveFunction']:
+"""Simulate a tequila objective or circuit
+
+ Parameters
+ ----------
+ objective: Objective:
+ tequila objective or circuit
+ variables: Dict:
+ The variables of the objective given as dictionary
+ with keys as tequila Variables/hashable types and values the corresponding real numbers
+ samples : int, optional:
+ if None a full wavefunction simulation is performed, otherwise a fixed number of samples is simulated
+ backend : str, optional:
+ specify the backend or give None for automatic assignment
+ noise: NoiseModel, optional:
+ specify a noise model to apply to simulation/sampling
+ device:
+ a device upon which (or in emulation of which) to sample
+ *args :
+
+ **kwargs :
+ read_out_qubits = list[int] (define the qubits which shall be measured, has only effect on pure QCircuit simulation with samples)
+
+ Returns
+ -------
+ float or QubitWaveFunction
+ the result of simulation.
+ """
+
+ variables=format_variable_dictionary(variables)
+
+ ifvariablesisNoneandnot(len(objective.extract_variables())==0):
+ raiseTequilaException(
+ "You called simulate for a parametrized type but forgot to pass down the variables: {}".format(
+ objective.extract_variables()))
+
+ compiled_objective=compile(objective=objective,samples=samples,variables=variables,backend=backend,
+ noise=noise,device=device,*args,**kwargs)
+
+ returncompiled_objective(variables=variables,samples=samples,*args,**kwargs)
+
+
+
+
+[docs]
+defdraw(objective,variables=None,backend:str=None,name=None,*args,**kwargs):
+"""
+ Pretty output (depends on installed backends) for jupyter notebooks
+ or similar HTML environments
+
+ Parameters
+ ----------
+ objective :
+ the tequila objective to print out
+ variables : optional:
+ Give variables if the objective is parametrized (not necesarry for displaying)
+ name: optional:
+ Name the objective (changes circuit filenames for qpic backend)
+ backend: str, optional:
+ chose preferred backend (of None or not found it will be automatically picked)
+ """
+ ifbackendnotinINSTALLED_SIMULATORS:
+ backend=None
+ ifnameisNone:
+ name=abs(hash("tmp"))
+
+ ifbackendisNone:
+ fromtequila.circuit.qpicimportsystem_has_qpic
+ ifsystem_has_qpic:
+ backend="qpic"
+ elif"cirq"inINSTALLED_SIMULATORS:
+ backend="cirq"
+ elif"qiskit"inINSTALLED_SIMULATORS:
+ backend="qiskit"
+
+ ifisinstance(objective,QTensor):
+ print("won't draw out all objectives in a tensor")
+ print(objective)
+
+ ifisinstance(objective,Objective):
+ print(objective)
+ drawn={}
+ fori,Einenumerate(objective.get_expectationvalues()):
+ ifEindrawn:
+ print("\nExpectation Value {} is the same as {}".format(i,drawn[E]))
+ else:
+ print("\nExpectation Value {}:".format(i))
+ measurements=E.count_measurements()
+ print("total measurements = {}".format(measurements))
+ variables=E.U.extract_variables()
+ print("variables = {}".format(len(variables)))
+ filename="{}_{}.png".format(name,i)
+ print("circuit = {}".format(filename))
+ draw(E.U,backend=backend,filename=filename)
+ drawn[E]=i
+ else:
+ ifbackendisNone:
+ print(objective)
+ elifbackend.lower()in["qpic","html"]:
+ try:
+ importIPython
+ importqpic
+ fromtequila.circuit.qpicimportexport_to
+ if"filename"notinkwargs:
+ kwargs["filename"]="tmp_{}.png".format(hash(backend))
+
+ circuit=objective
+ ifhasattr(circuit,"U"):
+ circuit=circuit.U
+ ifhasattr(circuit,"abstract_circuit"):
+ circuit=objective.abstract_circuit
+
+ export_to(circuit=circuit,*args,**kwargs)
+ width=None# full size
+ height=200
+ if"width"inkwargs:
+ width=kwargs["width"]
+ if"height"inkwargs:
+ height=kwargs["height"]# this is buggy in jupyter and will be ignored
+ image=IPython.display.Image(filename=kwargs["filename"],height=height,width=width)
+ IPython.display.display(image)
+
+ exceptImportErrorasE:
+ raiseException("Original Error Message:{}\nYou are missing dependencies for drawing: You need IPython, qpic and pdfatex.\n".format(E))
+ else:
+ compiled=compile_circuit(abstract_circuit=objective,backend=backend)
+ ifbackend=="qiskit":
+ returncompiled.circuit.draw(*args,**kwargs)
+ else:
+ print(compiled.circuit)
+ return""
+
+
+
+[docs]
+defcompile(objective:typing.Union['Objective','QCircuit','QTensor'],
+ variables:Dict[Union['Variable',Hashable],RealNumber]=None,
+ samples:int=None,
+ backend:str=None,
+ noise:NoiseModel=None,
+ device:str=None,
+ *args,
+ **kwargs)->typing.Union['BackendCircuit','Objective']:
+"""Compile a tequila objective or circuit to a backend
+
+ Parameters
+ ----------
+ objective: Objective:
+ tequila objective or circuit
+ variables: dict, optional:
+ The variables of the objective given as dictionary
+ with keys as tequila Variables and values the corresponding real numbers
+ samples: int, optional:
+ if None a full wavefunction simulation is performed, otherwise a fixed number of samples is simulated
+ backend : str, optional:
+ specify the backend or give None for automatic assignment
+ noise: NoiseModel, optional:
+ the noise model to apply to the objective or QCircuit.
+ device: optional:
+ a device on which (or in emulation of which) to sample the circuit.
+ Returns
+ -------
+ simulators.BackendCircuit or Objective
+ the compiled object.
+
+ """
+
+ backend=pick_backend(backend=backend,noise=noise,samples=samples,device=device)
+
+ ifvariablesisnotNone:
+ # allow hashable types as keys without casting it to variables
+ variables={assign_variable(k):vfork,vinvariables.items()}
+
+ ifisinstance(objective,QTensor):
+ ff=numpy.vectorize(compile_objective)
+ returnff(objective=objective,samples=samples,variables=variables,backend=backend,noise=noise,device=device,*args,**kwargs)
+
+ ifisinstance(objective,Objective)orhasattr(objective,"args"):
+ returncompile_objective(objective=objective,samples=samples,variables=variables,backend=backend,noise=noise,device=device,*args,**kwargs)
+ elifhasattr(objective,"gates")orhasattr(objective,"abstract_circuit"):
+ returncompile_circuit(abstract_circuit=objective,variables=variables,backend=backend,samples=samples,
+ noise=noise,device=device,*args,**kwargs)
+ else:
+ raiseTequilaException(
+ "Don't know how to compile object of type: {type}, \n{object}".format(type=type(objective),
+ object=objective))
+
+
+
+
+[docs]
+defcompile_to_function(objective:typing.Union['Objective','QCircuit'],*args,
+ **kwargs)->typing.Union['BackendCircuit','Objective']:
+"""
+ Notes
+ ----------
+ Same as compile but gives back callable wrapper
+ where parameters are passed down as arguments instead of dictionaries
+ the order of those arguments is the order of the parameter dictionary
+ given here. If not given it is the order returned by objective.extract_variables()
+
+ See compile for more information on the parameters of this function
+
+ Returns
+ -------
+ BackendCircuit or Objective:
+ wrapper over a compiled objective/circuit
+ can be called like: function(0.0,1.0,...,samples=None)
+ """
+
+ compiled_objective=compile(objective,*args,**kwargs)
+ if'variables'inkwargs:
+ varnames=list(kwargs['variables'].keys())
+ else:
+ varnames=objective.extract_variables()
+
+ defobjective_function(*fargs,**fkwargs):
+ iflen(fargs)!=len(varnames):
+ raiseException("Compiled function takes {} variables. You passed down {} arguments."
+ "Use keywords for samples and other instructions\n"
+ "like function(a,b,c, samples=10)".format(len(varnames),len(fargs)))
+ vars={varnames[i]:fargs[i]fori,vinenumerate(fargs)}
+ returncompiled_objective(variables=vars,**fkwargs)
+
+ returnobjective_function
Source code for tequila_code.simulators.simulator_base
+fromtequila.utilsimportTequilaException,to_float,TequilaWarning
+fromtequila.circuit.circuitimportQCircuit
+fromtequila.utils.keymapimportKeyMapSubregisterToRegister
+fromtequila.utils.miscimportto_float
+fromtequila.wavefunction.qubit_wavefunctionimportQubitWaveFunction
+fromtequila.circuit.compilerimportchange_basis
+fromtequilaimportBitString
+fromtequila.objective.objectiveimportVariable,format_variable_dictionary
+fromtequila.circuitimportcompiler
+
+importnumbers,typing,numpy,copy,warnings
+
+fromdataclassesimportdataclass
+
+"""
+Todo: Classes are now immutable:
+ - Add additional features from Skylars project
+ - Maybe only keep paulistrings and not full hamiltonian types
+"""
+
+
+
+[docs]
+classBackendCircuit():
+"""
+ Base class for circuits compiled to run on specific backends.
+
+ Attributes
+ ----------
+ no_translation:
+ set this attribute in the derived __init__ to prevent translation of abstract_circuits
+ needed for simulators that use native tequila types.
+ Default is false
+ abstract_circuit:
+ the tequila circuit from which the backend circuit is built.
+ circuit:
+ the compiled circuit in the backend language.
+ compiler_arguments:
+ dictionary of arguments for compilation needed for chosen backend. Overwritten by inheritors.
+ device:
+ instantiated device (or None) for executing circuits.
+ n_qubits:
+ the number of qubits this circuit operates on.
+ noise:
+ the NoiseModel applied to this circuit when sampled.
+ qubit_map:
+ the mapping from tequila qubits to the qubits of the backend circuit.
+ Dicationary with keys being integers that enumerate the abstract qubits of the abstract_circuit
+ and values being data-structures holding `number` and `instance` where number enumerates the
+ backend qubits and instance is the instance of a backend qubit
+ qubits:
+ a list of the qubits operated on by the circuit.
+
+ Methods
+ -------
+ create_circuit
+ generate a backend circuit from an abstract tequila circuit
+ check_device:
+ see if a given device is valid for the backend.
+ retrieve_device:
+ get an instance of or necessary informaton about a device, for emulation or use.
+ add_parametrized_gate
+ add a parametrized gate to a backend circuit.
+ add_basic_gate
+ add an unparametrized gate to a backend circuit.
+ add_measurement
+ add a measurement gate to a backend circuit.
+ initialize_circuit:
+ generate an empty circuit object for the backend.
+ update_variables:
+ overwrite the saved values of variables for backend execution.
+ simulate:
+ perform simulation, simulated sampling, or execute the circuit, e.g. with some hamiltonian for measurement.
+ sample_paulistring:
+ sample a circuit with one paulistring of a larger hamiltonian
+ sample:
+ sample a circuit, measuring an entire hamiltonian.
+ do_sample:
+ subroutine for sampling. must be overwritten by inheritors.
+ do_simulate:
+ subroutine for wavefunction simulation. must be overwritten by inheritors.
+ convert_measurements:
+ transform the result of simulation from the backend return type.
+ make_qubit_map:
+ create a dictionary to map the tequila qubit ordering to the backend qubits.
+ optimize_circuit:
+ use backend features to improve circuit depth.
+ extract_variables:
+ return a list of the variables in the abstract tequila circuit this backend circuit corresponds to.
+ """
+
+ # compiler instructions, override in backends
+ # try to reduce True statements as much as possible for new backends
+ compiler_arguments={
+ "trotterized":True,
+ "swap":True,
+ "multitarget":True,
+ "controlled_rotation":True,
+ "generalized_rotation":True,
+ "exponential_pauli":True,
+ "controlled_exponential_pauli":True,
+ "phase":True,
+ "power":True,
+ "hadamard_power":True,
+ "controlled_power":True,
+ "controlled_phase":True,
+ "toffoli":True,
+ "phase_to_z":True,
+ "cc_max":True
+ }
+
+ @property
+ defn_qubits(self)->numbers.Integral:
+ returnlen(self.qubit_map)
+
+ @property
+ defabstract_qubits(self)->typing.Iterable[numbers.Integral]:
+ returntuple(list(self.qubit_map.keys()))
+
+
+[docs]
+ defqubit(self,abstract_qubit):
+"""
+ Convenience. Gives back a qubit instance of the corresponding backend
+ Parameters
+ ----------
+ abstract_qubit
+ the abstract tequila qubit
+
+ Returns
+ -------
+ instance of backend qubit
+ """
+ returnself.qubit_map[abstract_qubit].instance
+
+
+ def__init__(self,abstract_circuit:QCircuit,variables,noise=None,device=None,
+ qubit_map=None,optimize_circuit=True,*args,**kwargs):
+"""
+
+ Parameters
+ ----------
+ abstract_circuit: QCircuit:
+ the circuit which is to be rendered in the backend language.
+ variables:
+ values for the variables of abstract_circuit
+ noise: optional:
+ noise to apply to abstract circuit.
+ device: optional:
+ device on which to sample (or emulate sampling) abstract circuit.
+ qubit_map: dictionary:
+ a qubit map which maps the abstract qubits in the abstract_circuit to the qubits on the backend
+ there is no need to initialize the corresponding backend types
+ the dictionary should simply be {int:int} (preferred) or {int:name}
+ if None the default will map to qubits 0 ... n_qubits -1 in the backend
+ optimize_circuit: bool:
+ whether or not to attempt backend depth optimization. Defaults to true.
+ args
+ kwargs
+ """
+
+ self._input_args={"abstract_circuit":abstract_circuit,"variables":variables,"noise":noise,
+ "qubit_map":qubit_map,"optimize_circuits":optimize_circuit,"device":device,**kwargs}
+
+ self.no_translation=False
+ self._variables=tuple(abstract_circuit.extract_variables())
+
+ compiler_arguments=self.compiler_arguments
+ ifnoiseisnotNone:
+ compiler_arguments["cc_max"]=True
+ compiler_arguments["controlled_phase"]=True
+ compiler_arguments["controlled_rotation"]=True
+ compiler_arguments["hadamard_power"]=True
+
+ # compile the abstract_circuit
+ c=compiler.CircuitCompiler(**compiler_arguments)
+
+ ifqubit_mapisNone:
+ qubit_map={q:ifori,qinenumerate(abstract_circuit.qubits)}
+ elifnotqubit_map=={q:ifori,qinenumerate(abstract_circuit.qubits)}:
+ warnings.warn("reveived custom qubit_map = {}\n"
+ "This is not fully integrated and might result in unexpected behaviour!"
+ .format(qubit_map),TequilaWarning)
+
+ iflen(qubit_map)>abstract_circuit.max_qubit()+1:
+ raiseTequilaException("Custom qubit_map has too many qubits {} vs {}".format(len(qubit_map),abstract_circuit.max_qubit()+1))
+ ifmax(qubit_map.keys())>abstract_circuit.max_qubit():
+ raiseTequilaException("Custom qubit_map tries to assign qubit {} but we only have {}".format(max(qubit_map.keys()),abstract_circuit.max_qubit()))
+
+ # qubit map is initialized to have BackendQubits as values (they carry number and instance attributes)
+ self.qubit_map=self.make_qubit_map(qubit_map)
+
+ # pre-compilation (still an abstract ciruit, but with gates decomposed depending on backend requirements)
+ compiled=c(abstract_circuit)
+ self.abstract_circuit=compiled
+
+ self.noise=noise
+ self.check_device(device)
+ self.device=self.retrieve_device(device)
+
+ # translate into the backend object
+ self.circuit=self.create_circuit(abstract_circuit=compiled,variables=variables)
+
+ ifoptimize_circuitandnoiseisNone:
+ self.circuit=self.optimize_circuit(circuit=self.circuit)
+
+ def__call__(self,
+ variables:typing.Dict[Variable,numbers.Real]=None,
+ samples:int=None,
+ *args,
+ **kwargs):
+"""
+ Simulate or sample the backend circuit.
+
+ Parameters
+ ----------
+ variables: dict:
+ dictionary assigning values to the variables of the circuit.
+ samples: int, optional:
+ how many shots to sample with. If None, perform full wavefunction simulation.
+ args
+ kwargs
+
+ Returns
+ -------
+ Float:
+ the result of simulating or sampling the circuit.
+ """
+
+ variables=format_variable_dictionary(variables=variables)
+ ifself._variablesisnotNoneandlen(self._variables)>0:
+ ifvariablesisNoneorset(self._variables)>set(variables.keys()):
+ raiseTequilaException(
+ "BackendCircuit received not all variables. Circuit depends on variables {}, you gave {}".format(
+ self._variables,variables))
+
+ self.update_variables(variables)
+ ifsamplesisNone:
+ returnself.simulate(variables=variables,noise=self.noise,*args,**kwargs)
+ else:
+ returnself.sample(variables=variables,samples=samples,noise=self.noise,*args,**kwargs)
+
+
+[docs]
+ defcreate_circuit(self,abstract_circuit:QCircuit,circuit=None,*args,**kwargs):
+"""
+ build the backend specific circuit from the abstract tequila circuit.
+
+ Parameters
+ ----------
+ abstract_circuit: QCircuit:
+ the circuit to build in the backend
+ circuit: BackendCircuitType (optional):
+ Add to this already initialized circuit (not all backends support + operation)
+ args
+ kwargs
+
+ Returns
+ -------
+ type varies
+ The circuit, compiled to the backend.
+ """
+
+ # Backend uses native tequila structures
+ ifself.no_translation:
+ returnabstract_circuit
+
+ result=circuit
+ ifresultisNone:
+ result=self.initialize_circuit(*args,**kwargs)
+
+ forginabstract_circuit.gates:
+ ifg.is_parametrized():
+ self.add_parametrized_gate(g,result,*args,**kwargs)
+ else:
+ self.add_basic_gate(g,result,*args,**kwargs)
+
+ returnresult
+
+
+
+[docs]
+ defcheck_device(self,device):
+"""
+ Verify if a device can be used in the selected backend. Overwritten by inheritors.
+ Parameters
+ ----------
+ device:
+ the device to verify.
+
+ Returns
+ -------
+
+ Raises
+ ------
+ TequilaException
+ """
+ ifdeviceisnotNone:
+ raiseTequilaException('Devices not enabled for {}'.format(str(type(self))))
+
+
+
+[docs]
+ defretrieve_device(self,device):
+"""
+ get the instantiated backend device object, from user provided object (e.g, a string).
+
+ Must be overwritten by inheritors, to use devices.
+
+ Parameters
+ ----------
+ device:
+ object which points to the device in question, to be returned.
+
+ Returns
+ -------
+ Type:
+ varies by backend.
+ """
+ ifdeviceisNone:
+ returndevice
+ else:
+ raiseTequilaException('Devices not enabled for {}'.format(str(type(self))))
+
+
+
+[docs]
+ defadd_parametrized_gate(self,gate,circuit,*args,**kwargs):
+ raiseTequilaException("Backend Handler needs to be overwritten for supported simulators")
+
+
+
+[docs]
+ defadd_basic_gate(self,gate,circuit,*args,**kwargs):
+ raiseTequilaException("Backend Handler needs to be overwritten for supported simulators")
+
+
+
+[docs]
+ defadd_measurement(self,circuit,target_qubits,*args,**kwargs):
+ raiseTequilaException("Backend Handler needs to be overwritten for supported simulators")
+
+
+
+[docs]
+ definitialize_circuit(self,*args,**kwargs):
+ raiseTequilaException("Backend Handler needs to be overwritten for supported simulators")
+
+
+
+[docs]
+ defupdate_variables(self,variables):
+"""
+ This is the default, which just translates the circuit again.
+ Overwrite in inheritors if parametrized circuits are supported.
+ """
+ self.circuit=self.create_circuit(abstract_circuit=self.abstract_circuit,variables=variables)
+
+
+
+[docs]
+ defsimulate(self,variables,initial_state=0,*args,**kwargs)->QubitWaveFunction:
+"""
+ simulate the circuit via the backend.
+
+ Parameters
+ ----------
+ variables:
+ the parameters with which to simulate the circuit.
+ initial_state: Default = 0:
+ one of several types; determines the base state onto which the circuit is applied.
+ Default: the circuit is applied to the all-zero state.
+ args
+ kwargs
+
+ Returns
+ -------
+ QubitWaveFunction:
+ the wavefunction of the system produced by the action of the circuit on the initial state.
+
+ """
+ self.update_variables(variables)
+ ifisinstance(initial_state,BitString):
+ initial_state=initial_state.integer
+ ifisinstance(initial_state,QubitWaveFunction):
+ iflen(initial_state.keys())!=1:
+ raiseTequilaException("only product states as initial states accepted")
+ initial_state=list(initial_state.keys())[0].integer
+
+ all_qubits=list(range(self.abstract_circuit.n_qubits))
+ active_qubits=self.qubit_map.keys()
+
+ # Keymap is only necessary if not all qubits are active
+ keymap_required=sorted(active_qubits)!=all_qubits
+
+ ifkeymap_required:
+ # maps from reduced register to full register
+ keymap=KeyMapSubregisterToRegister(subregister=active_qubits,register=all_qubits)
+
+ mapped_initial_state=keymap.inverted(initial_state).integerifkeymap_requiredelseint(initial_state)
+ result=self.do_simulate(variables=variables,initial_state=mapped_initial_state,*args,
+ **kwargs)
+
+ ifkeymap_required:
+ result.apply_keymap(keymap=keymap,initial_state=initial_state)
+
+ returnresult
+
+
+
+[docs]
+ defsample(self,variables,samples,read_out_qubits=None,circuit=None,*args,**kwargs):
+"""
+ Sample the circuit. If circuit natively equips paulistrings, sample therefrom.
+ Parameters
+ ----------
+ variables:
+ the variables with which to sample the circuit.
+ samples: int:
+ the number of samples to take.
+ read_out_qubits: int:
+ target qubits to measure (default is all)
+ args
+ kwargs
+
+ Returns
+ -------
+ QubitWaveFunction
+ The result of sampling, a recreated QubitWaveFunction in the sampled basis.
+
+ """
+ self.update_variables(variables)
+ ifread_out_qubitsisNone:
+ read_out_qubits=self.abstract_qubits
+
+ iflen(read_out_qubits)==0:
+ raiseException("read_out_qubits are empty")
+
+ ifcircuitisNone:
+ circuit=self.add_measurement(circuit=self.circuit,target_qubits=read_out_qubits)
+ else:
+ circuit=self.add_measurement(circuit=circuit,target_qubits=read_out_qubits)
+ returnself.do_sample(samples=samples,circuit=circuit,read_out_qubits=read_out_qubits,*args,**kwargs)
+
+
+
+[docs]
+ defsample_all_z_hamiltonian(self,samples:int,hamiltonian,variables,*args,**kwargs):
+"""
+ Sample from a Hamiltonian which only consists of Pauli-Z and unit operators
+ Parameters
+ ----------
+ samples
+ number of samples to take
+ hamiltonian
+ the tequila hamiltonian
+ args
+ arguments for do_sample
+ kwargs
+ keyword arguments for do_sample
+ Returns
+ -------
+ samples, evaluated and summed Hamiltonian expectationvalue
+ """
+ # make measurement instruction (measure all qubits in the Hamiltonian that are also in the circuit)
+ abstract_qubits_H=hamiltonian.qubits
+ assertlen(abstract_qubits_H)!=0# this case should be filtered out before
+ # assert that the Hamiltonian was mapped before
+ ifnotall(qinself.qubit_map.keys()forqinabstract_qubits_H):
+ raiseTequilaException(
+ "Qubits in {}-qubit Hamiltonian were not traced out for {}-qubit circuit".format(hamiltonian.n_qubits,
+ self.n_qubits))
+
+ # run simulators
+ counts=self.sample(samples=samples,read_out_qubits=abstract_qubits_H,variables=variables,*args,**kwargs)
+ read_out_map={q:ifori,qinenumerate(abstract_qubits_H)}
+
+ # compute energy
+ E=0.0
+ forpaulistringinhamiltonian.paulistrings:
+ n_samples=0
+ Etmp=0.0
+ forkey,countincounts.items():
+ # get all the non-trivial qubits of the current PauliString (meaning all Z operators)
+ # and mapp them to the backend qubits
+ mapped_ps_support=[read_out_map[i]foriinpaulistring._data.keys()]
+ # count all measurements that resulted in |1> for those qubits
+ parity=[kfori,kinenumerate(key.array)ifiinmapped_ps_support].count(1)
+ # evaluate the PauliString
+ sign=(-1)**parity
+ Etmp+=sign*count
+ n_samples+=count
+ E+=(Etmp/samples)*paulistring.coeff
+ # small failsafe
+ assertn_samples==samples
+ returnE
+
+
+
+[docs]
+ defsample_paulistring(self,samples:int,paulistring,variables,*args,
+ **kwargs)->numbers.Real:
+"""
+ Sample an individual pauli word (pauli string) and return the average result thereof.
+ Parameters
+ ----------
+ samples: int:
+ how many samples to evaluate.
+ paulistring:
+ the paulistring to be sampled.
+ args
+ kwargs
+
+ Returns
+ -------
+ float:
+ the average result of sampling the chosen paulistring
+ """
+
+ not_in_u=[qforqinpaulistring.qubitsifqnotinself.abstract_qubits]
+ reduced_ps=paulistring.trace_out_qubits(qubits=not_in_u)
+ ifreduced_ps.coeff==0.0:
+ return0.0
+ iflen(reduced_ps._data.keys())==0:
+ returnreduced_ps.coeff
+
+ # make basis change and translate to backend
+ basis_change=QCircuit()
+ qubits=[]
+ foridx,pinreduced_ps.items():
+ qubits.append(idx)
+ basis_change+=change_basis(target=idx,axis=p)
+
+ # add basis change to the circuit
+ # deepcopy is necessary to avoid changing the circuits
+ # can be circumvented by optimizing the measurements
+ # on construction: tq.ExpectationValue(H=H, U=U, optimize_measurements=True)
+ circuit=self.create_circuit(circuit=copy.deepcopy(self.circuit),abstract_circuit=basis_change)
+ # run simulators
+ counts=self.sample(samples=samples,circuit=circuit,read_out_qubits=qubits,variables=variables,*args,
+ **kwargs)
+ # compute energy
+ E=0.0
+ n_samples=0
+ forkey,countincounts.items():
+ parity=key.array.count(1)
+ sign=(-1)**parity
+ E+=sign*count
+ n_samples+=count
+ assertn_samples==samples
+ E=E/samples*paulistring.coeff
+ returnE
+
+
+
+[docs]
+ defdo_sample(self,samples,circuit,noise,abstract_qubits=None,*args,**kwargs)->QubitWaveFunction:
+"""
+ helper function for sampling. MUST be overwritten by inheritors.
+
+ Parameters
+ ----------
+ samples: int:
+ the number of samples to take
+ circuit:
+ the circuit to sample from.
+ Note:
+ Not necessarily self.circuit!
+ noise:
+ the noise to apply to the sampled circuit.
+ abstract_qubits:
+ specify which qubits to measure. Default is all
+ args
+ kwargs
+
+ Returns
+ -------
+ QubitWaveFunction:
+ the result of sampling.
+
+ """
+ raiseTequilaException("Backend Handler needs to be overwritten for supported simulators")
+
+
+
+[docs]
+ defdo_simulate(self,variables,initial_state,*args,**kwargs)->QubitWaveFunction:
+"""
+ helper for simulation. MUST be overwritten by inheritors.
+
+ Parameters
+ ----------
+ variables:
+ the variables with which the circuit may be simulated.
+ initial_state:
+ the initial state in which the system is in prior to the application of the circuit.
+ args
+ kwargs
+
+ Returns
+ -------
+ QubitWaveFunction
+ the result of simulating the circuit.
+
+ """
+ raiseTequilaException("Backend Handler needs to be overwritten for supported simulators")
+
+
+
+[docs]
+ defconvert_measurements(self,backend_result)->QubitWaveFunction:
+ raiseTequilaException("Backend Handler needs to be overwritten for supported simulators")
+
+
+
+[docs]
+ definitialize_qubit(self,number:int):
+"""
+
+ In case the backend has its own Qubit Types,
+ this function should be overwritten by inheritors.
+
+ Parameters
+ ----------
+ number
+ the qubit number
+
+ Returns
+ -------
+ Initialized backend qubit type
+
+ """
+ returnnumber
+
+
+
+[docs]
+ defmake_qubit_map(self,qubits:dict):
+"""
+ Build the mapping between abstract qubits.
+
+ Must be overwritten by inheritors to do anything other than check the validity of the map.
+ Parameters
+ ----------
+ qubits:
+ the qubits to map onto.
+ If given as a dictionary, the map is already defined
+ If given as a list the map will be those qubits mapped to 0 .... n_qubit-1 of the backend
+ Returns
+ -------
+ Dict
+ the dictionary that maps the qubits of the abstract circuits to an ordered sequence of integers.
+ keys are the abstract qubit integers
+ values are the backend qubits
+ those are data structures which contain name and instance
+ where number is the qubit identifier and instance the instance of the backend qubit
+ if the backend does not require a special object for qubits the instance should be the same as number
+ """
+
+ @dataclass
+ classBackendQubit:
+ number:int=None
+ instance:object=None
+
+ ifqubitsisNone:
+ qubits=range(self.abstract_circuit.n_qubits)
+
+ abstract_map=qubits
+ ifnothasattr(qubits,"keys")ornothasattr(qubits,"values"):
+ abstract_map={q:ifori,qinenumerate(qubits)}
+
+ ifall([hasattr(i,"number")andhasattr(i,"instance")foriinabstract_map.values()]):
+ # qubit_map already initialized backend_types
+ returnqubits
+
+ return{k:BackendQubit(number=v,instance=self.initialize_qubit(v))fork,vinabstract_map.items()}
+
+
+
+[docs]
+ defoptimize_circuit(self,circuit,*args,**kwargs):
+"""
+ Optimize a circuit using backend tools. Should be overwritten by inheritors.
+ Parameters
+ ----------
+ circuit:
+ the circuit to optimize
+ args
+ kwargs
+
+ Returns
+ -------
+ Type
+ Optimized version of the circuit.
+ """
+ returncircuit
+
+
+
+[docs]
+ defextract_variables(self)->typing.Dict[str,numbers.Real]:
+"""
+ extract the tequila variables from the circuit.
+ Returns
+ -------
+ dict:
+ the variables of the circuit.
+ """
+ result=self.abstract_circuit.extract_variables()
+ returnresult
+[docs]
+classBackendExpectationValue:
+"""
+ Class representing an ExpectationValue for evaluation by some backend.
+
+ Attributes
+ ----------
+ H:
+ the reduced tequila Hamiltonian(s) of the expectationvalue
+ reduction procedure is tracing out all qubits that are not part of the unitary U
+ stored as a tuple to evaluate multiple Hamiltonians over the same circuit faster in pure simulations
+ abstract_H:
+ the original (non-reduced) Hamiltonian(s)
+ n_qubits:
+ how many qubits appear in the expectationvalue.
+ U:
+ the underlying BackendCircuit of the expectationvalue.
+
+ Methods
+ -------
+ extract_variables:
+ return the underlying tequila variables of the circuit
+ initialize_hamiltonian
+ prepare the hamiltonian for iteration over as a tuple
+ initialize_unitary
+ compile the abstract circuit to a backend circuit.
+ simulate:
+ simulate the unitary to measure H
+ sample:
+ sample the unitary to measure H
+ sample_paulistring
+ sample a single term from H
+ update_variables
+ wrapper over the update_variables of BackendCircuit.
+
+ """
+ BackendCircuitType=BackendCircuit
+
+ # map to smaller subsystem if there are qubits which are not touched by the circuits,
+ # should be deactivated if expectationvalues are computed by the backend since the hamiltonians are currently not mapped
+ use_mapping=True
+
+ @property
+ defn_qubits(self):
+ returnself.U.n_qubits
+
+ @property
+ defH(self):
+ returnself._H
+
+ @property
+ defU(self):
+ returnself._U
+
+
+[docs]
+ defupdate_variables(self,variables):
+"""wrapper over circuit update_variables"""
+ self._U.update_variables(variables=variables)
+
+
+
+[docs]
+ defsample(self,variables,samples,*args,**kwargs)->numpy.array:
+"""
+ sample the expectationvalue.
+
+ Parameters
+ ----------
+ variables: dict:
+ variables to supply to the unitary.
+ samples: int:
+ number of samples to perform.
+ args
+ kwargs
+
+ Returns
+ -------
+ numpy.ndarray:
+ a numpy array, the result of sampling.
+ """
+
+ suggested=None
+ ifhasattr(samples,"lower")andsamples.lower()[:4]=="auto":
+ ifself.abstract_expectationvalue.samplesisNone:
+ raiseTequilaException("samples='auto' requested but no samples where set in individual expectation values")
+ total_samples=int(samples[5:])
+ samples=max(1,int(self.abstract_expectationvalue.samples*total_samples))
+ suggested=samples
+ # samples are not necessarily set (either the user has to set it or some functions like optimize_measurements)
+
+ ifsuggestedisnotNoneandsuggested!=samples:
+ warnings.warn("simulating with samples={}, but expectationvalue carries suggested samples={}\nTry calling with samples='auto-total#ofsamples'".format(samples,suggested),TequilaWarning)
+
+ self.update_variables(variables)
+
+ result=[]
+ forHinself._reduced_hamiltonians:
+ E=0.0
+ iflen(H.qubits)==0:
+ E=sum([ps.coeffforpsinH.paulistrings])
+ elifH.is_all_z():
+ E=self.U.sample_all_z_hamiltonian(samples=samples,hamiltonian=H,variables=variables,*args,
+ **kwargs)
+ else:
+ forpsinH.paulistrings:
+ E+=self.U.sample_paulistring(samples=samples,paulistring=ps,variables=variables,*args,
+ **kwargs)
+ result.append(to_float(E))
+ returnnumpy.asarray(result)
+
+
+
+[docs]
+ defsimulate(self,variables,*args,**kwargs):
+"""
+ Simulate the expectationvalue.
+
+ Parameters
+ ----------
+ variables:
+ variables to supply to the unitary.
+ args
+ kwargs
+
+ Returns
+ -------
+ numpy array:
+ the result of simulation.
+ """
+ self.update_variables(variables)
+ result=[]
+ forHinself.H:
+ final_E=0.0
+ # TODO inefficient,
+ # Always better to overwrite this function
+ wfn=self.U.simulate(variables=variables,*args,**kwargs)
+ final_E+=wfn.compute_expectationvalue(operator=H)
+ result.append(to_float(final_E))
+ returnnumpy.asarray(result)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/simulators/simulator_qulacs.html b/docs/sphinx/_modules/tequila_code/simulators/simulator_qulacs.html
new file mode 100644
index 0000000..4fc5846
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/simulators/simulator_qulacs.html
@@ -0,0 +1,704 @@
+
+
+
+
+
+
+
+ tequila_code.simulators.simulator_qulacs — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source code for tequila_code.simulators.simulator_qulacs
+importqulacs
+importnumbers,numpy
+importwarnings
+
+fromtequilaimportTequilaException,TequilaWarning
+fromtequila.utils.bitstringsimportBitNumbering,BitString,BitStringLSB
+fromtequila.wavefunction.qubit_wavefunctionimportQubitWaveFunction
+fromtequila.simulators.simulator_baseimportBackendCircuit,BackendExpectationValue,QCircuit,change_basis
+fromtequila.utils.keymapimportKeyMapRegisterToSubregister
+
+"""
+Developer Note:
+ Qulacs uses different Rotational Gate conventions: Rx(angle) = exp(i angle/2 X) instead of exp(-i angle/2 X)
+ And the same for MultiPauli rotational gates
+ The angles are scaled with -1.0 to keep things consistent with the rest of tequila
+"""
+
+
+[docs]
+classTequilaQulacsException(TequilaException):
+ def__str__(self):
+ return"Error in qulacs backend:"+self.message
+
+
+
+[docs]
+classBackendCircuitQulacs(BackendCircuit):
+"""
+ Class representing circuits compiled to qulacs.
+ See BackendCircuit for documentation of features and methods inherited therefrom
+
+ Attributes
+ ----------
+ counter:
+ counts how many distinct sympy.Symbol objects are employed in the circuit.
+ has_noise:
+ whether or not the circuit is noisy. needed by the expectationvalue to do sampling properly.
+ noise_lookup: dict:
+ dict mapping strings to lists of constructors for cirq noise channel objects.
+ op_lookup: dict:
+ dictionary mapping strings (tequila gate names) to cirq.ops objects.
+ variables: list:
+ a list of the qulacs variables of the circuit.
+
+ Methods
+ -------
+ add_noise_to_circuit:
+ apply a tequila NoiseModel to a qulacs circuit, by translating the NoiseModel's instructions into noise gates.
+ """
+
+ compiler_arguments={
+ "trotterized":True,
+ "swap":False,
+ "multitarget":True,
+ "controlled_rotation":True,# needed for gates depending on variables
+ "generalized_rotation":True,
+ "exponential_pauli":False,
+ "controlled_exponential_pauli":True,
+ "phase":True,
+ "power":True,
+ "hadamard_power":True,
+ "controlled_power":True,
+ "controlled_phase":True,
+ "toffoli":False,
+ "phase_to_z":True,
+ "cc_max":False
+ }
+
+ numbering=BitNumbering.LSB
+
+ def__init__(self,abstract_circuit,noise=None,*args,**kwargs):
+"""
+
+ Parameters
+ ----------
+ abstract_circuit: QCircuit:
+ the circuit to compile to qulacs
+ noise: optional:
+ noise to apply to the circuit.
+ args
+ kwargs
+ """
+ self.op_lookup={
+ 'I':qulacs.gate.Identity,
+ 'X':qulacs.gate.X,
+ 'Y':qulacs.gate.Y,
+ 'Z':qulacs.gate.Z,
+ 'H':qulacs.gate.H,
+ 'Rx':(lambdac:c.add_parametric_RX_gate,qulacs.gate.RX),
+ 'Ry':(lambdac:c.add_parametric_RY_gate,qulacs.gate.RY),
+ 'Rz':(lambdac:c.add_parametric_RZ_gate,qulacs.gate.RZ),
+ 'SWAP':qulacs.gate.SWAP,
+ 'Measure':qulacs.gate.Measurement,
+ 'Exp-Pauli':None
+ }
+ self.measurements=None
+ self.variables=[]
+ super().__init__(abstract_circuit=abstract_circuit,noise=noise,*args,**kwargs)
+ self.has_noise=False
+ ifnoiseisnotNone:
+
+ warnings.warn("Warning: noise in qulacs module will be dropped. Currently only works for qulacs version 0.5 or lower",TequilaWarning)
+
+ self.has_noise=True
+ self.noise_lookup={
+ 'bit flip':[qulacs.gate.BitFlipNoise],
+ 'phase flip':[lambdatarget,prob:qulacs.gate.Probabilistic([prob],[qulacs.gate.Z(target)])],
+ 'phase damp':[lambdatarget,prob:qulacs.gate.DephasingNoise(target,(1/2)*(1-numpy.sqrt(1-prob)))],
+ 'amplitude damp':[qulacs.gate.AmplitudeDampingNoise],
+ 'phase-amplitude damp':[qulacs.gate.AmplitudeDampingNoise,
+ lambdatarget,prob:qulacs.gate.DephasingNoise(target,(1/2)*(1-numpy.sqrt(1-prob)))
+ ],
+ 'depolarizing':[lambdatarget,prob:qulacs.gate.DepolarizingNoise(target,3*prob/4)]
+ }
+
+ self.circuit=self.add_noise_to_circuit(noise)
+
+
+[docs]
+ defupdate_variables(self,variables):
+"""
+ set new variable values for the circuit.
+ Parameters
+ ----------
+ variables: dict:
+ the variables to supply to the circuit.
+
+ Returns
+ -------
+ None
+ """
+ fork,angleinenumerate(self.variables):
+ self.circuit.set_parameter(k,angle(variables))
+
+
+
+[docs]
+ defdo_simulate(self,variables,initial_state,*args,**kwargs):
+"""
+ Helper function to perform simulation.
+
+ Parameters
+ ----------
+ variables: dict:
+ variables to supply to the circuit.
+ initial_state:
+ information indicating the initial state on which the circuit should act.
+ args
+ kwargs
+
+ Returns
+ -------
+ QubitWaveFunction:
+ QubitWaveFunction representing result of the simulation.
+ """
+ state=self.initialize_state(self.n_qubits)
+ lsb=BitStringLSB.from_int(initial_state,nbits=self.n_qubits)
+ state.set_computational_basis(BitString.from_binary(lsb.binary).integer)
+ self.circuit.update_quantum_state(state)
+
+ wfn=QubitWaveFunction.from_array(arr=state.get_vector(),numbering=self.numbering)
+ returnwfn
+
+
+
+[docs]
+ defconvert_measurements(self,backend_result,target_qubits=None)->QubitWaveFunction:
+"""
+ Transform backend evaluation results into QubitWaveFunction
+ Parameters
+ ----------
+ backend_result:
+ the return value of backend simulation.
+
+ Returns
+ -------
+ QubitWaveFunction
+ results transformed to tequila native QubitWaveFunction
+ """
+
+ result=QubitWaveFunction()
+ # todo there are faster ways
+
+
+ forkinbackend_result:
+ converted_key=BitString.from_binary(BitStringLSB.from_int(integer=k,nbits=self.n_qubits).binary)
+ ifconverted_keyinresult._state:
+ result._state[converted_key]+=1
+ else:
+ result._state[converted_key]=1
+
+ iftarget_qubitsisnotNone:
+ mapped_target=[self.qubit_map[q].numberforqintarget_qubits]
+ mapped_full=[self.qubit_map[q].numberforqinself.abstract_qubits]
+ keymap=KeyMapRegisterToSubregister(subregister=mapped_target,register=mapped_full)
+ result=result.apply_keymap(keymap=keymap)
+
+ returnresult
+
+
+
+[docs]
+ defdo_sample(self,samples,circuit,noise_model=None,initial_state=0,*args,**kwargs)->QubitWaveFunction:
+"""
+ Helper function for performing sampling.
+
+ Parameters
+ ----------
+ samples: int:
+ the number of samples to be taken.
+ circuit:
+ the circuit to sample from.
+ noise_model: optional:
+ noise model to be applied to the circuit.
+ initial_state:
+ sampling supports initial states for qulacs. Indicates the initial state to which circuit is applied.
+ args
+ kwargs
+
+ Returns
+ -------
+ QubitWaveFunction:
+ the results of sampling, as a Qubit Wave Function.
+ """
+ state=self.initialize_state(self.n_qubits)
+ lsb=BitStringLSB.from_int(initial_state,nbits=self.n_qubits)
+ state.set_computational_basis(BitString.from_binary(lsb.binary).integer)
+ circuit.update_quantum_state(state)
+ sampled=state.sampling(samples)
+ returnself.convert_measurements(backend_result=sampled,target_qubits=self.measurements)
+
+
+
+[docs]
+ defno_translation(self,abstract_circuit):
+"""
+ Todo: what is this for?
+ Parameters
+ ----------
+ abstract_circuit
+
+ Returns
+ -------
+
+ """
+ returnFalse
+[docs]
+ defadd_exponential_pauli_gate(self,gate,circuit,variables,*args,**kwargs):
+"""
+ Add a native qulacs Exponential Pauli gate to a circuit.
+ Parameters
+ ----------
+ gate: ExpPauliGateImpl:
+ the gate to add
+ circuit:
+ the qulacs circuit, to which the gate is to be added.
+ variables:
+ dict containing values of the parameters appearing in the pauli gate.
+ args
+ kwargs
+
+ Returns
+ -------
+ None
+ """
+ assertnotgate.is_controlled()
+ convert={'x':1,'y':2,'z':3}
+ pind=[convert[x.lower()]forxingate.paulistring.values()]
+ qind=[self.qubit(x)forxingate.paulistring.keys()]
+ iflen(gate.extract_variables())>0:
+ self.variables.append(-gate.parameter*gate.paulistring.coeff)
+ circuit.add_parametric_multi_Pauli_rotation_gate(qind,pind,
+ -gate.parameter(variables)*gate.paulistring.coeff)
+ else:
+ circuit.add_multi_Pauli_rotation_gate(qind,pind,-gate.parameter(variables)*gate.paulistring.coeff)
+
+
+
+[docs]
+ defadd_parametrized_gate(self,gate,circuit,variables,*args,**kwargs):
+"""
+ add a parametrized gate.
+ Parameters
+ ----------
+ gate: QGateImpl:
+ the gate to add to the circuit.
+ circuit:
+ the circuit to which the gate is to be added
+ variables:
+ dict that tells values of variables; needed IFF the gate is an ExpPauli gate.
+ args
+ kwargs
+
+ Returns
+ -------
+ None
+ """
+ op=self.op_lookup[gate.name]
+ ifgate.name=='Exp-Pauli':
+ self.add_exponential_pauli_gate(gate,circuit,variables)
+ return
+ else:
+ iflen(gate.extract_variables())>0:
+ op=op[0]
+ self.variables.append(-gate.parameter)
+ op(circuit)(self.qubit(gate.target[0]),-gate.parameter(variables=variables))
+ ifgate.is_controlled():
+ raiseTequilaQulacsException("Gates which depend on variables can not be controlled! Gate was:\n{}".format(gate))
+ return
+ else:
+ op=op[1]
+ qulacs_gate=op(self.qubit(gate.target[0]),-gate.parameter(variables=variables))
+ ifgate.is_controlled():
+ qulacs_gate=qulacs.gate.to_matrix_gate(qulacs_gate)
+ forcingate.control:
+ qulacs_gate.add_control_qubit(self.qubit(c),1)
+ circuit.add_gate(qulacs_gate)
+
+
+
+[docs]
+ defadd_basic_gate(self,gate,circuit,*args,**kwargs):
+"""
+ add an unparametrized gate to the circuit.
+ Parameters
+ ----------
+ gate: QGateImpl:
+ the gate to be added to the circuit.
+ circuit:
+ the circuit, to which a gate is to be added.
+ args
+ kwargs
+
+ Returns
+ -------
+ None
+ """
+ op=self.op_lookup[gate.name]
+ qulacs_gate=op(*[self.qubit(t)fortingate.target])
+ ifgate.is_controlled():
+ qulacs_gate=qulacs.gate.to_matrix_gate(qulacs_gate)
+ forcingate.control:
+ qulacs_gate.add_control_qubit(self.qubit(c),1)
+
+ circuit.add_gate(qulacs_gate)
+
+
+
+[docs]
+ defadd_measurement(self,circuit,target_qubits,*args,**kwargs):
+"""
+ Add a measurement operation to a circuit.
+ Parameters
+ ----------
+ circuit:
+ a circuit, to which the measurement is to be added.
+ target_qubits: List[int]
+ abstract target qubits
+ args
+ kwargs
+
+ Returns
+ -------
+ None
+ """
+ self.measurements=sorted(target_qubits)
+ returncircuit
+
+
+
+
+[docs]
+ defadd_noise_to_circuit(self,noise_model):
+"""
+ Apply noise from a NoiseModel to a circuit.
+ Parameters
+ ----------
+ noise_model: NoiseModel:
+ the noisemodel to apply to the circuit.
+
+ Returns
+ -------
+ qulacs.ParametrizedQuantumCircuit:
+ self.circuit, with noise added on.
+ """
+ c=self.circuit
+ n=noise_model
+ g_count=c.get_gate_count()
+ new=self.initialize_circuit()
+ foriinrange(g_count):
+ g=c.get_gate(i)
+ new.add_gate(g)
+ qubits=g.get_target_index_list()+g.get_control_index_list()
+ fornoiseinn.noises:
+ iflen(qubits)==noise.level:
+ forj,channelinenumerate(self.noise_lookup[noise.name]):
+ forqinqubits:
+ chan=channel(q,noise.probs[j])
+ new.add_gate(chan)
+ returnnew
+
+
+
+[docs]
+ defoptimize_circuit(self,circuit,max_block_size:int=4,silent:bool=True,*args,**kwargs):
+"""
+ reduce circuit depth using the native qulacs optimizer.
+ Parameters
+ ----------
+ circuit
+ max_block_size: int: Default = 4:
+ the maximum block size for use by the qulacs internal optimizer.
+ silent: bool:
+ whether or not to print the resullt of having optimized.
+ args
+ kwargs
+
+ Returns
+ -------
+ qulacs.QuantumCircuit:
+ optimized qulacs circuit.
+
+ """
+ old=circuit.calculate_depth()
+ opt=qulacs.circuit.QuantumCircuitOptimizer()
+ opt.optimize(circuit,max_block_size)
+ ifnotsilent:
+ print("qulacs: optimized circuit depth from {} to {} with max_block_size {}".format(old,
+ circuit.calculate_depth(),
+ max_block_size))
+ returncircuit
+
+
+
+
+[docs]
+classBackendExpectationValueQulacs(BackendExpectationValue):
+"""
+ Class representing Expectation Values compiled for Qulacs.
+
+ Ovverrides some methods of BackendExpectationValue, which should be seen for details.
+ """
+ use_mapping=True
+ BackendCircuitType=BackendCircuitQulacs
+
+
+[docs]
+ defsimulate(self,variables,*args,**kwargs)->numpy.array:
+"""
+ Perform simulation of this expectationvalue.
+ Parameters
+ ----------
+ variables:
+ variables, to be supplied to the underlying circuit.
+ args
+ kwargs
+
+ Returns
+ -------
+ numpy.array:
+ the result of simulation as an array.
+ """
+ # fast return if possible
+ ifself.HisNone:
+ returnnumpy.asarray([0.0])
+ eliflen(self.H)==0:
+ returnnumpy.asarray([0.0])
+ elifisinstance(self.H,numbers.Number):
+ returnnumpy.asarray[self.H]
+
+ self.U.update_variables(variables)
+ state=self.U.initialize_state(self.n_qubits)
+ self.U.circuit.update_quantum_state(state)
+ result=[]
+ forHinself.H:
+ ifisinstance(H,numbers.Number):
+ result.append(H)# those are accumulated unit strings, e.g 0.1*X(3) in wfn on qubits 0,1
+ else:
+ result.append(H.get_expectation_value(state))
+
+ returnnumpy.asarray(result)
+
+
+
+[docs]
+ definitialize_hamiltonian(self,hamiltonians):
+"""
+ Convert reduced hamiltonians to native Qulacs types for efficient expectation value evaluation.
+ Parameters
+ ----------
+ hamiltonians:
+ an interable set of hamiltonian objects.
+
+ Returns
+ -------
+ list:
+ initialized hamiltonian objects.
+
+ """
+
+ # map the reduced operators to the potentially smaller qubit system
+ qubit_map={}
+ fori,qinenumerate(self.U.abstract_circuit.qubits):
+ qubit_map[q]=i
+
+ result=[]
+ forHinhamiltonians:
+ qulacs_H=qulacs.Observable(self.n_qubits)
+ forpsinH.paulistrings:
+ string=""
+ fork,vinps.items():
+ string+=v.upper()+" "+str(qubit_map[k])
+ qulacs_H.add_operator(ps.coeff,string)
+ result.append(qulacs_H)
+ returnresult
+
+
+
+[docs]
+ defsample(self,variables,samples,*args,**kwargs)->numpy.array:
+"""
+ Sample this Expectation Value.
+ Parameters
+ ----------
+ variables:
+ variables, to supply to the underlying circuit.
+ samples: int:
+ the number of samples to take.
+ args
+ kwargs
+
+ Returns
+ -------
+ numpy.ndarray:
+ the result of sampling as a number.
+ """
+ self.update_variables(variables)
+ state=self.U.initialize_state(self.n_qubits)
+ self.U.circuit.update_quantum_state(state)
+ result=[]
+ forHinself._reduced_hamiltonians:# those are the hamiltonians which where non-used qubits are already traced out
+ E=0.0
+ ifH.is_all_z()andnotself.U.has_noise:
+ E=super().sample(samples=samples,variables=variables,*args,**kwargs)
+ else:
+ forpsinH.paulistrings:
+ # change basis, measurement is destructive so the state will be copied
+ # to avoid recomputation (except when noise was required)
+ bc=QCircuit()
+ foridx,pinps.items():
+ bc+=change_basis(target=idx,axis=p)
+ qbc=self.U.create_circuit(abstract_circuit=bc,variables=None)
+ Esamples=[]
+ forsampleinrange(samples):
+ ifself.U.has_noiseandsample>0:
+ state=self.U.initialize_state(self.n_qubits)
+ self.U.circuit.update_quantum_state(state)
+ state_tmp=state
+ else:
+ state_tmp=state.copy()
+ iflen(bc.gates)>0:# otherwise there is no basis change (empty qulacs circuit does not work out)
+ qbc.update_quantum_state(state_tmp)
+ ps_measure=1.0
+ foridxinps.keys():
+ assertidxinself.U.abstract_qubits# assert that the hamiltonian was really reduced
+ M=qulacs.gate.Measurement(self.U.qubit(idx),self.U.qubit(idx))
+ M.update_quantum_state(state_tmp)
+ measured=state_tmp.get_classical_value(self.U.qubit(idx))
+ ps_measure*=(-2.0*measured+1.0)# 0 becomes 1 and 1 becomes -1
+ Esamples.append(ps_measure)
+ E+=ps.coeff*sum(Esamples)/len(Esamples)
+ result.append(E)
+ returnnumpy.asarray(result)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/_modules/tequila_code/simulators/simulator_qulacs_gpu.html b/docs/sphinx/_modules/tequila_code/simulators/simulator_qulacs_gpu.html
new file mode 100644
index 0000000..6e2e510
--- /dev/null
+++ b/docs/sphinx/_modules/tequila_code/simulators/simulator_qulacs_gpu.html
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+ tequila_code.simulators.simulator_qulacs_gpu — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Divide self into subcircuits representing layers of simultaneous gates. Attempts to minimize gate depth.
+:returns: list of Moment objects.
+:rtype: list
substitute a gate at a given position with other gates.
+:param position: where in the list of gates the gate to be replaced occurs.
+:param gates: the gates to replace the unwanted gate with.
+
+
Returns:
+
self, with unwanted gate removed and new gates inserted. May not be a moment.
Divide self into subcircuits representing layers of simultaneous gates. Attempts to minimize gate depth.
+:returns: list of Moment objects.
+:rtype: list
Replace or insert gates at specific positions into the circuit
+at different positions (faster than multiple calls to replace_gate)
+
+
Parameters:
+
+
positions (list of int:) – the positions at which the gates should be added. Always refer to the positions in the original circuit
+
circuits (list or QCircuit:) – the gates to add at the corresponding positions
+
replace (list of bool: (Default value: None)) – Default is None which corresponds to all true
+decide if gates shall be replaces or if the new parts shall be inserted without replacement
+if replace[i] = true: gate at position [i] will be replaces by gates[i]
+if replace[i] = false: gates[i] circuit will be inserted at position [i] (beaming before gate previously at position [i])
Turn a given quantum circuit from tequila into graph form via NetworkX
+:param self: tq.gates.QCircuit
+:return: G, a graph in NetworkX with qubits as nodes and gate connections as edges
Function that checks which are the active qubits of two circuits and
+provides an unused qubit that is not among them. If all qubits are used
+it adds a new one.
compile a circuit.
+:param abstract_circuit: the circuit to compile.
+:type abstract_circuit: QCircuit
+:param variables: (Default value = None):
+
+
list of the variables whose gates, specifically, must compile.
+Used to prevent excess compilation in gates whose parameters are fixed.
+Default: compile every single gate.
helper function; returns circuit that performs change of basis.
+:param target: the qubit having its basis changed
+:param axis: The axis of rotation to shift into.
+:param daggered: adjusts the sign of the gate if axis = 1, I.E, change of basis about Y axis.
+:type daggered: bool:
+
+
Return type:
+
QCircuit that performs change of basis on target qubit onto desired axis
paulistring (Union[PauliString :) – given as PauliString structure or as string or dict or list
+if given as string: Format should be like X(0)Y(3)Z(2)
+if given as list: Format should be like [(0,’X’),(3,’Y’),(2,’Z’)]
+if given as dict: Format should be like { 0:’X’, 3:’Y’, 2:’Z’ }
+
angle – the angle (will be multiplied by paulistring coefficient if there is one)
A Qubit Excitation, as described under “qubit perspective” in https://doi.org/10.1039/D0SC06627C
+For the Fermionic operators under corresponding Qubit encodings: Use the chemistry interface
+:param angle: the angle of the excitation unitary
+:param target: even number of qubit indices interpreted as [0,1,2,3….] = [(0,1), (2,3), …]
+
+
i.e. as qubit excitations from 0 to 1, 2 to 3, etc
Convenient gate, one of the abstract gates defined by Quantum Experience Standard Header
+The most general single-qubit gate.
+Uses a pair of pi/2-pulses.
wrapper function for getting the gradients of Objectives,ExpectationValues, Unitaries (including single gates), and Transforms.
+:param obj (QCircuit,ParametrizedGateImpl,Objective,ExpectationValue,Transform,Variable): structure to be differentiated
+:param variables (list of Variable): parameter with respect to which obj should be differentiated.
+
+
default None: total gradient.
+
+
return: dictionary of Objectives, if called on gate, circuit, exp.value, or objective; if Variable or Transform, returns number.
Returns a NoiseModel with one QuantumNoise, having a kraus map corresponding to equal
+probabilities of each of the three pauli matrices being applied.
+
+
Parameters:
+
+
p (float:) – the probability with which the noise is applied.
+
level (int:) – the # of qubits in operations to apply this noise to.
Returns a NoiseModel of one QuantumNoise, having a kraus map corresponding to phase damping;
+Krauss map is defined following Nielsen and Chuang;
+E_0= [[1,0],
+
+
[0,sqrt(1-p)]]
+
+
+
E_1= [[0,0],
[0,sqrt(p)]]
+
+
+
+
Parameters:
+
+
p (float:) – the probability with which the noise is applied.
+
level (int:) – the # of qubits in operations to apply this noise to.
OPENQASM version 2.0 specification from:
+A. W. Cross, L. S. Bishop, J. A. Smolin, and J. M. Gambetta, e-print arXiv:1707.03429v2 [quant-ph] (2017).
+https://arxiv.org/pdf/1707.03429v2.pdf
filename – filename.filetype, e.g. my_circuit.pdf, my_circuit.png (everything that qpic supports)
+
style – string keyword (tequila, standard, generators) or dictionary containing the following keys:
+always_use_generators: represent all gates with their generators
+decompose_control_generators: Decompose the controls to generators. Effective only in combination with always_use_generators=True.
+group_together: Keep PauliStrings from the same generator together. Effective only in combination with always_use_generators=True.
+possible values: False, True, ‘TOUCH’ and ‘BARRIER’. True is the same as TOUCH.
+BARRIER will create a visible barrier in qpic
Sphinx supports automatic code documentation using the autodoc extension, which extracts docstrings from your code and integrates them into your documentation. This guide explains how to structure your code comments using reStructuredText to ensure your documentation is well-formatted and informative.
Before you start documenting, make sure you have the Sphinx project and autodoc enabled in your conf.py:
+
# In conf.py, ensure these extensions are present
+extensions=[
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.napoleon',# Optional: For Google/NumPy style docstrings
+]
+
reStructuredText is a lightweight markup language used in docstrings. Here’s how to write effective docstrings for different parts of your code.
+
2.1 Module-Level Docstring
+
At the top of your module (mymodule.py), include a general description:
+
"""
+mymodule.py
+===========
+
+This module contains functions and classes for data processing.
+"""
+
+
+
2.2 Documenting Functions
+
Describe the function’s purpose, parameters, and return values. Use the following format:
+
defadd_numbers(a,b):
+"""
+ Adds two numbers together.
+
+ :param int a: The first number.
+ :param int b: The second number.
+ :returns: The sum of `a` and `b`.
+ :rtype: int
+ """
+ returna+b
+
+
+
2.3 Documenting Classes
+
For classes, include a class-level docstring and document each method:
+
classCalculator:
+"""
+ A simple calculator class to perform basic arithmetic operations.
+
+ :param str name: The name of the calculator instance.
+ """
+
+ def__init__(self,name):
+"""
+ Initializes the calculator with a given name.
+ """
+ self.name=name
+
+ defmultiply(self,x,y):
+"""
+ Multiplies two numbers.
+
+ :param int x: The first factor.
+ :param int y: The second factor.
+ :returns: The product of `x` and `y`.
+ :rtype: int
+ """
+ returnx*y
+
+
+
2.4 Documenting Class Attributes
+
Use the :ivar directive to describe class attributes:
+
classRectangle:
+"""
+ Represents a geometric rectangle.
+
+ :ivar float length: The length of the rectangle.
+ :ivar float width: The width of the rectangle.
+ """
+ def__init__(self,length,width):
+ self.length=length
+ self.width=width
+
If you need to rebuild the documentation from scratch, use:
+
makeclean
+
+
+
This removes all previously built files.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/sphinx/genindex.html b/docs/sphinx/genindex.html
new file mode 100644
index 0000000..cefd45a
--- /dev/null
+++ b/docs/sphinx/genindex.html
@@ -0,0 +1,2024 @@
+
+
+
+
+
+
+
+ Index — Tequila Documentation 13.9.2024 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index
+
+
+
+
+
+
+
+
+
+
Index
+
+
+ A
+ | B
+ | C
+ | D
+ | E
+ | F
+ | G
+ | H
+ | I
+ | J
+ | K
+ | L
+ | M
+ | N
+ | O
+ | P
+ | Q
+ | R
+ | S
+ | T
+ | U
+ | V
+ | W
+ | X
+ | Y
+ | Z
+
+
Convenience initialization
+of Pauli Operators. Resulting structures can be added and multiplied together.
+Currently uses OpenFermion as backend (QubitOperators)
e.g.
+wfn1 = tq.QubitWaveFunction.from_string(“1.0*|00> + 1.0*|11>”).normalize()
+wfn2 = tq.QubitWaveFunction.from_string(“1.0*|00>”)
+operator = tq.paulis.KetBra(ket=wfn1, bra=wfn1)
+initializes the transfer operator from the all-zero state to a Bell state
+
+
Parameters:
+
+
ket (QubitWaveFunction:) – QubitWaveFunction which defines the ket element
+can also be given as string or array or integer
+
bra (QubitWaveFunction:) – QubitWaveFunction which defines the bra element
+can also be given as string or array or integer
+
hermitian (bool: (Default False)) – if True the hermitian version H + H^dagger is returned
+
threshold (float: (Default 1.e-6)) – elements smaller than the threshold will be ignored
+
n_qubits (only needed if ket and/or bra are passed down as integers)
+
+
+
Returns:
+
a tequila QubitHamiltonian (not necessarily hermitian) representing the KetBra operator desired.
wfn (QubitWaveFunction or int, or string, or array :) – The wavefunction onto which the projector projects
+Needs to be passed down as tequilas QubitWaveFunction type
+See the documentation on how to initialize a QubitWaveFunction from
+integer, string or array (can also be passed down diretly as one of those types)
+
+
+
+
threshold: float: (Default value = 0.0)
neglect small parts of the operator
+
+
+
n_qubits: only needed when an integer is given as wavefunction
Welcome to the Tequila Documentation website.
+This website provides comprehensive information about the modules available in Tequila,
+including a detailed overview of each module’s functions and instructions on how to utilise them correctly.
+
Additionally, there are practical tutorials that illustrate the capabilities of Tequila.
convenience function to compile gradient objects and relavant types. For use by inheritors.
+
+
Parameters:
+
+
objective (Objective:) – the objective whose gradient is to be calculated.
+
variables (list:) – the variables to take gradients with resepct to.
+
gradient – special argument to change what structure is used to calculate the gradient, like numerical, or QNG.
+Default: use regular, analytic gradients.
+
optional – special argument to change what structure is used to calculate the gradient, like numerical, or QNG.
+Default: use regular, analytic gradients.
+
args
+
kwargs
+
+
+
Returns:
+
both the uncompiled and compiled gradients of objective, w.r.t variables.
convenience function to compile hessians for optimizers which require it.
+:param variables: the variables of the hessian.
+:param grad_obj: the gradient object, to be differentiated once more
+:param comp_grad_obj: the compiled gradient object, used for further compilation of the hessian.
+:param hessian: extra information to modulate compilation of the hessian.
+:type hessian: optional:
+:param args:
+:param kwargs:
+
+
Returns:
+
uncompiled and compiled hessian objects, in that order
convenience function to wrap over compile; for use by inheritors.
+:param objective: an objective to compile.
+:type objective: Objective:
+:param args:
+:param kwargs:
Convenience function to format the variables of some objective recieved in calls to optimzers.
+
+
Parameters:
+
+
objective (Objective:) – the objective being optimized.
+
initial_values (dict or string:) – initial values for the variables of objective, as a dictionary.
+if string: can be zero or random
+if callable: custom function that initializes when keys are passed
+if None: random initialization between 0 and 2pi (not recommended)
+
variables (list:) – the variables being optimized over.
+
+
+
Returns:
+
active_angles, a dict of those variables being optimized.
+passive_angles, a dict of those variables NOT being optimized.
+variables: formatted list of the variables being optimized.
convenience function to get the gradients of some variable out of the history.
+:param key: the name of the variable whose gradients are sought
+:type key: str:
+
+
Returns:
+
a dictionary, representing the gradient of variable ‘key’ over time.
Convenience function to plot the progress of the optimizer over time.
+:param property: which property (eg angles, energies, gradients) to plot.
+
+
Default: plot energies over time.
+
+
+
Parameters:
+
+
key (str, optional:) – if property is ‘angles’ or ‘gradients’, key allows you to plot just an individual variables’ property.
+Default: plot everything
+
filename – if give, plot to this file; else, plot to terminal.
+Default: plot to terminal.
+
optional – if give, plot to this file; else, plot to terminal.
+Default: plot to terminal.
+
baselines (dict, optional:) – dictionary of plotting axis baseline information.
+Default: use whatever matplotlib auto-generates.
OptimizerGD allows for two modalities: it can either function as a ‘stepper’, simply calculating updated
+parameter values for a given object; or it can be called to perform an entire optimization. The former
+is used to accomplish the latter, and can give users a more fine-grained control of the optimization.
+See Optimizer for details on inherited attributes or methods; there are several.
dictionary mapping object ids as strings to said object’s current stored moments; a pair of lists of floats,
+namely running tallies of gradient momenta. said momenta are used to SCALE or REDIRECT gradient descent steps.
dictionary mapping object ids as strings to an int; how many optimization steps have been performed for
+a given object. Relevant only to the Adam optimizer.
a float. Hyperparameter: used to adjust the learning rate each iteration using the formula: lr := original_lr / (iteration ** alpha)
+Default: None. If not specify alpha or lr given as a list: lr will not be adjusted
a float. Hyperparameter: used to adjust the step of the gradient for spsa method in each iteration
+following: c := original_c / (iteration ** gamma)
+Default value: None. If not specify gamma or c given as a list: c will not be adjusted
a float. Hyperparameter: scales (perhaps nonlinearly) all second moment terms in any relavant method.
+in some literature, may be referred to as ‘beta_2’.
a float or list of floats. Hyperparameter: The step rate used in the spsa gradient.
+If it is a list, the steprate will change each iteration until the last item of the list is reached.
perform all initialization for an objective, register it with lookup tables, and return it compiled.
+MUST be called before step is used.
+
+
Parameters:
+
+
objective (Objective:) – the objective to ready for optimization.
+
initial_values (dict, optional:) – the initial values of to prepare the optimizer with.
+Default: choose randomly.
+
variables (list, optional:) – which variables to optimize over, and hence prepare gradients for.
+Default value: optimize over all variables in objective.
+
gradient (optional:) – extra keyword; information used to compile alternate gradients.
+Default: prepare the standard, analytical gradient.
perform a single optimization step and return suggested parameters.
+:param objective: the compiled objective, to perform an optimization step for. MUST be one returned by prepare.
+:type objective: Objective:
+:param parameters: the parameters to use in performing the optimization step.
+:type parameters: dict:
Initialize and call the GD optimizer.
+:param objective: The tequila objective to optimize
+:type objective: Objective :
+:param lr: the learning rate. Default 0.1.
+:type lr: float or list of floats >0:
+:param alpha: scaling factor to adjust learning rate each iteration. default None
+:type alpha: float >0:
+:param gamma: scaling facto to adjust step for gradient in spsa method. default None
+:type gamma: float >0:
+:param beta: scaling factor for first moments. default 0.9
+:type beta: float >0:
+:param rho: scaling factor for second moments. default 0.999
+:type rho: float >0:
+:param c: stepsize for the gradient of the spsa method
+:type c: float or list of floats:
+:param epsilon: small float for stability of division. default 10^-7
+:type epsilon: float>0:
+:param method: which variation on Gradient Descent to use. Options include ‘sgd’,’adam’,’nesterov’,’adagrad’,’rmsprop’, etc.
+:type method: string: Default = ‘sgd’
+:param initial_values:
+
+
+
Initial values as dictionary of Hashable types (variable keys) and floating point numbers. If given None,
they will all be set to zero
+
+
+
+
+
Parameters:
+
+
variables (List[Hashable], optional:) – List of Variables to optimize
+
gradient (optional:) – the gradient to use. If None, calculated in the usual way. if str=’qng’, then the qng is calculated.
+If a dictionary of objectives, those objectives are used. If another dictionary,
+an attempt will be made to interpret that dictionary to get, say, numerical gradients.
+
samples (int, optional:) – samples/shots to take in every run of the quantum circuits (None activates full wavefunction simulation)
+
maxiter (int : Default = 100:) – the maximum number of iterations to run.
+
diis (int, optional:) – Number of iteration before starting DIIS acceleration.
+
backend (str, optional:) – Simulation backend which will be automatically chosen if set to None
+
noise (NoiseModel, optional:) – a NoiseModel to apply to all expectation values in the objective.
+
device (optional:) – the device from which to (potentially, simulatedly) sample all quantum circuits employed in optimization.
+
tol (float : Default = 10^-4) – Convergence tolerance for optimization; if abs(delta f) smaller than tol, stop.
+
silent (bool : Default = False:) – No printout if True
+
save_history (bool: Default = True:) – Save the history throughout the optimization
+
calibrate_lr (bool: Default = False:) – Calibrates the value of the learning rate
+
+
+
+
+
Note
+
optional kwargs may include beta, beta2, and rho, parameters which affect
+(but do not need to be altered) the various method algorithms.
objective (Objective :) – The tequila objective to optimize
+
gradient (Union[str, Dict[Variable, Objective], None] : Default value = None):) – ‘2-point’, ‘cs’ or ‘3-point’ for numerical gradient evaluation (does not work in combination with all optimizers),
+dictionary of variables and tequila objective to define own gradient,
+None for automatic construction (default)
+Other options include ‘qng’ to use the quantum natural gradient.
+
hessian (Union[str, Dict[Variable, Objective], None], optional:) – ‘2-point’, ‘cs’ or ‘3-point’ for numerical gradient evaluation (does not work in combination with all optimizers),
+dictionary (keys:tuple of variables, values:tequila objective) to define own gradient,
+None for automatic construction (default)
+
initial_values (Dict[Hashable, numbers.Real], optional:) – Initial values as dictionary of Hashable types (variable keys) and floating point numbers. If given None they will all be set to zero
+
variables (List[Hashable], optional:) – List of Variables to optimize
+
samples (int, optional:) – samples/shots to take in every run of the quantum circuits (None activates full wavefunction simulation)
+
maxiter (int : (Default value = 100):) – max iters to use.
+
backend (str, optional:) – Simulator backend, will be automatically chosen if set to None
+
backend_options (dict, optional:) – Additional options for the backend
+Will be unpacked and passed to the compiled objective in every call
+
noise (NoiseModel, optional:) – a NoiseModel to apply to all expectation values in the objective.
+
method (str : (Default = "BFGS"):) – Optimization method (see scipy documentation, or ‘available methods’)
+
tol (float : (Default = 1.e-3):) – Convergence tolerance for optimization (see scipy documentation)
+
method_options (dict, optional:) – Dictionary of options
+(see scipy documentation)
+
method_bounds (Dict[Hashable, Tuple[float, float]], optional:) – bounds for the variables (see scipy documentation)
+
method_constraints (optional:) – (see scipy documentation
+
silent (bool :) – No printout if True
+
save_history (bool:) – Save the history throughout the optimization
method (str:) – The optimization method (e.g. bfgs, cobyla, nelder-mead, …)
+see ‘tq.optimizers.show_available_methods()’ for an overview
+
objective (tq.Objective:) – The abstract tequila objective to be optimized
+
variables (list of names:) – The variables which shall be optimized given as list
+Can be passed as list of names or list of tq variables
+
initial_values (dict:) – Initial values for the optimization, passed as dictionary
+with the variable names as keys.
+Alternatively zero, random or a single number are accepted
+
maxiter – maximum number of iterations
+
kwargs –
further keyword arguments for the actual minimization functions
+can also be called directly as tq.minimize_modulename
+e.g. tq.minimize_scipy
+See their documentation for more details
+
example: gradient keyword:
+gradient (Default Value: None):
+instructions for gradient compilation
+can be a dictionary of tequila objectives representing the gradients
+or a string/dictionary giving instructions for numerical gradients
+examples are
Compilation of CRy as on https://doi.org/10.1103/PhysRevA.102.062612
+If not control passed, Ry returned
+:param case:
+:type case: if 1 employs eq. 12 from the paper, if 0 eq. 13
Consistent formatting of excitation indices
+idx = [(p0,q0),(p1,q1),…,(pn,qn)]
+sorted as: p0<p1<pn and pi<qi
+:param idx: list of index tuples describing a single(!) fermionic excitation
+:return: list of index tuples
Consistent formatting of excitation variable
+idx = [(p0,q0),(p1,q1),…,(pn,qn)]
+sorted as: pi<qi and p0 < p1 < p2
+:param idx: list of index tuples describing a single(!) fermionic excitation
+:return: sign of the variable with re-ordered indices
spin-paired double excitations (both electrons occupy the same spatial orbital and are excited to another spatial orbital)
+in the jordan-wigner representation are identical to 4-qubit excitations which can be compiled more efficient
+this function hels to automatically detect those cases
Manage Basis Integrals of Quantum Chemistry
+All integrals are held in their original basis, the corresponding mo-coefficients have to be passed down
+and are usually held by the QuantumChemistryBaseClass
Get all molecular integrals in given orbital basis (determined by orbital_coefficients in self or the ones passed here)
+active space is considered if not explicitly ignored
+:param orbital_coefficients:
+:type orbital_coefficients: orbital coefficients in the given basis (first index is basis, second index is orbitals). Need to go over full basis (no active space)
+:param ordering:
+:type ordering: ordering of the two-body integrals (default is openfermion)
+:param ignore_active_space:
+:type ignore_active_space: ignore active space and give back full integrals
returns: * two-body orbitals in given basis (using basis functions, not molecular orbitals. No active space considered)
+* ordering is “chem” i.e. Mulliken i.e. integrals_{abcd} = <ac|g|bd>
Verify if orbital coefficients are valid (i.e. if they define a orthonormal set of orbitals)
+:param orbital_coefficients:
+:type orbital_coefficients: the orbital coefficients C_ij with i:basis and j:orbitals
+:param tolerance:
+
+
Return type:
+
True or False depending if the overlap matrix of the basis is transformed to a unit matrix
Function to reorder tensors according to some convention.
+
+
Parameters:
+
to –
Ordering scheme of choice.
+‘openfermion’, ‘of’ (default) :
+
+
openfermion - ordering, corresponds to integrals of the type
+h^pq_rs = int p(1)* q(2)* O(1,2) r(2) s(1) (O(1,2)
+with operators a^pq_rs = a^p a^q a_r a_s (a^p == a^dagger_p)
+currently needed for dependencies on openfermion-library
+
+
+
’chem’, ‘c’ :
quantum chemistry ordering, collect particle terms,
+more convenient for real-space methods
+h^pq_rs = int p(1) q(1) O(1,2) r(2) s(2)
+This is output by psi4
+
+
’phys’, ‘p’ :
typical physics ordering, integrals of type
+h^pq_rs = int p(1)* q(2)* O(1,2) r(1) s(2)
+with operators a^pq_rs = a^p a^q a_s a_r
Get subspace of tensor by a set of index lists
+according to hPQ.sub_lists(idx_lists=[p, q]) = [hPQ for P in p and Q in q]
+
This essentially is an implementation of a non-contiguous slicing using numpy.take
+
+
Parameters:
+
idx_lists – List of lists, each defining the desired subspace per axis
+Size needs to match order of tensor, and lists successively correspond to axis=0,1,2,…,N
Get subspace of tensor by a string
+Currently is able to resolve an active space, named ‘a’, full space ‘f’, and the complement ‘p’ = ‘f’ - ‘a’.
+Full space in this context may also be smaller than actual tensor dimension.
+
The specification of active space in this context only allows to pick a set from a list of orbitals, and
+is not able to resolve an active space from irreducible representations.
+
Example for one-body tensor:
+hPQ.sub_lists(name=’ap’) = [hPQ for P in active_indices and Q in _passive_indices]
+
+
Parameters:
+
name – String specifying the desired subspace, elements need to be a (active), f (full), p (full - active)
OpenFermion uses case sensitive hash tables for chemical elements
+I.e. you need to name Lithium: ‘Li’ and ‘li’ or ‘LI’ will not work
+this convenience function does the naming
+:return: first letter converted to upper rest to lower
Returns the geometry
+If a xyz filename was given the file is read out
+otherwise it is assumed that the geometry was given as string
+which is then reformatted as a list usable as input for openfermion
+:return: geometry as list
+e.g. [(h,(0.0,0.0,0.35)),(h,(0.0,0.0,-0.35))]
+Units: Angstrom!
Expects a state in spin-orbital ordering
+Returns the corresponding qubit state in the class encoding
+:param state:
+
+
basis-state as occupation number vector in spin orbitals
+sorted as: [0_up, 0_down, 1_up, 1_down, … N_up, N_down]
+with N being the number of spatial orbitals
+
+
+
Returns:
+
basis-state as qubit state in the corresponding mapping
Expects a state in spin-orbital ordering
+Returns the corresponding qubit state in the class encoding
+:param state:
+
+
basis-state as occupation number vector in spin orbitals
+sorted as: [0_up, 0_down, 1_up, 1_down, … N_up, N_down]
+with N being the number of spatial orbitals
+
+
+
Returns:
+
basis-state as qubit state in the corresponding mapping
Expects a state in spin-orbital ordering
+Returns the corresponding qubit state in the class encoding
+:param state:
+
+
basis-state as occupation number vector in spin orbitals
+sorted as: [0_up, 0_down, 1_up, 1_down, … N_up, N_down]
+with N being the number of spatial orbitals
+
+
+
Returns:
+
basis-state as qubit state in the corresponding mapping
Call classical methods over PySCF (needs to be installed) or
+use as a shortcut to calculate quantum energies (see make_upccgsd_ansatz)
+
+
Parameters:
+
+
method (method name) – classical: HF, MP2, CCSD, CCSD(T), FCI
+quantum: SPA-GASD (SPA can be dropped as well as letters in GASD)
+examples: GSD is the same as UpCCGSD, SPA alone is equivalent to SPA-D
+see make_upccgsd_ansatz of the this class for more information
Shortcut for convenience
+:param label: label for the angles
+:param hcb: if True the circuit will not map from HCB to JW (or other encodings that might be supported in the future)
+
+
Return type:
+
Default SPA ansatz (equivalent to PNO-UpCCD with madness PNOs)
Overwriting baseclass to allow names like : PNO-UpCCD etc
+:param label:
+:type label: label the variables of the ansatz ( variables will be labelled (indices, X, (label, layer) witch X=D/S)
+:param direct_compiling:
+:type direct_compiling: Directly compile the first layer (works only for transformation that implement the hcb_to_me function)
+:param name: if HCB is included in name: do not map from hard-core Boson to qubit encoding of this molecule
+
+
if SPA is included in name: Use the separable pair ansatz (excitations will be restricted to the PNO structure of the surrogate model)
+Excitations: can be “S” (use only singles), “D” (use only doubles), “GSD” (generalized singles and doubles), “GASD” (approximate singles, neglecting Z terms in JW)
+
+
+
Parameters:
+
+
neglect_z (neglect all Z terms in singles excitations generators)
+
order (repetition of layers, can be given over the name as well, the order needs to be the first in the name then (i.e. 2-UpCCGSD, 2-SPA-GSD, etc))
Computes the spin-free [2]_R12 correction, needing only the 1- and 2-RDM of a reference method
+Requires either 1-RDM, 2-RDM or information to compute them in kwargs
+
+
Parameters:
+
+
rdm1 – 1-electron reduced density matrix
+
rdm2 – 2-electron reduced density matrix
+
gamma – f12-exponent, for a correlation factor f_12 = -1/gamma * exp[-gamma*r_12]
+
n_ri – dimensionality of RI-basis; if None, then the maximum available via tensors / basis-set is used
+
f12_filename – when using madness_interface, <q|h|p> and <rs|1/r_12|pq> already available;
+need to provide f12-tensor <rs|f_12|pq> as “.bin” from madness or “.npy”, assuming Mulliken ordering
+
kwargs – e.g. RDM-information via {“U”: QCircuit, “variables”: optimal angles}, needs to be passed if rdm1,rdm2 not
+yet computed
plot orbitals to cube file (needs madtequila backend installed)
+:param method: if you want to plot frozen orbitals you can hand in a Tequila Orbital structure with idx_total defined
+:type method: orbital, the orbital index (starting from 0 on the active orbitals)
+:param filename:
+:type filename: name of the cubefile (default: mra_orbital_X.cube where X is the total index of the active orbital)
+:param args:
+:type args: further arguments for plot2cube
+:param kwargs further keyword arguments for plot2cube:
+:param see here for more https:
+:type see here for more https: //github.com/kottmanj/madness/tree/tequila/src/apps/plot
molecule (The tequila molecule whose orbitals are to be optimized)
+
circuit (The circuit that defines the ansatz to the wavefunction in the VQE) – can be None, if a customized vqe_solver is passed that can construct a circuit
+
vqe_solver (The VQE solver (the default - vqe_solver=None - will take the given circuit and construct an expectationvalue out of molecule.make_hamiltonian and the given circuit)) – A customized object can be passed that needs to be callable with the following signature: vqe_solver(H=H, circuit=self.circuit, molecule=molecule, **self.vqe_solver_arguments)
use_hcb (indicate if the circuit is in hardcore Boson encoding)
+
vqe_solver_arguments (Optional arguments for a customized vqe_solver or the default solver) – for the default solver: vqe_solver_arguments={“optimizer_arguments”:A, “restrict_to_hcb”:False} where A holds the kwargs for tq.minimize
+restrict_to_hcb keyword controls if the standard (in whatever encoding the molecule structure has) Hamiltonian is constructed or the hardcore_boson hamiltonian
+
initial_guess (Initial guess for the MCSCF module of PySCF (Matrix of orbital rotation coefficients)) –
The default (None) is a unit matrix
+predefined commands are
+
+
initial_guess=”random”
+initial_guess=”random_loc=X_scale=Y” with X and Y being floats
+This initialized a random guess using numpy.random.normal(loc=X, scale=Y) with X=0.0 and Y=0.1 as defaults
+
+
+
return_mcscf (return the PySCF MCSCF structure after optimization)
+
molecule_arguments (arguments to pass to molecule_factory or default molecule constructor | only change if you know what you are doing)
Call classical methods over PySCF (needs to be installed) or
+use as a shortcut to calculate quantum energies (see make_upccgsd_ansatz)
+
+
Parameters:
+
+
method (method name) – classical: HF, MP2, CCSD, CCSD(T), FCI – with pyscf
+quantum: UpCCD, UpCCSD, UpCCGSD, k-UpCCGSD, UCCSD,
+see make_upccgsd_ansatz of the this class for more information
+
args
+
kwargs (for quantum methods, keyword arguments for minimizer)
Same functionality as qc_base.compute_rdms (look there for more information),
+plus the additional option to compute 1- and 2-RDM using psi4 by the keyword psi4_rdms
+
+
Parameters:
+
+
U – Quantum Circuit to achieve the desired state psi = U |0rangle, optional if psi4_rdms is set to True
+
variables – If U is parametrized, then need to hand over a set of fixed variables
+
spin_free – Set whether matrices should be spin-free (summation over spin) or defined by spin-orbitals
+
get_rdm1 – Set whether either one or both rdm1, rdm2 should be computed. If both are needed at some point,
+it is recommended to compute them at once.
+Note that whatever is specified in psi4_options has priority.
+
get_rdm2 – Set whether either one or both rdm1, rdm2 should be computed. If both are needed at some point,
+it is recommended to compute them at once.
+Note that whatever is specified in psi4_options has priority.
+
psi4_method – Method to be run, currently only methods returning a CIWavefuntion are supported
+(e.g. “detci” + ex_level in options, or “fci”, “cisdt”, “casscf”, but NOT “cisd”)
+
psi4_options – Options to be handed over to psi4, containing e.g. excitation level of “detci”-method.
+If “detci__opdm” for 1-RDM and “detci__tpdm” for 2-RDM are not included, the keywords get_rdm1, get_rdm2 are
+used (if both are specified, prioritizing psi4_options).
Called by self.__init__() with args and kwargs passed through
+Override this in derived class such that it returns an intitialized instance of the integral manager
+
In the BaseClass it is required to pass the following with kwargs on init:
+- one_body_integrals as matrix
+- two_body_integrals as NBTensor of numpy.ndarray (four indices, openfermion ordering)
+- nuclear_repulsion (constant part of hamiltonian - optional)
Computes the spin-free [2]_R12 correction, needing only the 1- and 2-RDM of a reference method
+Requires either 1-RDM, 2-RDM or information to compute them in kwargs
+
+
Parameters:
+
+
rdm1 – 1-electron reduced density matrix
+
rdm2 – 2-electron reduced density matrix
+
gamma – f12-exponent, for a correlation factor f_12 = -1/gamma * exp[-gamma*r_12]
+
n_ri – dimensionality of RI-basis; if None, then the maximum available via tensors / basis-set is used
+
cabs_type –
+
either “active” for using a given basis set as is as approximative CBS (complete basis set), and specify
+
+
OBS (orbital basis) by an active space
+- or “cabs+” for CABS+-approach as in
+
+
Valeev, E. F. (2004). Improving on the resolution of the identity in linear R12 ab initio theories.
+Chemical Physics Letters, 395(4–6), 190–195. https://doi.org/10.1016/j.cplett.2004.07.061
+-> pass cabs_name in cabs_options
+
+
+
cabs_options – dict, which needs at least {“cabs_name”: some CABS basis set} if cabs_type==”cabs+”
+
kwargs – e.g. RDM-information via {“U”: QCircuit, “variables”: optimal angles} if computation via VQE,
+or {“rdm__psi4_method”: some CI method, “rdm__psi4_options”: dict with psi4 options} if computation via
+psi4, compare to psi4_interface.compute_rdms
+one of the above needs to be passed if rdm1,rdm2 not yet computed
Returns RMD2 if computed with compute_rdms function before
+This is returned in Dirac (physics) notation by default (can be changed in compute_rdms with keyword)!
Call classical methods over PySCF (needs to be installed) or
+use as a shortcut to calculate quantum energies (see make_upccgsd_ansatz)
+
+
Parameters:
+
+
method (method name) – classical: HF, MP2, CCSD, CCSD(T), FCI – with pyscf
+quantum: UpCCD, UpCCSD, UpCCGSD, k-UpCCGSD, UCCSD,
+see make_upccgsd_ansatz of the this class for more information
+
args
+
kwargs (for quantum methods, keyword arguments for minimizer)
Base Class for tequila chemistry functionality
+This is what is initialized with tq.Molecule(…)
+We try to define all main methods here and only implemented specializations in the derived classes
+Derived classes interface specific backends (e.g. Psi4, PySCF and Madness). See PACKAGE_interface.py for more
Convenience function for orbital correlator circuit (correlating spatial orbital i and j through a spin-paired double excitation) with standard naming of variables
+See arXiv:2207.12421 Eq.22 for UC(1,2)
Convenience function for orbital rotation circuit (rotating spatial orbital i and j) with standard naming of variables
+See arXiv:2207.12421 Eq.6 for UR(0,1)
+Parameters:
+———-
+
+
+
indices:
tuple of two spatial(!) orbital indices
+
+
angle:
Numeric or hashable type or tequila objective. Default is None and results
+in automatic naming as (“R”,i,j)
+
+
label:
can be passed instead of angle to have auto-naming with label (“R”,i,j,label)
+useful for repreating gates with individual variables
+
+
control:
List of possible control qubits
+
+
assume_real:
Assume that the wavefunction will always stay real.
+Will reduce potential gradient costs by a factor of 2
Call classical methods over PySCF (needs to be installed) or
+use as a shortcut to calculate quantum energies (see make_upccgsd_ansatz)
+
+
Parameters:
+
+
method (method name) – classical: HF, MP2, CCSD, CCSD(T), FCI – with pyscf
+quantum: UpCCD, UpCCSD, UpCCGSD, k-UpCCGSD, UCCSD,
+see make_upccgsd_ansatz of the this class for more information
+
args
+
kwargs (for quantum methods, keyword arguments for minimizer)
Computes the one- and two-particle reduced density matrices (rdm1 and rdm2) given
+a unitary U. This method uses the standard ordering in physics as denoted below.
+Note, that the representation of the density matrices depends on the qubit transformation
+used. The Jordan-Wigner encoding corresponds to ‘classical’ second quantized density
+matrices in the occupation picture.
+
We only consider real orbitals and thus real-valued RDMs.
+The matrices are set as private members _rdm1, _rdm2 and can be accessed via the properties rdm1, rdm2.
+
+
Parameters:
+
+
U – Quantum Circuit to achieve the desired state psi = U |0rangle, non-optional
+
variables – If U is parametrized, then need to hand over a set of fixed variables
+
spin_free – Set whether matrices should be spin-free (summation over spin) or defined by spin-orbitals
+
get_rdm1 – Set whether either one or both rdm1, rdm2 should be computed. If both are needed at some point,
+it is recommended to compute them at once.
+
get_rdm2 – Set whether either one or both rdm1, rdm2 should be computed. If both are needed at some point,
+it is recommended to compute them at once.
+
rdm_trafo – The rdm operators can be transformed, e.g., a^dagger_i a_j -> U^dagger a^dagger_i a_j U,
+where U represents the transformation. The default is set to None, implying that U equas the identity.
+
evaluate – if true, the tequila expectation values are evaluated directly via the tq.simulate command.
+the protocol is optimized to avoid repetation of wavefunction simulation
+if false, the rdms are returned as tq.QTensors
Consistent formatting of excitation indices
+idx = [(p0,q0),(p1,q1),…,(pn,qn)]
+sorted as: p0<p1<pn and pi<qi
+:param idx: list of index tuples describing a single(!) fermionic excitation
+:return: tuple-list of index tuples
Constructs a quantum circuit from a given real unitary matrix using Givens rotations.
+
This method decomposes a unitary matrix into a series of Givens and Rz (phase) rotations,
+then constructs and returns a quantum circuit that implements this sequence of rotations.
+
Parameters:
+- unitary (numpy.array): A real unitary matrix representing the transformation to implement.
+- tol (float): A tolerance threshold below which matrix elements are considered zero.
+- ordering (list of tuples or ‘Optimized’): Custom ordering of indices for Givens rotations or ‘Optimized’ to generate them automatically.
+
Returns:
+- QCircuit: A quantum circuit implementing the series of rotations decomposed from the unitary.
Assuming a pair-specific model, create a pair-specific index list
+to be used in make_upccgsd_ansatz(indices = … )
+Excite from a set of references (i) to any pair coming from (i),
+i.e. any (i,j)/(j,i). If general excitations are allowed, also
+allow excitations from pairs to appendant pairs and reference.
+
+
pair_info
file or list including information about pair structure
+references single number, pair double
+example: as file: “0,1,11,11,00,10” (hand over file name)
+
+
in file, skip first row assuming some text with information
+as list:[‘0’,’1`’,’11’,’11’,’00’,’10’]
+~> two reference orbitals 0 and 1,
+then two orbitals from pair 11, one from 00, one mixed 10
Transform a circuit in the hardcore-boson encoding (HCB)
+to the encoding of this molecule
+HCB is supposed to be encoded on the first n_orbitals qubits
+:param U:
+:type U: HCB circuit (using the alpha qubits)
+:param condensed:
+:type condensed: assume that incoming U is condensed (HCB on the first n_orbitals; and not, as for example in JW on the first n even orbitals)
Called by self.__init__() with args and kwargs passed through
+Override this in derived class such that it returns an intitialized instance of the integral manager
+
In the BaseClass it is required to pass the following with kwargs on init:
+- one_body_integrals as matrix
+- two_body_integrals as NBTensor of numpy.ndarray (four indices, openfermion ordering)
+- nuclear_repulsion (constant part of hamiltonian - optional)
Automatically calls the right subroutines to construct ansatze implemented in tequila.chemistry
+name: namne of the ansatz, examples are: UpCCGSD, UpCCD, SPA, UCCSD, SPA+UpCCD, SPA+GS
Creates the transformed hermitian generator of UCC type unitaries:
M(a^dagger_{a_0} a_{i_0} a^dagger{a_1}a_{i_1} … - h.c.)
+where the qubit map M depends is self.transformation
+
+
+
+
Parameters:
+
+
indices (Iterable[Tuple[int, int]] :) – List of tuples [(a_0, i_0), (a_1, i_1), … ] - recommended format, in spin-orbital notation (alpha odd numbers, beta even numbers)
+can also be given as one big list: [a_0, i_0, a_1, i_1 …]
+
form (str : (Default value None):) – Manipulate the generator to involution or projector
+set form=’involution’ or ‘projector’
+the default is no manipulation which gives the standard fermionic excitation operator back
+
remove_constant_term (bool: (Default value True):) – by default the constant term in the qubit operator is removed since it has no effect on the unitary it generates
+if the unitary is controlled this might not be true!
+
+
+
Returns:
+
1j*Transformed qubit excitation operator, depends on self.transformation
Make excitation generator in the hardcore-boson approximation (all electrons are forced to spin-pairs)
+use only in combination with make_hardcore_boson_hamiltonian()
Creates a molecule in openfermion format by running psi4 and extracting the data
+Will check for previous outputfiles before running
+Will not recompute if a file was found
+
+
Parameters:
+
parameters – An instance of ParametersQC, which also holds an instance of ParametersPsi4 via parameters.psi4
+The molecule will be saved in parameters.filename, if this file exists before the call the molecule will be imported from the file
Separable Pair Ansatz (SPA) for general molecules
+see arxiv:
+edges: a list of tuples that contain the orbital indices for the specific pairs
+
+
one example: edges=[(0,), (1,2,3), (4,5)] are three pairs, one with a single orbital [0], one with three orbitals [1,2,3] and one with two orbitals [4,5]
+
+
hcb: spa ansatz in the hcb (hardcore-boson) space without transforming to current transformation (e.g. JordanWigner), use this for example in combination with the self.make_hardcore_boson_hamiltonian() and see the article above for more info
+use_units_of_pi: circuit angles in units of pi
+label: label the variables in the circuit
+optimize: optimize the circuit construction (see article). Results in shallow circuit from Ry and CNOT gates
+ladder: if true the excitation pattern will be local. E.g. in the pair from orbitals (1,2,3) we will have the excitations 1->2 and 2->3, if set to false we will have standard coupled-cluster style excitations - in this case this would be 1->2 and 1->3
initial_amplitudes (Union[str :) – initial amplitudes given as ManyBodyAmplitudes structure or as string
+where ‘mp2’, ‘cc2’ or ‘ccsd’ are possible initializations
+
include_reference_ansatz – Also do the reference ansatz (prepare closed-shell Hartree-Fock) (Default value = True)
+
parametrized – Initialize with variables, otherwise with static numbers (Default value = True)
UpGCCSD Ansatz similar as described by Lee et. al.
+
+
Parameters:
+
+
include_reference – include the HF reference state as initial state
+
indices – pass custom defined set of indices from which the ansatz will be created
+List of tuples of tuples spin-indices e.g. [((2*p,2*q),(2*p+1,2*q+1)), …]
+
label – An additional label that is set with the variables
+default is None and no label will be set: variables names will be
+(x, (p,q)) for x in range(order)
+with a label the variables will be named
+(label, (x, (p,q)))
+
order – Order of the ansatz (default is 1)
+determines how often the ordering gets repeated
+parameters of repeating layers are independent
+
assume_real – assume a real wavefunction (that is always the case if the reference state is real)
+reduces potential gradient costs from 4 to 2
+
mix_sd – Changes the ordering from first all doubles and then all singles excitations (DDDDD….SSSS….) to
+a mixed order (DS-DS-DS-DS-…) where one DS pair acts on the same MOs. Useful to consider when systems
+with high electronic correlation and system high error associated with the no Trotterized UCC.
Creates a quantum circuit that applies a phase rotation based on phi to both components (up and down) of a given qubit.
+
Parameters:
+- i (int): The index of the qubit to which the rotation will be applied.
+- phi (float): The rotation angle. The actual rotation applied will be multiplied with -2 for both components.
+
Returns:
+- QCircuit: A quantum circuit object containing the sequence of rotations applied to the up and down components of the specified qubit.
Computes the spin-free [2]_R12 correction, needing only the 1- and 2-RDM of a reference method
+Requires either 1-RDM, 2-RDM or information to compute them in kwargs
+
+
Parameters:
+
+
rdm1 – 1-electron reduced density matrix
+
rdm2 – 2-electron reduced density matrix
+
gamma – f12-exponent, for a correlation factor f_12 = -1/gamma * exp[-gamma*r_12]
+
n_ri – dimensionality of RI-basis; specify only, if want to truncate available RI-basis
+if None, then the maximum available via tensors / basis-set is used
+must not be larger than size of available RI-basis, and not smaller than size of OBS
+for n_ri==dim(OBS), the correction returns zero
+
external_info – for usage in qc_base, need to provide information where to find one-body tensor f12-tensor <rs|f_12|pq>;
+pass dictionary with {“f12_filename”: where to find f12-tensor, “scheme”: ordering scheme of tensor}
+
kwargs – e.g. RDM-information via {“U”: QCircuit, “variables”: optimal angles}, needs to be passed if rdm1,rdm2 not
+yet computed
Prepare reference state in the Hardcore-Boson approximation (eqch qubit represents two spin-paired electrons)
+:rtype: tq.QCircuit that prepares the HCB reference
Returns RMD2 if computed with compute_rdms function before
+This is returned in Dirac (physics) notation by default (can be changed in compute_rdms with keyword)!
orbital_coefficients (second index is new orbital indes, first is old orbital index (summed over), indices are assumed to be defined on the active space)
+
ignore_active_space (if true orbital_coefficients are not assumed to be given in the active space)
Decomposes a real unitary matrix into Givens rotations (theta) and Rz rotations (phi).
+
Parameters:
+- unitary (numpy.array): A real unitary matrix to decompose. It cannot be complex.
+- tol (float): Tolerance for considering matrix elements as zero. Elements with absolute value less than tol are treated as zero.
+- ordering (list of tuples or ‘Optimized’): Custom ordering of indices for Givens rotations or ‘Optimized’ to generate them automatically.
+- return_diagonal (bool): If True, the function also returns the diagonal matrix as part of the output.
+
Returns:
+- list: A list of tuples, each representing a Givens rotation. Each tuple contains the rotation angle theta and indices (i,j) of the rotation.
+- list: A list of tuples, each representing an Rz rotation. Each tuple contains the rotation angle phi and the index (i) of the rotation.
+- numpy.array (optional): The diagonal matrix after applying all Givens rotations, returned if return_diagonal is True.
+
+
+
+
+tequila_code.quantumchemistry.qc_base.givens_matrix(n, p, q, theta)[source]
+
Construct a complex Givens rotation matrix of dimension n by theta between rows/columns p and q.
Reconstructs a matrix from given Givens rotations and Rz diagonal rotations.
+This function is effectively an inverse of get_givens_decomposition, and therefore only works with data in the same format as its output.
+
Parameters:
+- n (int): The size of the unitary matrix to be reconstructed.
+- theta_list (list of tuples): Each tuple contains (angle, i, j) representing a Givens rotation of angle radians, applied to rows/columns i and j.
+- phi_list (list of tuples): Each tuple contains (angle, i), representing an Rz rotation by angle radians applied to the `i`th diagonal element.
+- to_real_if_possible (bool): If True, converts the matrix to real if its imaginary part is effectively zero.
+- tol (float): The tolerance whether to swap a complex rotation for a sign change.
+
Returns:
+- numpy.ndarray: The reconstructed complex or real matrix, depending on the to_real_if_possible flag and matrix composition.
geometry – molecular geometry as string or as filename (needs to be in xyz format with .xyz ending)
+
basis_set – quantum chemistry basis set (sto-3g, cc-pvdz, etc)
+
transformation – The Fermion to Qubit Transformation (jordan-wigner, bravyi-kitaev, bravyi-kitaev-tree and whatever OpenFermion supports)
+
backend – quantum chemistry backend (psi4, pyscf)
+
guess_wfn – pass down a psi4 guess wavefunction to start the scf cycle from
+can also be a filename leading to a stored wavefunction
+
name – name of the molecule, if not given it’s auto-deduced from the geometry
+can also be done vice versa (i.e. geometry is then auto-deduced to name.xyz)
+
args
+
kwargs
+
+
+
Return type:
+
The Fermion to Qubit Transformation (jordan-wigner, bravyi-kitaev, bravyi-kitaev-tree and whatever OpenFermion supports)
Initialize a tequila Molecule directly from an openfermion molecule object
+:param molecule: The openfermion molecule
+:param transformation: The Fermion to Qubit Transformation (jordan-wigner, bravyi-kitaev, bravyi-kitaev-tree and whatever OpenFermion supports)
+:param backend: The quantum chemistry backend, can be None in this case
Compile a tequila objective or circuit to a backend
+
+
Parameters:
+
+
objective (Objective:) – tequila objective or circuit
+
variables (dict, optional:) – The variables of the objective given as dictionary
+with keys as tequila Variables and values the corresponding real numbers
+
samples (int, optional:) – if None a full wavefunction simulation is performed, otherwise a fixed number of samples is simulated
+
backend (str, optional:) – specify the backend or give None for automatic assignment
+
noise (NoiseModel, optional:) – the noise model to apply to the objective or QCircuit.
+
device (optional:) – a device on which (or in emulation of which) to sample the circuit.
compile a circuit to render it callable and return it.
+:param abstract_circuit: the circuit to compile
+:type abstract_circuit: QCircuit:
+:param variables: the variables to compile the circuit with.
+:type variables: dict, optional:
+:param backend: the backend to compile the circuit to.
+:type backend: str, optional:
+:param samples: only matters if not None; compile the circuit for sampling/verify backend can do so
+:type samples: int, optional:
+:param device: the device on which the circuit should (perhaps emulatedly) sample.
+:type device: optional:
+:param noise: the noise to apply to the circuit
+:type noise: str or NoiseModel, optional:
+:param args:
+:param kwargs:
compile an objective to render it callable and return it.
+:param objective: the objective to compile
+:type objective: Objective:
+:param variables: the variables to compile the objective with. Will autogenerate zeros for all variables if not supplied.
+:type variables: dict, optional:
+:param backend: the backend to compile the objective to.
+:type backend: str, optional:
+:param samples: only matters if not None; compile the objective for sampling/verify backend can do so
+:type samples: int, optional:
+:param device: the device on which the objective should (perhaps emulatedly) sample.
+:type device: optional:
+:param noise: the noise to apply to all circuits in the objective.
+:type noise: str or NoiseModel, optional:
+:param args:
+:param kwargs:
Same as compile but gives back callable wrapper
+where parameters are passed down as arguments instead of dictionaries
+the order of those arguments is the order of the parameter dictionary
+given here. If not given it is the order returned by objective.extract_variables()
+
See compile for more information on the parameters of this function
+
+
Returns:
+
wrapper over a compiled objective/circuit
+can be called like: function(0.0,1.0,…,samples=None)
choose, or verify, a backend for the user.
+:param backend: what backend to choose or verify. if None: choose for the user.
+:type backend: str, optional:
+:param samples: if int and not None, choose (verify) a simulator which supports sampling.
+:type samples: int, optional:
+:param noise: if not None, choose (verify) a simulator supports the specified noise.
+:type noise: str or NoiseModel, optional:
+:param device: verify that a given backend supports the specified device. MUST specify backend, if not None.
+
+
if None: do not emulate or use real device.
+
+
+
Parameters:
+
exclude_symbolic (bool, optional:) – whether or not to exclude the tequila debugging simulator from the available simulators, when choosing.
objective (Objective:) – tequila objective or circuit
+
variables (Dict:) – The variables of the objective given as dictionary
+with keys as tequila Variables/hashable types and values the corresponding real numbers
+
samples (int, optional:) – if None a full wavefunction simulation is performed, otherwise a fixed number of samples is simulated
+
backend (str, optional:) – specify the backend or give None for automatic assignment
+
noise (NoiseModel, optional:) – specify a noise model to apply to simulation/sampling
+
device – a device upon which (or in emulation of which) to sample
+
*args
+
**kwargs – read_out_qubits = list[int] (define the qubits which shall be measured, has only effect on pure QCircuit simulation with samples)
set this attribute in the derived __init__ to prevent translation of abstract_circuits
+needed for simulators that use native tequila types.
+Default is false
the mapping from tequila qubits to the qubits of the backend circuit.
+Dicationary with keys being integers that enumerate the abstract qubits of the abstract_circuit
+and values being data-structures holding number and instance where number enumerates the
+backend qubits and instance is the instance of a backend qubit
Must be overwritten by inheritors to do anything other than check the validity of the map.
+:param qubits: the qubits to map onto.
+
+
If given as a dictionary, the map is already defined
+If given as a list the map will be those qubits mapped to 0 …. n_qubit-1 of the backend
+
+
+
Returns:
+
the dictionary that maps the qubits of the abstract circuits to an ordered sequence of integers.
+keys are the abstract qubit integers
+values are the backend qubits
+those are data structures which contain name and instance
+where number is the qubit identifier and instance the instance of the backend qubit
+if the backend does not require a special object for qubits the instance should be the same as number
Sample the circuit. If circuit natively equips paulistrings, sample therefrom.
+:param variables: the variables with which to sample the circuit.
+:param samples: the number of samples to take.
+:type samples: int:
+:param read_out_qubits: target qubits to measure (default is all)
+:type read_out_qubits: int:
+:param args:
+:param kwargs:
+
+
Returns:
+
The result of sampling, a recreated QubitWaveFunction in the sampled basis.
Sample from a Hamiltonian which only consists of Pauli-Z and unit operators
+:param samples: number of samples to take
+:param hamiltonian: the tequila hamiltonian
+:param args: arguments for do_sample
+:param kwargs: keyword arguments for do_sample
+
+
Return type:
+
samples, evaluated and summed Hamiltonian expectationvalue
Sample an individual pauli word (pauli string) and return the average result thereof.
+:param samples: how many samples to evaluate.
+:type samples: int:
+:param paulistring: the paulistring to be sampled.
+:param args:
+:param kwargs:
+
+
Returns:
+
the average result of sampling the chosen paulistring
variables – the parameters with which to simulate the circuit.
+
initial_state (Default = 0:) – one of several types; determines the base state onto which the circuit is applied.
+Default: the circuit is applied to the all-zero state.
+
args
+
kwargs
+
+
+
Returns:
+
the wavefunction of the system produced by the action of the circuit on the initial state.
the reduced tequila Hamiltonian(s) of the expectationvalue
+reduction procedure is tracing out all qubits that are not part of the unitary U
+stored as a tuple to evaluate multiple Hamiltonians over the same circuit faster in pure simulations
add an unparametrized gate to the circuit.
+:param gate: the gate to be added to the circuit.
+:type gate: QGateImpl:
+:param circuit: the circuit, to which a gate is to be added.
+:param args:
+:param kwargs:
Add a native qulacs Exponential Pauli gate to a circuit.
+:param gate: the gate to add
+:type gate: ExpPauliGateImpl:
+:param circuit: the qulacs circuit, to which the gate is to be added.
+:param variables: dict containing values of the parameters appearing in the pauli gate.
+:param args:
+:param kwargs:
Add a measurement operation to a circuit.
+:param circuit: a circuit, to which the measurement is to be added.
+:param target_qubits: abstract target qubits
+:type target_qubits: List[int]
+:param args:
+:param kwargs:
add a parametrized gate.
+:param gate: the gate to add to the circuit.
+:type gate: QGateImpl:
+:param circuit: the circuit to which the gate is to be added
+:param variables: dict that tells values of variables; needed IFF the gate is an ExpPauli gate.
+:param args:
+:param kwargs:
reduce circuit depth using the native qulacs optimizer.
+:param circuit:
+:param max_block_size: the maximum block size for use by the qulacs internal optimizer.
+:type max_block_size: int: Default = 4:
+:param silent: whether or not to print the resullt of having optimized.
+:type silent: bool:
+:param args:
+:param kwargs:
Convert reduced hamiltonians to native Qulacs types for efficient expectation value evaluation.
+:param hamiltonians: an interable set of hamiltonian objects.
Sample this Expectation Value.
+:param variables: variables, to supply to the underlying circuit.
+:param samples: the number of samples to take.
+:type samples: int:
+:param args:
+:param kwargs: