Skip to content

Commit

Permalink
Update tutorial and docs Action. Add mergify.
Browse files Browse the repository at this point in the history
  • Loading branch information
caleb-johnson committed Sep 8, 2024
1 parent 89e947c commit 92d1181
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 69 deletions.
37 changes: 20 additions & 17 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ on:
branches:
- main
- 'stable/**'

jobs:
build_and_deploy_docs:
build:
name: Build docs
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
Expand All @@ -34,21 +34,24 @@ jobs:
shell: bash
run: |
tox -edocs
- name: Prepare docs artifact
if: always()
shell: bash
run: |
mkdir artifact
cp -a docs/_build/html artifact/addon_sqd_html_docs
- name: Upload docs artifact
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-pages-artifact@v3
with:
name: addon_sqd_html_docs
path: ./artifact
- name: Deploy docs
if: ${{ github.ref == 'refs/heads/stable/0.3' }}
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/_build/html/
path: docs/_build/html

deploy:
name: Deploy docs
if: ${{ github.ref == 'refs/heads/stable/0.3' }}
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
8 changes: 8 additions & 0 deletions .mergify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pull_request_rules:
- name: backport
conditions:
- label=stable backport potential
actions:
backport:
branches:
- stable/0.3
102 changes: 50 additions & 52 deletions docs/tutorials/01_getting_started_fermionic.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,33 @@
"source": [
"# Improving energy estimation of a Fermionic Hamiltonian with SQD\n",
"\n",
"In this tutorial, we will show how to use the `sqd` package to post-process quantum samples using the [self-consistent configuration recovery technique](https://arxiv.org/abs/2405.05068).\n",
"\n",
"This technique can be done iteratively by repeating 4 steps:\n",
"\n",
"- Correct the full set of bitstring samples, using *a priori* knowledge of particle number and the most updated orbital occupancy information obtained from the ground state approximations at each iteration.\n",
" \n",
"- Probabilistically create batches of subsamples from corrected bitstrings.\n",
" \n",
"- Project and diagonalize the molecular Hamiltonian over each sampled subspace.\n",
" \n",
"- Save the minimum ground state energy found across all batches and update the avg orbital occupancy.\n",
"\n",
"In this tutorial we implement a [Qiskit patterns](https://docs.quantum.ibm.com/guides/intro-to-patterns) for post-processing quantum samples to find good ground state approximations:\n",
"1. **Map** problem to a quantum circuit\n",
"2. **Optimize** for target hardware\n",
"3. **Execute** on target hardware\n",
"4. **Post-process** results (using *SQD*)"
"In this tutorial, we will implement a [Qiskit pattern](https://docs.quantum.ibm.com/guides/intro-to-patterns) showing how to post-process noisy quantum samples using the sample-based quantum diagonalization (SQD) technique introduced by [Robledo Moreno et al., 2024](https://arxiv.org/abs/2405.05068).\n",
"\n",
"The pattern we will implement can be described in four steps:\n",
"\n",
"- **Step 1: Map to quantum problem**\n",
" - Generate ansatz for estimating the ground state\n",
"- **Step 2: Optimize the problem**\n",
" - Transpile the ansatz for the backend\n",
"- **Step 3: Execute the experiments**\n",
" - Draw samples from the ansatz using the ``Sampler`` primitive\n",
"- **Step 4: Reconstruct results**\n",
" - ***Estimate the ground state using SQD as described below.***\n",
" \n",
"The SQD workflow in step 4 is implemented by repeating the following steps in a loop:\n",
"\n",
" - Refine the full set of bitstring samples, using prior knowledge of the particle number and the average orbital occupancy from the most recent iteration.\n",
" - Probabilistically create batches of subsamples from refined bitstrings.\n",
" - Project and diagonalize the molecular Hamiltonian over each sampled subspace.\n",
" - Save the minimum ground state energy found across all batches and update the average orbital occupancy."
]
},
{
"cell_type": "markdown",
"id": "afeb054c",
"metadata": {},
"source": [
"## Step 1: Map problem to a quantum circuit\n",
"## Step 1: Map to quantum problem\n",
"\n",
"In this tutorial, we will approximate the ground state energy of an $N_2$ molecule. First, we will specify the molecule and its properties. Next, we will create a [local unitary cluster Jastrow (LUCJ)](https://pubs.rsc.org/en/content/articlelanding/2023/sc/d3sc02516k) ansatz (quantum circuit) to generate samples from a quantum computer for ground state energy estimation."
]
Expand All @@ -41,7 +43,7 @@
"id": "a6755afb-ca1e-4473-974b-ba89acc8abce",
"metadata": {},
"source": [
"### Specify the molecule and its properties"
"First, we will specify the molecule and its properties."
]
},
{
Expand Down Expand Up @@ -83,9 +85,7 @@
"id": "96bfe018",
"metadata": {},
"source": [
"### Create the `LUCJ` Ansatz\n",
"\n",
"The `LUCJ` ansatz is a parameterized quantum circuit, and we will initialize it with `t2` and `t1` amplitudes obtained from a CCSD calculation."
"Next, we will create the ansatz. The `LUCJ` ansatz is a parameterized quantum circuit, and we will initialize it with `t2` and `t1` amplitudes obtained from a CCSD calculation."
]
},
{
Expand All @@ -106,7 +106,7 @@
"name": "stderr",
"output_type": "stream",
"text": [
"Overwritten attributes get_hcore get_ovlp of <class 'pyscf.scf.hf_symm.SymAdaptedRHF'>\n"
"Overwritten attributes get_ovlp get_hcore of <class 'pyscf.scf.hf_symm.SymAdaptedRHF'>\n"
]
}
],
Expand Down Expand Up @@ -177,15 +177,15 @@
"id": "db11bf6d",
"metadata": {},
"source": [
"## Step 2: Optimize for target hardware"
"## Step 2: Optimize the problem"
]
},
{
"cell_type": "markdown",
"id": "0760b3f3",
"metadata": {},
"source": [
"Next, we will optimize our circuit for a target hardware. We need to choose the hardware device to use before optimizing our circuit. We will use a fake 127-qubit backend from ``qiskit_ibm_runtime`` to emulate a real device."
"Next, we will optimize our circuit for a target backend. We need to choose the device to use before optimizing our circuit. We will use a fake 127-qubit backend from ``qiskit_ibm_runtime`` to emulate a real device."
]
},
{
Expand Down Expand Up @@ -223,8 +223,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Gate counts (w/o pre-init passes): OrderedDict({'rz': 7421, 'sx': 6016, 'ecr': 2240, 'x': 324, 'measure': 32, 'barrier': 1})\n",
"Gate counts (w/ pre-init passes): OrderedDict({'rz': 4155, 'sx': 3186, 'ecr': 1262, 'x': 210, 'measure': 32, 'barrier': 1})\n"
"Gate counts (w/o pre-init passes): OrderedDict({'rz': 7402, 'sx': 6009, 'ecr': 2232, 'x': 318, 'measure': 32, 'barrier': 1})\n",
"Gate counts (w/ pre-init passes): OrderedDict({'rz': 4159, 'sx': 3185, 'ecr': 1262, 'x': 209, 'measure': 32, 'barrier': 1})\n"
]
}
],
Expand Down Expand Up @@ -255,7 +255,7 @@
"id": "0cc1edef",
"metadata": {},
"source": [
"## Step 3: Execute on target hardware"
"## Step 3: Execute the experiments"
]
},
{
Expand Down Expand Up @@ -294,17 +294,15 @@
"id": "6df05b6e",
"metadata": {},
"source": [
"## Step 4: Post-process results"
"## Step 4: Reconstruct results"
]
},
{
"cell_type": "markdown",
"id": "851bc98e-9c08-4e78-9472-36301abc11d8",
"metadata": {},
"source": [
"### Transform the counts into a bitstring matrix and probability array for post-processing\n",
"\n",
"In order to speed up the bitwise processing required in this workflow, we use Numpy arrays to hold representations of the bitstrings and sampling frequencies."
"First, we will transform the counts into a bitstring matrix and probability array for post-processing. Each row in the matrix represents one unique bitstring. Since qubits are normally indexed from the right of a bitstring, column `0` of the matrix represents qubit `N`, and column `N` represents qubit `0`."
]
},
{
Expand All @@ -329,8 +327,9 @@
"### Iteratively refine the samples using SQD and approximate the ground state\n",
"\n",
"There are a few user-controlled options which are important for this technique:\n",
"- ``iterations``: Number of self-consistent configuration recovery iterations\n",
"- ``n_batches``: Number of batches of configurations used by the different calls to the eigenstate solver\n",
"\n",
"- ``iterations``: Number of self-consistent iterations to perform SQD\n",
"- ``n_batches``: Number of batches of configurations\n",
"- ``samples_per_batch``: Number of unique configurations to include in each batch\n",
"- ``max_davidson_cycles``: Maximum number of Davidson cycles run by each eigensolver"
]
Expand Down Expand Up @@ -374,17 +373,16 @@
"e_hist = np.zeros((iterations, n_batches)) # energy history\n",
"s_hist = np.zeros((iterations, n_batches)) # spin history\n",
"occupancy_hist = np.zeros((iterations, 2 * num_orbitals))\n",
"occupancies_bitwise = None # orbital i corresponds to column i in bitstring matrix\n",
"occupancies_bitwise = None # index i corresponds to column i in bitstring matrix\n",
"for i in range(iterations):\n",
" print(f\"Starting configuration recovery iteration {i}\")\n",
" # On the first iteration, we have no orbital occupancy information from the\n",
" # solver, so we just post-select from the full bitstring set based on hamming weight.\n",
" # solver, so we just post-select on the full set of noisy configurations\n",
" if occupancies_bitwise is None:\n",
" bs_mat_tmp = bitstring_matrix_full\n",
" probs_arr_tmp = probs_arr_full\n",
"\n",
" # In following iterations, we use both the occupancy info and the target hamming\n",
" # weight to refine bitstrings.\n",
" # If we have average orbital occupancy information, we use it to refine the full set of noisy configurations\n",
" else:\n",
" bs_mat_tmp, probs_arr_tmp = recover_configurations(\n",
" bitstring_matrix_full,\n",
Expand All @@ -395,7 +393,7 @@
" rand_seed=rand_seed,\n",
" )\n",
"\n",
" # Throw out samples with incorrect hamming weight and create batches of subsamples.\n",
" # Throw out configurations with incorrect particle number in either the spin-up or spin-down systems\n",
" batches = postselect_and_subsample(\n",
" bs_mat_tmp,\n",
" probs_arr_tmp,\n",
Expand All @@ -407,10 +405,10 @@
" )\n",
"\n",
" # Run eigenstate solvers in a loop. This loop should be parallelized for larger problems.\n",
" int_e = np.zeros(n_batches)\n",
" int_s = np.zeros(n_batches)\n",
" int_occs = np.zeros((n_batches, 2 * num_orbitals))\n",
" cs = []\n",
" e_batch = np.zeros(n_batches)\n",
" s_batch = np.zeros(n_batches)\n",
" occs_batch = np.zeros((n_batches, 2 * num_orbitals))\n",
" wf_amps = []\n",
" for j in range(n_batches):\n",
" addresses = bitstring_matrix_to_sorted_addresses(batches[j], open_shell=open_shell)\n",
" energy_sci, coeffs_sci, avg_occs, spin = solve_fermion(\n",
Expand All @@ -421,20 +419,20 @@
" max_davidson=max_davidson_cycles,\n",
" )\n",
" energy_sci += nuclear_repulsion_energy\n",
" int_e[j] = energy_sci\n",
" int_s[j] = spin\n",
" int_occs[j, :num_orbitals] = avg_occs[0]\n",
" int_occs[j, num_orbitals:] = avg_occs[1]\n",
" cs.append(coeffs_sci)\n",
" e_batch[j] = energy_sci\n",
" s_batch[j] = spin\n",
" occs_batch[j, :num_orbitals] = avg_occs[0]\n",
" occs_batch[j, num_orbitals:] = avg_occs[1]\n",
" wf_amps.append(coeffs_sci)\n",
"\n",
" # Combine batch results\n",
" avg_occupancy = np.mean(int_occs, axis=0)\n",
" avg_occupancy = np.mean(occs_batch, axis=0)\n",
" # The occupancies from the solver should be flipped to match the bits in the bitstring matrix.\n",
" occupancies_bitwise = flip_orbital_occupancies(avg_occupancy)\n",
"\n",
" # Track optimization history\n",
" e_hist[i, :] = int_e\n",
" s_hist[i, :] = int_s\n",
" e_hist[i, :] = e_batch\n",
" s_hist[i, :] = s_batch\n",
" occupancy_hist[i, :] = avg_occupancy"
]
},
Expand All @@ -445,7 +443,7 @@
"source": [
"### Visualize the results\n",
"\n",
"The first plot shows that after a couple of iterations we estimate the ground state energy within ``~200 mH``. Remember, the quantum samples in this demo were pure noise. The signal here comes from *a priori* knowledge of the electronic structure and molecular Hamiltonian.\n",
"The first plot shows that after a couple of iterations we estimate the ground state energy within ``~200 mH``. Remember, the quantum samples in this demo were pure noise. The signal here comes from prior knowledge of the electronic structure and molecular Hamiltonian.\n",
"\n",
"The second plot shows the average occupancy of each spatial orbital after the final iteration. We can see that both the spin-up and spin-down electrons occupy the first five orbitals with high probability in our solutions."
]
Expand Down

0 comments on commit 92d1181

Please sign in to comment.