Skip to content

Commit

Permalink
Develop into main to test automated workflow for Sphinx documentation. (
Browse files Browse the repository at this point in the history
#397)

* Improving draw method: now works with strings for gate parameters (#391)
* Added support for multi-controlled gates on qiskit (MCRX, MCRY, MCRZ, MCPHASE) (#390)
* Add automated documentation workflow & gitignore (#396)
* Unitaryhack issue 386: Augment the Rotosolve optimizer to support Rotoselect (#392)
* ⚡Added rotoselect algorithm and improved rotosolve to use 2 evaluations per parameter.
* Unitaryhack issue 384: Circuit as reference state in the ansatz definition (#398)
* added method `Circuit.fix_variational_parametrs`, which turns all variational gates non-variational.
* Added support for circuits referrence states for the ansatze
* Fixing Pyscf Incompatibility problem  (#394)
* update CI to check for version 2.5
* Update mp2_solver.py

---------

Co-authored-by: AlexandreF-1qbit <[email protected]>
Co-authored-by: ValentinS4t1qbit <[email protected]>
Co-authored-by: Alexandre Fleury <[email protected]>
Co-authored-by: Valentin Senicourt <[email protected]>
Co-authored-by: GitHub Actions <[email protected]>
Co-authored-by: Anush Venkatakrishnan <[email protected]>
Co-authored-by: Or Golan <[email protected]>
Co-authored-by: Kazuki Tsuoka <[email protected]>
Co-authored-by: Colin Burdine <[email protected]>
  • Loading branch information
10 people authored Jun 18, 2024
1 parent 6791dc8 commit 3b49634
Show file tree
Hide file tree
Showing 28 changed files with 799 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:

- name: Install pyscf
run: |
python -m pip install pyscf==2.4.0
python -m pip install pyscf
python -m pip install git+https://github.com/pyscf/semiempirical
if: always()

Expand Down
42 changes: 42 additions & 0 deletions .github/workflows/deploy_docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Build & deploy sphinx document

on:
workflow_dispatch:
push:
branches:
- main

jobs:
build-deploy:
name: Build & deploy sphinx document
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install Dependencies
run: |
python -m pip install sphinx sphinx_rtd_theme nbsphinx
python -m pip install .
- name: Run build command
run: |
sphinx-apidoc -o ./docs/source ./tangelo
sphinx-build -M html ./docs/source ./docs/build -E
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
publish_branch: gh-pages
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/build/html
force_orphan: true
user_name: 'github-actions[bot]'
user_email: 'github-actions[bot]@users.noreply.github.com'
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**/__pycache__
.pytest_cache
**egg-info
**/.DS_Store
**/build
9 changes: 8 additions & 1 deletion tangelo/algorithms/classical/mp2_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,14 @@ def simulate(self):
if self.uhf:
self.mp2_fragment = self.mp.UMP2(self.mean_field, frozen=self.frozen)
else:
self.mp2_fragment = self.mp.RMP2(self.mean_field, frozen=self.frozen)
import pyscf
if pyscf.__version__ == '2.5.0' and self.mean_field.istype('ROHF'):
mf = self.mean_field
mf = mf.remove_soscf()
mf = mf.to_uhf()
self.mp2_fragment = self.mp.UMP2(mf, frozen=self.frozen, mo_coeff=mf.mo_coeff, mo_occ=None)
else:
self.mp2_fragment = self.mp.RMP2(self.mean_field, frozen=self.frozen)

self.mp2_fragment.verbose = 0
_, self.mp2_t2 = self.mp2_fragment.kernel()
Expand Down
2 changes: 1 addition & 1 deletion tangelo/algorithms/variational/adapt_vqe_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ def choose_operator(self, gradients, tolerance=1e-3):
max_partial = gradients[sorted_op_indices[-1]]

if self.verbose:
print(f"LARGEST PARTIAL DERIVATIVE: {max_partial :4E}")
print(f"LARGEST PARTIAL DERIVATIVE: {max_partial:4E}")

return [sorted_op_indices[-1]] if max_partial >= tolerance else []

Expand Down
28 changes: 24 additions & 4 deletions tangelo/linq/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import warnings

import numpy as np
import sympy as sp
from cirq.contrib.svg import SVGCircuit

from tangelo.linq import Gate
Expand Down Expand Up @@ -180,14 +181,26 @@ def applied_gates(self):
return self._applied_gates if "CMEASURE" in self.counts else self._gates

def draw(self):
"""Method to output a prettier version of the circuit for use in jupyter notebooks that uses cirq SVGCircuit"""
# circular import
"""Method to output a prettier version of the circuit
for use in jupyter notebooks that uses cirq SVGCircuit"""
from tangelo.linq.translator.translate_cirq import translate_c_to_cirq
cirq_circ = translate_c_to_cirq(self)
# Remove identity gates that are added in translate_c_to_cirq (to ensure all qubits are initialized) before drawing.
circuit_copy = self.copy()
for gate in circuit_copy._gates:
if gate.parameter and isinstance(gate.parameter, str):
gate.parameter = self._string_to_sympy(gate)

cirq_circ = translate_c_to_cirq(circuit_copy)
cirq_circ.__delitem__(0)
return SVGCircuit(cirq_circ)

def _string_to_sympy(self, gate):
"""Convert a gate parameter (type string) to a sympy symbol"""
try:
return sp.symbols(gate.parameter)
except Exception as e:
print(f"Error converting {gate.parameter} to sympy symbol: {e}")
return gate.parameter

def copy(self):
"""Return a deepcopy of circuit"""
return Circuit(copy.deepcopy(self._gates), n_qubits=self._qubits_simulated, name=self.name, cmeasure_control=copy.deepcopy(self._cmeasure_control))
Expand Down Expand Up @@ -427,6 +440,13 @@ def finalize_cmeasure_control(self):
if isinstance(self._cmeasure_control, ClassicalControl):
self._cmeasure_control.finalize()

def fix_variational_parameters(self):
"""Fix all variational parameters in this circuit, making the corresponding gates non-variational."""

for gate in self._variational_gates:
gate.is_variational = False
self._variational_gates = []


def stack(*circuits):
""" Take list of circuits as input, and stack them (e.g concatenate them along the
Expand Down
19 changes: 19 additions & 0 deletions tangelo/linq/tests/test_translator_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,25 @@ def test_qiskit(self):
sim_results = qiskit_simulator.run(translated_circuit).result()
np.testing.assert_array_almost_equal(sim_results.get_statevector(translated_circuit), reference_big_msq, decimal=6)

@unittest.skipIf("qiskit" not in installed_backends, "Test Skipped: Backend not available \n")
def test_qiskit_multi_control(self):
from qiskit import QuantumCircuit

for g in ["CRX", "CRY", "CRZ", "CPHASE"]:
c = Circuit([Gate(g, 2, control=[0, 1], parameter=1.)])
c_qiskit = translate_c(c, target="qiskit")
q = QuantumCircuit(3)
if g == "CRX":
q.mcrx(1., [0, 1], 2)
elif g == "CRY":
q.mcry(1., [0, 1], 2)
elif g == "CRZ":
q.mcrz(1., [0, 1], 2)
elif g == "CPHASE":
q.mcp(1., [0, 1], 2)

assert c_qiskit.data == q.data

@unittest.skipIf("cirq" not in installed_backends, "Test Skipped: Backend not available \n")
def test_cirq(self):
"""
Expand Down
11 changes: 9 additions & 2 deletions tangelo/linq/translator/translate_qiskit.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ def get_qiskit_gates():
GATE_QISKIT["CRX"] = qiskit.QuantumCircuit.crx
GATE_QISKIT["CRY"] = qiskit.QuantumCircuit.cry
GATE_QISKIT["CRZ"] = qiskit.QuantumCircuit.crz
GATE_QISKIT["MCRX"] = qiskit.QuantumCircuit.mcrx
GATE_QISKIT["MCRY"] = qiskit.QuantumCircuit.mcry
GATE_QISKIT["MCRZ"] = qiskit.QuantumCircuit.mcrz
GATE_QISKIT["MCPHASE"] = qiskit.QuantumCircuit.mcp
GATE_QISKIT["CNOT"] = qiskit.QuantumCircuit.cx
GATE_QISKIT["SWAP"] = qiskit.QuantumCircuit.swap
GATE_QISKIT["XX"] = qiskit.QuantumCircuit.rxx
Expand Down Expand Up @@ -95,14 +99,17 @@ def translate_c_to_qiskit(source_circuit: Circuit, save_measurements=False, no_c
# Maps the gate information properly. Different for each backend (order, values)
for gate in source_circuit._gates:
if gate.control is not None:
if len(gate.control) > 1:
if (len(gate.control) > 1) and (gate.name not in {"CRX", "CRY", "CRZ", "CPHASE"}):
raise ValueError('Multi-controlled gates not supported with qiskit. Gate {gate.name} with controls {gate.control} is not allowed')
if gate.name in {"H", "Y", "X", "Z", "S", "T"}:
(GATE_QISKIT[gate.name])(target_circuit, gate.target[0])
elif gate.name in {"RX", "RY", "RZ", "PHASE"}:
(GATE_QISKIT[gate.name])(target_circuit, gate.parameter, gate.target[0])
elif gate.name in {"CRX", "CRY", "CRZ", "CPHASE"}:
(GATE_QISKIT[gate.name])(target_circuit, gate.parameter, gate.control[0], gate.target[0])
if len(gate.control) > 1:
(GATE_QISKIT["M" + gate.name])(target_circuit, gate.parameter, gate.control, gate.target[0])
else:
(GATE_QISKIT[gate.name])(target_circuit, gate.parameter, gate.control[0], gate.target[0])
elif gate.name in {"CNOT", "CH", "CX", "CY", "CZ"}:
(GATE_QISKIT[gate.name])(target_circuit, gate.control[0], gate.target[0])
elif gate.name in {"SWAP"}:
Expand Down
6 changes: 5 additions & 1 deletion tangelo/toolboxes/ansatz_generator/adapt_ansatz.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ def update_var_params(self, var_params):

def prepare_reference_state(self):
"""Prepare a circuit generating the HF reference state."""
if self.reference_state.upper() == "HF":
if isinstance(self.reference_state, Circuit):
ref_circuit = self.reference_state.copy()
ref_circuit.fix_variational_parameters()
return ref_circuit
elif self.reference_state.upper() == "HF":
return get_reference_circuit(n_spinorbitals=self.n_spinorbitals, n_electrons=self.n_electrons,
mapping=self.mapping, up_then_down=self.up_then_down, spin=self.spin)
else:
Expand Down
25 changes: 19 additions & 6 deletions tangelo/toolboxes/ansatz_generator/hea.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,25 @@ class HEA(Ansatz):
n_qubits (int) : The number of qubits in the ansatz.
Default, None.
n_electrons (int) : Self-explanatory.
reference_state (str): "HF": Hartree-Fock reference state. "zero": for
no reference state. Default: "HF".
reference_state (str, Circuit): "HF": Hartree-Fock reference state. "zero": for
no reference state. Can also be a Circuit object, in which case a copy of
circuit with variational parameters fixed is used. Default: "HF".
"""

def __init__(self, molecule=None, mapping="jw", up_then_down=False,
n_layers=2, rot_type="euler", n_qubits=None, n_electrons=None,
spin=None, reference_state="HF"):

if not (bool(molecule) ^ (bool(n_qubits) and (bool(n_electrons) | (reference_state == "zero")))):
raise ValueError(f"A molecule OR qubit + electrons number must be "
"provided when instantiating the HEA with the HF reference state. "
"For reference_state='zero', only the number of qubits is needed.")
# Ensure sufficient parameters are passed to instantiate this HEA with the given reference state
if isinstance(reference_state, Circuit):
if not bool(molecule) and not bool(n_qubits):
raise ValueError('Either a molecule or a qubit number must be specified to instantiate a HEA with a Circuit reference state.')
elif reference_state == 'HF':
if not bool(molecule) and not (bool(n_qubits) and bool(n_electrons)):
raise ValueError('Either a molecule or a qubit number + electron number must be specified to instantiate a HEA with the "HF" reference state.')
elif reference_state == 'zero':
if not bool(molecule) and not bool(n_qubits):
raise ValueError('Either a molecule or a qubit number must be specified to instantiate a HEA with the "zero" reference state.')

if n_qubits:
self.n_qubits = n_qubits
Expand Down Expand Up @@ -124,6 +131,12 @@ def set_var_params(self, var_params=None):

def prepare_reference_state(self):
"""Prepare a circuit generating the HF reference state."""

if isinstance(self.reference_state, Circuit):
ref_circuit = self.reference_state.copy()
ref_circuit.fix_variational_parameters()
return ref_circuit

if self.reference_state not in self.supported_reference_state:
raise ValueError(f"{self.reference_state} not in supported reference state methods of:{self.supported_reference_state}")

Expand Down
29 changes: 24 additions & 5 deletions tangelo/toolboxes/ansatz_generator/ilc.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ class ILC(Ansatz):
max_ilc_gens (int or None): Maximum number of generators allowed in the ansatz. If None,
one generator from each DIS group is selected. If int, then min(|DIS|, max_ilc_gens)
generators are selected in order of decreasing |dEILC/dtau|. Default, None.
reference_state (string): The reference state id for the ansatz. The
supported reference states are stored in the supported_reference_state
attributes. Default, "HF".
reference_state (string, Circuit): The reference state id for the ansatz. If a Circuit object
is passed, then a copy of this circuit overrides the qmf_circuit and its variational
parameters override qmf_var_params. The supported string reference states are stored in the
supported_reference_state attributes. Default, "HF".
"""

def __init__(self, molecule, mapping="jw", up_then_down=False, acs=None,
Expand Down Expand Up @@ -114,14 +115,24 @@ def __init__(self, molecule, mapping="jw", up_then_down=False, acs=None,
self.n_spinorbitals, self.n_electrons,
self.up_then_down, self.spin)

# If a circuit is supplied as the reference state use this as the QMF circuit
# while retaining all variational parameters:
if isinstance(reference_state, Circuit):
self.qmf_circuit = reference_state.copy()
self.qmf_var_params = [
gate.parameter for gate in reference_state._variational_gates
]
self.n_qmf_params = len(self.qmf_var_params)
else:
self.qmf_circuit = qmf_circuit
self.n_qmf_params = 2 * self.n_qubits

self.qmf_var_params = np.array(qmf_var_params) if isinstance(qmf_var_params, list) else qmf_var_params
if not isinstance(self.qmf_var_params, np.ndarray):
self.qmf_var_params = init_qmf_from_hf(self.n_spinorbitals, self.n_electrons,
self.mapping, self.up_then_down, self.spin)
if self.qmf_var_params.size != 2 * self.n_qubits:
raise ValueError("The number of QMF variational parameters must be 2 * n_qubits.")
self.n_qmf_params = 2 * self.n_qubits
self.qmf_circuit = qmf_circuit

self.acs = acs
self.ilc_tau_guess = ilc_tau_guess
Expand Down Expand Up @@ -172,6 +183,7 @@ def set_var_params(self, var_params=None):
# Initialize ILC parameters by matrix diagonalization (see Appendix B, Refs. 1 & 2).
elif var_params == "diag":
initial_var_params = get_ilc_params_by_diag(self.qubit_ham, self.acs, self.qmf_var_params)

# Insert the QMF variational parameters at the beginning.
initial_var_params = np.concatenate((self.qmf_var_params, initial_var_params))
else:
Expand All @@ -187,11 +199,18 @@ def prepare_reference_state(self):
wavefunction with HF, multi-reference state, etc). These preparations must be consistent
with the transform used to obtain the qubit operator. """

# Note: because reference state parameters are needed in this ansatz, the reference state circuit
# is simply copied and stored in self.qmf_circuit:
if isinstance(self.reference_state, Circuit):
return self.qmf_circuit

if self.reference_state not in self.supported_reference_state:
raise ValueError(f"Only supported reference state methods are: "
f"{self.supported_reference_state}.")

if self.reference_state == "HF":
reference_state_circuit = get_qmf_circuit(self.qmf_var_params, True)

return reference_state_circuit

def build_circuit(self, var_params=None):
Expand Down
10 changes: 8 additions & 2 deletions tangelo/toolboxes/ansatz_generator/puccd.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ class pUCCD(Ansatz):
Args:
molecule (SecondQuantizedMolecule): Self-explanatory.
reference_state (string): String refering to an initial state.
Default: "HF".
reference_state (string, Circuit): String refering to an initial state.
Can also be a Circuit object, in which case a copy of
circuit with variational parameters fixed is used. Default: "HF".
"""

def __init__(self, molecule, reference_state="HF"):
Expand Down Expand Up @@ -100,6 +101,11 @@ def prepare_reference_state(self):
the qubit operator.
"""

if isinstance(self.reference_state, Circuit):
reference_state_circuit = self.reference_state.copy()
reference_state_circuit.fix_variational_parameters()
return reference_state_circuit

if self.reference_state not in self.supported_reference_state:
raise ValueError(f"Only supported reference state methods are:{self.supported_reference_state}")

Expand Down
26 changes: 21 additions & 5 deletions tangelo/toolboxes/ansatz_generator/qcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ class QCC(Ansatz):
max_qcc_gens (int or None): Maximum number of generators allowed in the ansatz. If None,
one generator from each DIS group is selected. If int, then min(|DIS|, max_qcc_gens)
generators are selected in order of decreasing |dEQCC/dtau|. Default, None.
reference_state (string): The reference state id for the ansatz. The
supported reference states are stored in the supported_reference_state
attributes. Default, "HF".
reference_state (string): The reference state id for the ansatz. If a Circuit object
is passed, then a copy of this circuit overrides the qmf_circuit and its variational
parameters override qmf_var_params. The supported reference states are stored in the
supported_reference_state attributes. Default, "HF".
"""

def __init__(self, molecule, mapping="jw", up_then_down=False, dis=None,
Expand Down Expand Up @@ -121,14 +122,24 @@ def __init__(self, molecule, mapping="jw", up_then_down=False, dis=None,
self.n_spinorbitals, self.n_electrons,
self.up_then_down, self.spin)

# If a circuit is supplied as the reference state use this as the QMF circuit
# while retaining all variational parameters:
if isinstance(reference_state, Circuit):
self.qmf_circuit = reference_state.copy()
self.qmf_var_params = [
gate.parameter for gate in reference_state._variational_gates
]
self.n_qmf_params = len(self.qmf_var_params)
else:
self.qmf_circuit = qmf_circuit
self.n_qmf_params = 2 * self.n_qubits

self.qmf_var_params = np.array(qmf_var_params) if isinstance(qmf_var_params, list) else qmf_var_params
if not isinstance(self.qmf_var_params, np.ndarray):
self.qmf_var_params = init_qmf_from_hf(self.n_spinorbitals, self.n_electrons,
self.mapping, self.up_then_down, self.spin)
if self.qmf_var_params.size != 2 * self.n_qubits:
raise ValueError("The number of QMF variational parameters must be 2 * n_qubits.")
self.n_qmf_params = 2 * self.n_qubits
self.qmf_circuit = qmf_circuit

self.dis = dis
self.qcc_tau_guess = qcc_tau_guess
Expand Down Expand Up @@ -191,6 +202,11 @@ def prepare_reference_state(self):
wavefunction with HF, multi-reference state, etc). These preparations must be consistent
with the transform used to obtain the qubit operator. """

# Note: because reference state parameters are needed in this ansatz, the reference state circuit
# is simply copied and stored in self.qmf_circuit:
if isinstance(self.reference_state, Circuit):
return self.qmf_circuit

if self.reference_state not in self.supported_reference_state:
raise ValueError(f"Only supported reference state methods are: "
f"{self.supported_reference_state}.")
Expand Down
Loading

0 comments on commit 3b49634

Please sign in to comment.