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

Add QCQMC unit tests and speed up actions #353

Merged
merged 13 commits into from
Jul 1, 2024
2 changes: 2 additions & 0 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ jobs:
run: |
pip install pytest
# RECIRQ_IMPORT_FAILSAFE: skip tests on unsupported Cirq configurations
# EXPORT_OMP_NUM_THREADS: pyscf has poor openmp performance which slows down qcqmc tests.
export OMP_NUM_THREADS=1
RECIRQ_IMPORT_FAILSAFE=y pytest -v

nbformat:
Expand Down
2 changes: 1 addition & 1 deletion recirq/qcqmc/afqmc_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def get_charge_charge_generator(indices: Tuple[int, int]) -> of.FermionOperator:
"""Returns the generator for density evolution between the indices

Args:
indices: The indices to for charge-charge terms.:w
indices: The indices to for charge-charge terms.

Returns:
The generator for density evolution for this pair of electrons.
Expand Down
125 changes: 125 additions & 0 deletions recirq/qcqmc/afqmc_generators_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Copyright 2024 Google
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for afqmc_generators.py."""

import openfermion as of

from recirq.qcqmc import afqmc_generators, layer_spec


def test_get_pp_plus_gate_generators():
n_elec = 4
heuristic_layers = (
layer_spec.LayerSpec(base_gate="charge_charge", layout="in_pair"),
)
gate_generators = afqmc_generators.get_pp_plus_gate_generators(
n_elec=n_elec, heuristic_layers=heuristic_layers, do_pp=False
)
assert len(gate_generators) == 4
assert gate_generators[0] == of.FermionOperator("2^ 2 4^ 4", 1.0)
assert gate_generators[1] == of.FermionOperator("3^ 3 5^ 5", 1.0)
assert gate_generators[2] == of.FermionOperator("0^ 0 6^ 6", 1.0)
assert gate_generators[3] == of.FermionOperator("1^ 1 7^ 7", 1.0)

gate_generators_w_pp = afqmc_generators.get_pp_plus_gate_generators(
n_elec=n_elec, heuristic_layers=heuristic_layers, do_pp=True
)
assert len(gate_generators_w_pp) == 6
assert gate_generators_w_pp[0] == of.FermionOperator(
"2 3 4^ 5^", -1.0j
) + of.FermionOperator("5 4 3^ 2^", +1.0j)
assert gate_generators_w_pp[1] == of.FermionOperator(
"0 1 6^ 7^", -1.0j
) + of.FermionOperator("7 6 1^ 0^", 1.0j)
assert gate_generators_w_pp[2:] == gate_generators


def test_get_pair_hopping_gate_generators():
n_pairs = 2
n_elec = 4
gate_generators = afqmc_generators.get_pair_hopping_gate_generators(
n_pairs=n_pairs, n_elec=n_elec
)
assert len(gate_generators) == 2
assert gate_generators[0] == of.FermionOperator(
"2 3 4^ 5^", -1.0j
) + of.FermionOperator("5 4 3^ 2^", 1.0j)
assert gate_generators[1] == of.FermionOperator(
"0 1 6^ 7^", -1.0j
) + of.FermionOperator("7 6 1^ 0^", 1.0j)


def test_get_charge_charge_generator():
indices = (2, 3)
gate_generator = afqmc_generators.get_charge_charge_generator(indices=indices)
assert gate_generator == of.FermionOperator("2^ 2 3^ 3", 1.0)


def test_get_givens_generator():
indices = (2, 3)
gate_generator = afqmc_generators.get_givens_generator(indices=indices)
assert gate_generator == of.FermionOperator("2^ 3", 1.0j) - of.FermionOperator(
"3^ 2", 1.0j
)


def test_get_layer_generators():
n_elec = 4
layer_spec_in_pair = layer_spec.LayerSpec(
base_gate="charge_charge", layout="in_pair"
)
gate_generators = afqmc_generators.get_layer_generators(
layer_spec=layer_spec_in_pair, n_elec=n_elec
)
assert len(gate_generators) == 4
assert gate_generators[0] == of.FermionOperator("2^ 2 4^ 4", 1.0)
assert gate_generators[1] == of.FermionOperator("3^ 3 5^ 5", 1.0)
assert gate_generators[2] == of.FermionOperator("0^ 0 6^ 6", 1.0)
assert gate_generators[3] == of.FermionOperator("1^ 1 7^ 7", 1.0)

layer_spec_cross_pair = layer_spec.LayerSpec(
base_gate="givens", layout="cross_pair"
)
gate_generators = afqmc_generators.get_layer_generators(
layer_spec=layer_spec_cross_pair, n_elec=n_elec
)
assert len(gate_generators) == 2
assert gate_generators[0] == of.FermionOperator("0^ 4", -1.0j) + of.FermionOperator(
"4^ 0", 1.0j
)
assert gate_generators[1] == of.FermionOperator("1^ 5", -1.0j) + of.FermionOperator(
"5^ 1", 1.0j
)


