Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop into main to test automated workflow for Sphinx documentation. #397

Merged
merged 7 commits into from
Jun 18, 2024
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

This file documents the main changes between versions of the code.

## [0.4.3] - 2024-05-21

### Added

- DMET: HF and MP2 solvers
- DMET: fragment active space can be specified by usrs as a callable function (see DMET notebook)

### Changed

- Copyrights (SandboxAQ 2024)
- Call to qiskit state vector simulator and IBM Q hardware experiment submissions (compatibility with Qiskit v1.0)


### Deprecated / Removed


## [0.4.2] - 2023-12-20

### Added
Expand Down
2 changes: 1 addition & 1 deletion tangelo/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@

""" Define version number here. It is read in setup.py, and bumped automatically
when using the new release Github action. """
__version__ = "0.4.2"
__version__ = "0.4.3"
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
Loading
Loading