def test_get_heuristic_gate_generators():
n_elec = 4
heuristic_layers = (
layer_spec.LayerSpec(base_gate="charge_charge", layout="in_pair"),
layer_spec.LayerSpec(base_gate="givens", layout="cross_pair"),
)
gate_generators = afqmc_generators.get_heuristic_gate_generators(
n_elec=n_elec, layer_specs=heuristic_layers
)
assert len(gate_generators) == 6
assert gate_generators[0] == of.FermionOperator("2^ 2 4^ 4", 1.0)
assert gate_generators[1] == of.FermionOperator("3^ 3 5^ 5", 1.0)
assert gate_generators[2] == of.FermionOperator("0^ 0 6^ 6", 1.0)
assert gate_generators[3] == of.FermionOperator("1^ 1 7^ 7", 1.0)
assert gate_generators[4] == of.FermionOperator("0^ 4", -1.0j) + of.FermionOperator(
"4^ 0", 1.0j
)
assert gate_generators[5] == of.FermionOperator("1^ 5", -1.0j) + of.FermionOperator(
"5^ 1", 1.0j
)
2 changes: 1 addition & 1 deletion recirq/qcqmc/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def get_ansatz_qubit_wf(
def get_two_body_params_from_qchem_amplitudes(
qchem_amplitudes: np.ndarray,
) -> np.ndarray:
"""Translates perfect pairing amplitudes from qchem to rotation angles.
r"""Translates perfect pairing amplitudes from qchem to rotation angles.

qchem style: 1 |1100> + t_i |0011>
our style: cos(\theta_i) |1100> + sin(\theta_i) |0011>
Expand Down
Empty file added recirq/qcqmc/converters_test.py
Empty file.
26 changes: 15 additions & 11 deletions recirq/qcqmc/hamiltonian_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,16 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import cirq
import numpy as np
import pytest
from openfermion import (
get_fermion_operator,
get_ground_state,
get_number_preserving_sparse_operator,
)
from openfermion import (get_fermion_operator, get_ground_state,
get_number_preserving_sparse_operator)

from recirq.qcqmc.hamiltonian import (
HamiltonianFileParams,
PyscfHamiltonianParams,
build_hamiltonian_from_file,
build_hamiltonian_from_pyscf,
)
from recirq.qcqmc.hamiltonian import (HamiltonianFileParams,
PyscfHamiltonianParams,
build_hamiltonian_from_file,
build_hamiltonian_from_pyscf)


def test_load_from_file_hamiltonian_runs():
Expand All @@ -38,6 +34,14 @@ def test_load_from_file_hamiltonian_runs():
assert hamiltonian_data.two_body_integrals_pqrs.shape == (2, 2, 2, 2)


def test_hamiltonian_serialize():
params = HamiltonianFileParams(
name="test hamiltonian", integral_key="fh_sto3g", n_orb=2, n_elec=2
)
params2 = cirq.read_json(json_text=cirq.to_json(params))
assert params2 == params


@pytest.mark.parametrize(
"integral_key, n_orb, n_elec, do_eri_restore",
[
Expand Down
70 changes: 70 additions & 0 deletions recirq/qcqmc/layer_spec_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright 2024 Google
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import cirq
import pytest

from recirq.qcqmc import layer_spec


@pytest.mark.parametrize("base_gate", ("charge_charge", "givens"))
@pytest.mark.parametrize("layout", ("in_pair", "cross_pair", "cross_spin"))
def test_layer_spec(base_gate, layout):
ls = layer_spec.LayerSpec(base_gate=base_gate, layout=layout)
ls2 = cirq.read_json(json_text=cirq.to_json(ls))
assert ls2 == ls
with pytest.raises(ValueError, match=r"base_gate is set*"):
ls = layer_spec.LayerSpec(base_gate=base_gate + "y", layout=layout)
with pytest.raises(ValueError, match=r"layout is set*"):
ls = layer_spec.LayerSpec(base_gate=base_gate, layout=layout + "y")
with pytest.raises(ValueError, match=r"base_gate is set*"):
ls = layer_spec.LayerSpec(base_gate=base_gate + "x", layout=layout + "y")


@pytest.mark.parametrize("n_elec", range(1, 10))
def test_get_indices_heuristic_layer_cross_pair(n_elec):
n_pairs = max(n_elec // 2 - 1, 0)
n_terms = 0
for x in layer_spec.get_indices_heuristic_layer_cross_pair(n_elec):
assert len(x) == 2
if n_terms % 2 == 0:
assert x[0] + x[1] == 2 * n_elec - 4
else:
assert x[0] + x[1] == 2 * n_elec - 2
n_terms += 1
assert n_terms == 2 * n_pairs


@pytest.mark.parametrize("n_elec", range(1, 10))
def test_get_indices_heuristic_layer_cross_spin(n_elec):
n_pairs = n_elec // 2
n_terms = 0
for x in layer_spec.get_indices_heuristic_layer_cross_spin(n_elec):
assert len(x) == 2
assert x[1] - x[0] == 1
n_terms += 1
assert n_terms == 2 * n_pairs


@pytest.mark.parametrize("n_elec", range(1, 10))
def test_get_indices_heuristic_layer_in_pair(n_elec):
n_pairs = n_elec // 2
n_terms = 0
for x in layer_spec.get_indices_heuristic_layer_in_pair(n_elec):
assert len(x) == 2
if n_terms % 2 == 0:
assert x[1] + x[0] == 2 * n_elec - 2
else:
assert x[1] + x[0] == 2 * n_elec
n_terms += 1
assert n_terms == 2 * n_pairs
4 changes: 2 additions & 2 deletions recirq/qcqmc/optimize_wf_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def test_pp_plus_wf_energy_sloppy_1(fixture_8_qubit_ham: HamiltonianData):
trial_wf = build_pp_plus_trial_wavefunction(
params,
dependencies={fixture_8_qubit_ham.params: fixture_8_qubit_ham},
do_print=True,
do_print=False,
)

assert trial_wf.ansatz_energy < -1.947
Expand All @@ -133,7 +133,7 @@ def test_diamond_pp_wf_energy(fixture_12_qubit_ham: HamiltonianData):
trial_wf = build_pp_plus_trial_wavefunction(
params,
dependencies={fixture_12_qubit_ham.params: fixture_12_qubit_ham},
do_print=True,
do_print=False,
)

assert trial_wf.ansatz_energy < -10.4
Expand Down
Loading
Loading