diff --git a/qhbmlib/__init__.py b/qhbmlib/__init__.py index 6d861eab..591c715b 100644 --- a/qhbmlib/__init__.py +++ b/qhbmlib/__init__.py @@ -12,21 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Import QHBM library modules.*""" +"""Defines the qhbmlib package.""" -from qhbmlib import architectures -from qhbmlib import circuit_infer -from qhbmlib import circuit_infer_utils -from qhbmlib import circuit_model -from qhbmlib import circuit_model_utils -from qhbmlib import energy_infer -from qhbmlib import energy_infer_utils -from qhbmlib import energy_model -from qhbmlib import energy_model_utils -from qhbmlib import hamiltonian_infer -from qhbmlib import hamiltonian_infer_utils -from qhbmlib import hamiltonian -from qhbmlib import qmhl_loss -from qhbmlib import quantum_data +from qhbmlib import data +from qhbmlib import inference +from qhbmlib import models from qhbmlib import utils -from qhbmlib import vqt_loss diff --git a/qhbmlib/architectures.py b/qhbmlib/architectures.py deleted file mode 100644 index b5967bf6..00000000 --- a/qhbmlib/architectures.py +++ /dev/null @@ -1,197 +0,0 @@ -# Copyright 2021 The QHBM Library Authors. All Rights Reserved. -# -# 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. -# ============================================================================== -"""Selection of quantum circuits architectures used in defining QHBMs.""" - -import cirq -import sympy -import tensorflow_quantum as tfq - -# ============================================================================ # -# HEA components. -# ============================================================================ # - - -def get_xz_rotation(q, a, b): - """Two-axis single qubit rotation.""" - return cirq.Circuit(cirq.X(q)**a, cirq.Z(q)**b) - - -def get_xyz_rotation(q, a, b, c): - """General single qubit rotation.""" - return cirq.Circuit(cirq.X(q)**a, cirq.Y(q)**b, cirq.Z(q)**c) - - -def get_cz_exp(q0, q1, a): - """Exponent of entangling CZ gate.""" - return cirq.Circuit(cirq.CZPowGate(exponent=a)(q0, q1)) - - -def get_zz_exp(q0, q1, a): - """Exponent of entangling ZZ gate.""" - return cirq.Circuit(cirq.ZZPowGate(exponent=a)(q0, q1)) - - -def get_xz_rotation_layer(qubits, layer_num, name): - """Apply two-axis single qubit rotations to all the given qubits.""" - circuit = cirq.Circuit() - for n, q in enumerate(qubits): - sx, sz = sympy.symbols("sx_{0}_{1}_{2} sz_{0}_{1}_{2}".format( - name, layer_num, n)) - circuit += get_xz_rotation(q, sx, sz) - return circuit - - -def get_cz_exp_layer(qubits, layer_num, name): - """Apply parameterized CZ gates to all pairs of nearest-neighbor qubits.""" - circuit = cirq.Circuit() - for n, (q0, q1) in enumerate(zip(qubits[::2], qubits[1::2])): - a = sympy.symbols("sc_{0}_{1}_{2}".format(name, layer_num, 2 * n)) - circuit += get_cz_exp(q0, q1, a) - shifted_qubits = qubits[1::] - for n, (q0, q1) in enumerate(zip(shifted_qubits[::2], shifted_qubits[1::2])): - a = sympy.symbols("sc_{0}_{1}_{2}".format(name, layer_num, 2 * n + 1)) - circuit += get_cz_exp(q0, q1, a) - return circuit - - -def get_hardware_efficient_model_unitary(qubits, num_layers, name): - """Build our full parameterized model unitary.""" - circuit = cirq.Circuit() - for layer_num in range(num_layers): - new_circ = get_xz_rotation_layer(qubits, layer_num, name) - circuit += new_circ - if len(qubits) > 1: - new_circ = get_cz_exp_layer(qubits, layer_num, name) - circuit += new_circ - return circuit - - -def get_zz_exp_layer(qubits, layer_num, name): - """Apply ZZ gates to all pairs of nearest-neighbor qubits.""" - circuit = cirq.Circuit() - for n, (q0, q1) in enumerate(zip(qubits[::2], qubits[1::2])): - a = sympy.symbols("sc_{0}_{1}_{2}".format(name, layer_num, 2 * n)) - circuit += get_zz_exp(q0, q1, a) - shifted_qubits = qubits[1::] - for n, (q0, q1) in enumerate(zip(shifted_qubits[::2], shifted_qubits[1::2])): - a = sympy.symbols("sc_{0}_{1}_{2}".format(name, layer_num, 2 * n + 1)) - circuit += get_zz_exp(q0, q1, a) - return circuit - - -def hea_1d_zz(qubits, num_layers, name): - """Build our full parameterized model unitary.""" - circuit = cirq.Circuit() - for layer_num in range(num_layers): - new_circ = get_xz_rotation_layer(qubits, layer_num, name) - circuit += new_circ - if len(qubits) > 1: - new_circ = get_zz_exp_layer(qubits, layer_num, name) - circuit += new_circ - return circuit - - -# ============================================================================ # -# 2D HEA. -# ============================================================================ # - - -def get_2d_xz_rotation_layer(rows, cols, layer_num, name): - """Apply single qubit rotations on a grid of qubits.""" - circuit = cirq.Circuit() - for r in range(rows): - for c in range(cols): - sx = sympy.Symbol(f"sx_{name}_{layer_num}_{r}_{c}") - sz = sympy.Symbol(f"sz_{name}_{layer_num}_{r}_{c}") - circuit += get_xz_rotation(cirq.GridQubit(r, c), sx, sz) - return circuit - - -def get_2d_cz_exp_layer(rows, cols, layer_num, name): - """Apply CZ gates to all pairs of nearest-neighbor qubits on a grid.""" - circuit = cirq.Circuit() - # Apply horizontal bonds - for r in range(rows): - for par in [0, 1]: - for q_c_0, q_c_1 in zip(range(par, cols, 2), range(par + 1, cols, 2)): - scz = sympy.Symbol(f"scz_{name}_{layer_num}_row{r}_{q_c_0}_{q_c_1}") - circuit += get_cz_exp( - cirq.GridQubit(r, q_c_0), cirq.GridQubit(r, q_c_1), scz) - # Apply vertical bonds - for c in range(cols): - for par in [0, 1]: - for q_r_0, q_r_1 in zip(range(par, rows, 2), range(par + 1, rows, 2)): - scz = sympy.Symbol(f"scz_{name}_{layer_num}_col{c}_{q_r_0}_{q_r_1}") - circuit += get_cz_exp( - cirq.GridQubit(q_r_0, c), cirq.GridQubit(q_r_1, c), scz) - return circuit - - -def get_2d_hea(rows, cols, num_layers, name): - """Build a 2D HEA ansatz. - - Args: - rows: int specifying the number of rows in the ansatz. - cols: int specifying the number of columns in the ansatz. - num_layers: int specifying how many layers of 2D HEA to apply. - name: string which will be included in the parameters of the ansatz. - - Returns: - circuit: `cirq.Circuit` which is the ansatz. - """ - circuit = cirq.Circuit() - for layer in range(num_layers): - xz_circuit = get_2d_xz_rotation_layer(rows, cols, layer, name) - circuit += xz_circuit - cz_circuit = get_2d_cz_exp_layer(rows, cols, layer, name) - circuit += cz_circuit - return circuit - - -def get_2d_xyz_rotation_layer(rows, cols, layer_num, name): - """Apply single qubit rotations on a grid of qubits.""" - circuit = cirq.Circuit() - for r in range(rows): - for c in range(cols): - sx = sympy.Symbol(f"sx_{name}_{layer_num}_{r}_{c}") - sy = sympy.Symbol(f"sy_{name}_{layer_num}_{r}_{c}") - sz = sympy.Symbol(f"sz_{name}_{layer_num}_{r}_{c}") - circuit += get_xyz_rotation(cirq.GridQubit(r, c), sx, sy, sz) - return circuit - - -# ============================================================================ # -# Trotter components. -# ============================================================================ # - - -def get_trotter_model_unitary(p, h_list, name): - """Get a trotterized ansatz. - - Args: - p: integer representing the number of QAOA steps. - h_list: List of `cirq.PauliSum`s representing the Hamiltonians to - exponentiate to build the circuit. - name: string used to make symbols unique to this call. - - Returns: - circuit: `cirq.Circuit` representing the parameterized QAOA ansatz. - """ - circuit = cirq.Circuit() - for j in range(p): - for n, h in enumerate(h_list): - new_symb = sympy.Symbol("phi_{0}_L{1}_H{2}".format(name, j, n)) - circuit += tfq.util.exponential([h], coefficients=[new_symb]) - return circuit diff --git a/qhbmlib/data/__init__.py b/qhbmlib/data/__init__.py new file mode 100644 index 00000000..d2cda788 --- /dev/null +++ b/qhbmlib/data/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2021 The QHBM Library Authors. All Rights Reserved. +# +# 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. +# ============================================================================== +"""Defines the qbmlib.data package.""" + +from qhbmlib.data.qhbm_data import QHBMData +from qhbmlib.data.quantum_data import QuantumData + +__all__ = [ + "QHBMData", + "QuantumData", +] diff --git a/qhbmlib/data/qhbm_data.py b/qhbmlib/data/qhbm_data.py new file mode 100644 index 00000000..b3c1e463 --- /dev/null +++ b/qhbmlib/data/qhbm_data.py @@ -0,0 +1,39 @@ +# Copyright 2021 The QHBM Library Authors. All Rights Reserved. +# +# 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. +# ============================================================================== +"""Interface to quantum data sources defined by QHBMs.""" + +from typing import Union + +import tensorflow as tf + +from qhbmlib.data import quantum_data +from qhbmlib.inference import qhbm +from qhbmlib.models import hamiltonian + + +class QHBMData(quantum_data.QuantumData): + """QuantumData defined by a QHBM.""" + + def __init__(self, input_qhbm: qhbm.QHBM): + """Initializes a QHBMData. + + Args: + qhbm: An inference engine for a QHBM. + """ + self.qhbm = input_qhbm + + def expectation(self, observable: Union[tf.Tensor, hamiltonian.Hamiltonian]): + """See base class docstring.""" + return tf.squeeze(self.qhbm.expectation(observable), 0) diff --git a/qhbmlib/quantum_data.py b/qhbmlib/data/quantum_data.py similarity index 70% rename from qhbmlib/quantum_data.py rename to qhbmlib/data/quantum_data.py index 595ab6a6..a6ce0ae4 100644 --- a/qhbmlib/quantum_data.py +++ b/qhbmlib/data/quantum_data.py @@ -19,16 +19,14 @@ import tensorflow as tf -from qhbmlib import hamiltonian_infer -from qhbmlib import hamiltonian_model +from qhbmlib.models import hamiltonian class QuantumData(abc.ABC): """Interface for quantum datasets.""" @abc.abstractmethod - def expectation(self, observable: Union[tf.Tensor, - hamiltonian_model.Hamiltonian]): + def expectation(self, observable: Union[tf.Tensor, hamiltonian.Hamiltonian]): """Take the expectation value of an observable against this dataset. Args: @@ -41,19 +39,3 @@ def expectation(self, observable: Union[tf.Tensor, this quantum data source. """ raise NotImplementedError() - - -class QHBMData(QuantumData): - """QuantumData defined by a QHBM.""" - - def __init__(self, qhbm: hamiltonian_infer.QHBM): - """Initializes a QHBMData. - - Args: - qhbm: An inference engine for a QHBM. - """ - self.qhbm = qhbm - - def expectation(self, observable): - """See base class docstring.""" - return tf.squeeze(self.qhbm.expectation(observable), 0) diff --git a/qhbmlib/hamiltonian.py b/qhbmlib/hamiltonian.py deleted file mode 100644 index 051b632f..00000000 --- a/qhbmlib/hamiltonian.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright 2021 The QHBM Library Authors. All Rights Reserved. -# -# 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. -# ============================================================================== -"""Hamiltonians to use with QHBMs.""" - -import random - -import cirq - - -def _qubit_grid(rows, cols): - """Rectangle of qubits returned as a nested list.""" - qubits = [] - for r in range(rows): - qubits.append([cirq.GridQubit(r, c) for c in range(cols)]) - return qubits - - -def heisenberg_bond(q0, q1, bond_type): - """Given two Cirq qubits, return the PauliSum that bonds them.""" - if bond_type == 1: - bond = cirq.X - elif bond_type == 2: - bond = cirq.Y - elif bond_type == 3: - bond = cirq.Z - else: - raise ValueError("Unknown bond type.") - return cirq.PauliSum.from_pauli_strings( - [cirq.PauliString(bond(q0), bond(q1))]) - - -def heisenberg_hamiltonian_shard(rows, columns, jh, jv, bond_type): - """Returns a commuting subset of the 2D Heisenberg Hamiltonian.""" - qubits = _qubit_grid(rows, columns) - heisenberg = cirq.PauliSum() - # Apply horizontal bonds - for r in qubits: - for q0, q1 in zip(r, r[1::]): - heisenberg += jh * heisenberg_bond(q0, q1, bond_type) - # Apply vertical bonds - for r0, r1 in zip(qubits, qubits[1::]): - for q0, q1 in zip(r0, r1): - heisenberg += jv * heisenberg_bond(q0, q1, bond_type) - return heisenberg - - -def tfim_x_shard(rows, columns): - """Build the X component of a rectangular toroid TFIM at critical field.""" - lambda_crit = 3.05 # see https://arxiv.org/abs/cond-mat/0703788 - qubits = cirq.GridQubit.rect(rows, columns) - x_sum = cirq.PauliSum() - for q in qubits: - x_sum += lambda_crit * cirq.X(q) - return x_sum - - -def tfim_zz_shard(rows, columns): - """Uniform ferromagnetic interaction on a rectangular toroid.""" - qubits = _qubit_grid(rows, columns) - extended_qubits = _qubit_grid(rows, columns) - for r, row in enumerate(qubits): - extended_qubits[r].append(row[0]) - extended_qubits.append(qubits[0]) - zz_sum = cirq.PauliSum() - # Horizontal interactions. - for row in extended_qubits[:-1]: - for q0, q1 in zip(row, row[1:]): - zz_sum += cirq.Z(q0) * cirq.Z(q1) - # Vertical interactions. - for row_0, row_1 in zip(extended_qubits, extended_qubits[1:]): - for q0, q1 in zip(row_0[:-1], row_1): - zz_sum += cirq.Z(q0) * cirq.Z(q1) - return zz_sum - - -def tfim_x_shard_random(rows, columns, mean, std): - """Build the X component of a rectangular toroid TFIM with random fields.""" - qubits = cirq.GridQubit.rect(rows, columns) - x_sum = cirq.PauliSum() - for q in qubits: - x_sum += random.normalvariate(mean, std) * cirq.X(q) - return x_sum - - -def tfim_zz_shard_random(rows, columns, mean, std): - """Random ferromagnetic interaction on a rectangular toroid.""" - qubits = _qubit_grid(rows, columns) - extended_qubits = _qubit_grid(rows, columns) - for r, row in enumerate(qubits): - extended_qubits[r].append(row[0]) - extended_qubits.append(qubits[0]) - zz_sum = cirq.PauliSum() - # Horizontal interactions. - for row in extended_qubits[:-1]: - for q0, q1 in zip(row, row[1:]): - zz_sum += random.normalvariate(mean, std) * cirq.Z(q0) * cirq.Z(q1) - # Vertical interactions. - for row_0, row_1 in zip(extended_qubits, extended_qubits[1:]): - for q0, q1 in zip(row_0[:-1], row_1): - zz_sum += random.normalvariate(mean, std) * cirq.Z(q0) * cirq.Z(q1) - return zz_sum diff --git a/qhbmlib/inference/__init__.py b/qhbmlib/inference/__init__.py new file mode 100644 index 00000000..3c05588c --- /dev/null +++ b/qhbmlib/inference/__init__.py @@ -0,0 +1,43 @@ +# Copyright 2021 The QHBM Library Authors. All Rights Reserved. +# +# 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. +# ============================================================================== +"""Defines the qhbmlib.inference package.""" + +from qhbmlib.inference.ebm import AnalyticEnergyInference +from qhbmlib.inference.ebm import BernoulliEnergyInference +from qhbmlib.inference.ebm import EnergyInference +from qhbmlib.inference.ebm import EnergyInferenceBase +from qhbmlib.inference.ebm_utils import probabilities +from qhbmlib.inference.qhbm import QHBM +from qhbmlib.inference.qhbm_utils import density_matrix +from qhbmlib.inference.qhbm_utils import fidelity +from qhbmlib.inference.qmhl_loss import qmhl +from qhbmlib.inference.qnn import QuantumInference +from qhbmlib.inference.qnn_utils import unitary +from qhbmlib.inference.vqt_loss import vqt + +__all__ = [ + "AnalyticEnergyInference", + "BernoulliEnergyInference", + "density_matrix", + "EnergyInference", + "EnergyInferenceBase", + "fidelity", + "probabilities", + "QHBM", + "qmhl", + "QuantumInference", + "unitary", + "vqt", +] diff --git a/qhbmlib/energy_infer.py b/qhbmlib/inference/ebm.py similarity index 98% rename from qhbmlib/energy_infer.py rename to qhbmlib/inference/ebm.py index d4f482f6..86e15415 100644 --- a/qhbmlib/energy_infer.py +++ b/qhbmlib/inference/ebm.py @@ -23,7 +23,7 @@ import tensorflow_probability as tfp from tensorflow_probability import distributions as tfd -from qhbmlib import energy_model +from qhbmlib.models import energy from qhbmlib import utils @@ -57,7 +57,7 @@ class EnergyInferenceBase(tf.keras.layers.Layer, abc.ABC): """ def __init__(self, - input_energy: energy_model.BitstringEnergy, + input_energy: energy.BitstringEnergy, initial_seed: Union[None, tf.Tensor] = None, name: Union[None, str] = None): """Initializes an EnergyInferenceBase. @@ -234,7 +234,7 @@ class EnergyInference(EnergyInferenceBase): """Provides some default method implementations.""" def __init__(self, - input_energy: energy_model.BitstringEnergy, + input_energy: energy.BitstringEnergy, num_expectation_samples: int, initial_seed: Union[None, tf.Tensor] = None, name: Union[None, str] = None): @@ -371,7 +371,7 @@ class AnalyticEnergyInference(EnergyInference): """Uses an explicit categorical distribution to implement parent functions.""" def __init__(self, - input_energy: energy_model.BitstringEnergy, + input_energy: energy.BitstringEnergy, num_expectation_samples: int, initial_seed: Union[None, tf.Tensor] = None, name: Union[None, str] = None): @@ -448,7 +448,7 @@ class BernoulliEnergyInference(EnergyInference): """Manages inference for a Bernoulli defined by spin energies.""" def __init__(self, - input_energy: energy_model.BernoulliEnergy, + input_energy: energy.BernoulliEnergy, num_expectation_samples: int, initial_seed: Union[None, tf.Tensor] = None, name: Union[None, str] = None): diff --git a/qhbmlib/energy_infer_utils.py b/qhbmlib/inference/ebm_utils.py similarity index 77% rename from qhbmlib/energy_infer_utils.py rename to qhbmlib/inference/ebm_utils.py index b0991172..701f642e 100644 --- a/qhbmlib/energy_infer_utils.py +++ b/qhbmlib/inference/ebm_utils.py @@ -18,18 +18,19 @@ import tensorflow as tf -from qhbmlib import energy_model +from qhbmlib.models import energy -def probabilities(energy: energy_model.BitstringEnergy): +def probabilities(input_energy: energy.BitstringEnergy): """Returns the probabilities of the EBM. Args: - energy: The energy function defining the EBM. + input_energy: The energy function defining the EBM. """ all_bitstrings = tf.constant( - list(itertools.product([0, 1], repeat=energy.num_bits)), dtype=tf.int8) - all_energies = energy(all_bitstrings) + list(itertools.product([0, 1], repeat=input_energy.num_bits)), + dtype=tf.int8) + all_energies = input_energy(all_bitstrings) energy_exp = tf.math.exp(-all_energies) partition = tf.math.reduce_sum(energy_exp) return energy_exp / partition diff --git a/qhbmlib/hamiltonian_infer.py b/qhbmlib/inference/qhbm.py similarity index 79% rename from qhbmlib/hamiltonian_infer.py rename to qhbmlib/inference/qhbm.py index 9616753f..b3dc3d15 100644 --- a/qhbmlib/hamiltonian_infer.py +++ b/qhbmlib/inference/qhbm.py @@ -19,9 +19,9 @@ import tensorflow as tf -from qhbmlib import circuit_infer -from qhbmlib import energy_infer -from qhbmlib import hamiltonian_model +from qhbmlib.inference import ebm # pylint: disable=unused-import +from qhbmlib.inference import qnn # pylint: disable=unused-import +from qhbmlib.models import hamiltonian from qhbmlib import utils @@ -63,21 +63,21 @@ class QHBM(tf.keras.layers.Layer): """ def __init__(self, - e_inference: energy_infer.EnergyInference, - q_inference: circuit_infer.QuantumInference, + input_ebm: ebm.EnergyInference, + input_qnn: qnn.QuantumInference, name: Union[None, str] = None): """Initializes a QHBM. Args: - e_inference: Attends to density operator eigenvalues. - q_inference: Attends to density operator eigenvectors. + input_ebm: Attends to density operator eigenvalues. + input_qnn: Attends to density operator eigenvectors. name: Optional name for the model. """ super().__init__(name=name) - self._e_inference = e_inference - self._q_inference = q_inference - self._hamiltonian = hamiltonian_model.Hamiltonian(e_inference.energy, - q_inference.circuit) + self._e_inference = input_ebm + self._q_inference = input_qnn + self._modular_hamiltonian = hamiltonian.Hamiltonian( + self.e_inference.energy, self.q_inference.circuit) @property def e_inference(self): @@ -90,19 +90,19 @@ def q_inference(self): return self._q_inference @property - def hamiltonian(self): + def modular_hamiltonian(self): """The modular Hamiltonian defining this QHBM.""" - return self._hamiltonian + return self._modular_hamiltonian def circuits(self, num_samples: int): r"""Draws thermally distributed eigenstates from the model Hamiltonian. Here we explain the algorithm. First, construct $X$ to be a classical random variable with probability distribution $p_\theta(x)$ set by - `model.hamiltonian.energy`. Then, draw $n = $`num\_samples` bitstrings, + `model.e_inference.energy`. Then, draw $n = $`num\_samples` bitstrings, $S=\{x_1, \ldots, x_n\}$, from $X$. For each unique $x_i\in S$, set `states[i]` to the TFQ string representation of $U_\phi\ket{x_i}$, where - $U_\phi$ is set by `self.hamiltonian.circuit`. Finally, set `counts[i]` + $U_\phi$ is set by `self.q_inference.circuit`. Finally, set `counts[i]` equal to the number of times $x_i$ occurs in $S$. Args: @@ -112,26 +112,25 @@ def circuits(self, num_samples: int): Returns: states: 1D `tf.Tensor` of dtype `tf.string`. Each entry is a TFQ string - representation of an eigenstate of the Hamiltonian `self.hamiltonian`. + representation of an eigenstate of `self.modular_hamiltonian`. counts: 1D `tf.Tensor` of dtype `tf.int32`. `counts[i]` is the number of times `states[i]` was drawn from the ensemble. """ samples = self.e_inference.sample(num_samples) bitstrings, _, counts = utils.unique_bitstrings_with_counts(samples) - states = self.hamiltonian.circuit(bitstrings) + states = self.q_inference.circuit(bitstrings) return states, counts - def expectation(self, observables: Union[tf.Tensor, - hamiltonian_model.Hamiltonian]): + def expectation(self, observables: Union[tf.Tensor, hamiltonian.Hamiltonian]): """Estimates observable expectation values against the density operator. TODO(#119): add expectation and derivative equations and discussions from updated paper. - Implicitly sample `num_samples` pure states from the canonical ensemble - corresponding to the thermal state defined by `self.hamiltonian`. For each - such state |psi>, estimate the expectation value for each - `ops[j]`. Then, average these expectation values over the sampled states. + Implicitly sample pure states from the canonical ensemble corresponding to + the thermal state defined by `self.modular_hamiltonian`. For each such state + |psi>, estimate the expectation value for each `ops[j]`. + Then, average these expectation values over the sampled states. Args: model: The modular Hamiltonian whose normalized exponential is the diff --git a/qhbmlib/hamiltonian_infer_utils.py b/qhbmlib/inference/qhbm_utils.py similarity index 88% rename from qhbmlib/hamiltonian_infer_utils.py rename to qhbmlib/inference/qhbm_utils.py index ad1e4dec..a49bdbdc 100644 --- a/qhbmlib/hamiltonian_infer_utils.py +++ b/qhbmlib/inference/qhbm_utils.py @@ -16,12 +16,12 @@ import tensorflow as tf -from qhbmlib import circuit_infer_utils -from qhbmlib import energy_infer_utils -from qhbmlib import hamiltonian_model +from qhbmlib.inference import qnn_utils +from qhbmlib.inference import ebm_utils +from qhbmlib.models import hamiltonian -def density_matrix(model: hamiltonian_model.Hamiltonian): +def density_matrix(model: hamiltonian.Hamiltonian): r"""Returns the thermal state corresponding to a modular Hamiltonian. Given a modular Hamiltonian $K_{\theta\phi} = U_\phi K_\theta U_\phi^\dagger$, @@ -53,14 +53,13 @@ def density_matrix(model: hamiltonian_model.Hamiltonian): model: Modular Hamiltonian whose corresponding thermal state is the density matrix to be calculated. """ - probabilities = tf.cast( - energy_infer_utils.probabilities(model.energy), tf.complex64) - unitary_matrix = circuit_infer_utils.unitary(model.circuit) + probabilities = tf.cast(ebm_utils.probabilities(model.energy), tf.complex64) + unitary_matrix = qnn_utils.unitary(model.circuit) return tf.einsum("k,ik,kj->ij", probabilities, unitary_matrix, tf.linalg.adjoint(unitary_matrix)) -def fidelity(model: hamiltonian_model.Hamiltonian, sigma: tf.Tensor): +def fidelity(model: hamiltonian.Hamiltonian, sigma: tf.Tensor): r"""Calculate the fidelity between a QHBM and a density matrix. Definition of the fidelity between two quantum states $\rho$ and $\sigma$ is @@ -106,9 +105,8 @@ def fidelity(model: hamiltonian_model.Hamiltonian, sigma: tf.Tensor): A scalar `tf.Tensor` which is the fidelity between the density matrix represented by this QHBM and `sigma`. """ - k_theta = tf.cast( - energy_infer_utils.probabilities(model.energy), tf.complex64) - u_phi = circuit_infer_utils.unitary(model.circuit) + k_theta = tf.cast(ebm_utils.probabilities(model.energy), tf.complex64) + u_phi = qnn_utils.unitary(model.circuit) u_phi_dagger = tf.linalg.adjoint(u_phi) sqrt_k_theta = tf.sqrt(k_theta) omega = tf.einsum("a,ab,bc,cd,d->ad", sqrt_k_theta, u_phi_dagger, sigma, diff --git a/qhbmlib/qmhl_loss.py b/qhbmlib/inference/qmhl_loss.py similarity index 76% rename from qhbmlib/qmhl_loss.py rename to qhbmlib/inference/qmhl_loss.py index aeaa7b20..72ac22d9 100644 --- a/qhbmlib/qmhl_loss.py +++ b/qhbmlib/inference/qmhl_loss.py @@ -14,20 +14,21 @@ # ============================================================================== """Implementation of the QMHL loss function.""" -from qhbmlib import hamiltonian_infer -from qhbmlib import quantum_data +from qhbmlib.data import quantum_data +from qhbmlib.inference import qhbm -def qmhl(data: quantum_data.QuantumData, qhbm: hamiltonian_infer.QHBM): +def qmhl(data: quantum_data.QuantumData, input_qhbm: qhbm.QHBM): """Calculate the QMHL loss of the QHBM against the quantum data. See equation 21 in the appendix. Args: data: The data mixed state to learn. - qhbm: QHBM being trained to approximate `data`. + input_qhbm: QHBM being trained to approximate `data`. Returns: The quantum cross-entropy between the data and the model. """ - return data.expectation(qhbm.hamiltonian) + qhbm.e_inference.log_partition() + return (data.expectation(input_qhbm.modular_hamiltonian) + + input_qhbm.e_inference.log_partition()) diff --git a/qhbmlib/circuit_infer.py b/qhbmlib/inference/qnn.py similarity index 92% rename from qhbmlib/circuit_infer.py rename to qhbmlib/inference/qnn.py index d0e4f338..e18e3cc9 100644 --- a/qhbmlib/circuit_infer.py +++ b/qhbmlib/inference/qnn.py @@ -20,9 +20,9 @@ import tensorflow as tf import tensorflow_quantum as tfq -from qhbmlib import circuit_model -from qhbmlib import energy_model -from qhbmlib import hamiltonian_model +from qhbmlib.models import circuit # pylint: disable=unused-import +from qhbmlib.models import energy +from qhbmlib.models import hamiltonian from qhbmlib import utils @@ -30,7 +30,7 @@ class QuantumInference(tf.keras.layers.Layer): """Methods for inference on QuantumCircuit objects.""" def __init__(self, - circuit: circuit_model.QuantumCircuit, + input_circuit: circuit.QuantumCircuit, backend: Union[str, cirq.Sampler] = "noiseless", differentiator: Union[None, tfq.differentiators.Differentiator] = None, @@ -38,15 +38,15 @@ def __init__(self, """Initialize a QuantumInference layer. Args: - circuit: The parameterized quantum circuit on which to do inference. + input_circuit: The parameterized quantum circuit on which to do inference. backend: Specifies what backend TFQ will use to compute expectation values. `str` options are {"noisy", "noiseless"}; users may also specify a preconfigured cirq execution object to use instead. differentiator: Specifies how to take the derivative of a quantum circuit. name: Identifier for this inference engine. """ - circuit.build([]) - self._circuit = circuit + input_circuit.build([]) + self._circuit = input_circuit self._differentiator = differentiator self._backend = backend self._sample_layer = tfq.layers.Sample(backend=backend) @@ -92,7 +92,7 @@ def differentiator(self): return self._differentiator def expectation(self, initial_states: tf.Tensor, - observables: Union[tf.Tensor, hamiltonian_model.Hamiltonian]): + observables: Union[tf.Tensor, hamiltonian.Hamiltonian]): """Returns the expectation values of the observables against the QNN. Args: @@ -113,7 +113,7 @@ def expectation(self, initial_states: tf.Tensor, u = self.circuit ops = observables post_process = lambda x: x - elif isinstance(observables.energy, energy_model.PauliMixin): + elif isinstance(observables.energy, energy.PauliMixin): u = self.circuit + observables.circuit_dagger ops = observables.operator_shards post_process = lambda y: tf.map_fn( diff --git a/qhbmlib/circuit_infer_utils.py b/qhbmlib/inference/qnn_utils.py similarity index 72% rename from qhbmlib/circuit_infer_utils.py rename to qhbmlib/inference/qnn_utils.py index 33dc45da..c9e0c705 100644 --- a/qhbmlib/circuit_infer_utils.py +++ b/qhbmlib/inference/qnn_utils.py @@ -17,16 +17,17 @@ import tensorflow as tf import tensorflow_quantum as tfq -from qhbmlib import circuit_model +from qhbmlib.models import circuit -def unitary(circuit: circuit_model.QuantumCircuit): +def unitary(input_circuit: circuit.QuantumCircuit): """Returns the unitary matrix corresponding to the given circuit. Args: - circuit: Quantum circuit whose unitary matrix is to be calculated. + input_circuit: Quantum circuit whose unitary matrix is to be calculated. """ return tfq.layers.Unitary()( - circuit.pqc, - symbol_names=circuit.symbol_names, - symbol_values=tf.expand_dims(circuit.symbol_values, 0)).to_tensor()[0] + input_circuit.pqc, + symbol_names=input_circuit.symbol_names, + symbol_values=tf.expand_dims(input_circuit.symbol_values, + 0)).to_tensor()[0] diff --git a/qhbmlib/vqt_loss.py b/qhbmlib/inference/vqt_loss.py similarity index 70% rename from qhbmlib/vqt_loss.py rename to qhbmlib/inference/vqt_loss.py index 1a2b5bab..a7e7a2e8 100644 --- a/qhbmlib/vqt_loss.py +++ b/qhbmlib/inference/vqt_loss.py @@ -18,20 +18,20 @@ import tensorflow as tf -from qhbmlib import hamiltonian_infer -from qhbmlib import hamiltonian_model +from qhbmlib.inference import qhbm +from qhbmlib.models import hamiltonian -def vqt(qhbm: hamiltonian_infer.QHBM, - hamiltonian: Union[tf.Tensor, - hamiltonian_model.Hamiltonian], beta: tf.Tensor): +def vqt(input_qhbm: qhbm.QHBM, + target_hamiltonian: Union[tf.Tensor, + hamiltonian.Hamiltonian], beta: tf.Tensor): """Computes the VQT loss of a given QHBM and Hamiltonian. This function is differentiable within a `tf.GradientTape` scope. Args: - qhbm: Inference methods for the model. - hamiltonian: The Hamiltonian whose thermal state is to be learned. If + input_qhbm: Inference methods for the model. + target_hamiltonian: The Hamiltonian whose thermal state is to be learned. If it is a `tf.Tensor`, it is of type `tf.string` with shape [1], result of calling `tfq.convert_to_tensor` on a list of `cirq.PauliSum`, `[op]`. Otherwise, a Hamiltonian. @@ -45,11 +45,11 @@ def vqt(qhbm: hamiltonian_infer.QHBM, # See equations B4 and B5 in appendix. TODO(#119): confirm equation number. def f_vqt(bitstrings): h_expectations = tf.squeeze( - qhbm.q_inference.expectation(bitstrings, hamiltonian), 1) + input_qhbm.q_inference.expectation(bitstrings, target_hamiltonian), 1) beta_h_expectations = beta * h_expectations - energies = tf.stop_gradient(qhbm.hamiltonian.energy(bitstrings)) + energies = tf.stop_gradient(input_qhbm.e_inference.energy(bitstrings)) return beta_h_expectations - energies - average_expectation = qhbm.e_inference.expectation(f_vqt) - current_partition = tf.stop_gradient(qhbm.e_inference.log_partition()) + average_expectation = input_qhbm.e_inference.expectation(f_vqt) + current_partition = tf.stop_gradient(input_qhbm.e_inference.log_partition()) return average_expectation - current_partition diff --git a/qhbmlib/models/__init__.py b/qhbmlib/models/__init__.py new file mode 100644 index 00000000..1e475db4 --- /dev/null +++ b/qhbmlib/models/__init__.py @@ -0,0 +1,41 @@ +# Copyright 2021 The QHBM Library Authors. All Rights Reserved. +# +# 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. +# ============================================================================== +"""Defines the qhbmlib.models package.""" + +from qhbmlib.models.circuit import DirectQuantumCircuit +from qhbmlib.models.circuit import QAIA +from qhbmlib.models.circuit import QuantumCircuit +from qhbmlib.models.energy import BernoulliEnergy +from qhbmlib.models.energy import BitstringEnergy +from qhbmlib.models.energy import KOBE +from qhbmlib.models.energy import PauliMixin +from qhbmlib.models.energy_utils import Parity +from qhbmlib.models.energy_utils import SpinsFromBitstrings +from qhbmlib.models.energy_utils import VariableDot +from qhbmlib.models.hamiltonian import Hamiltonian + +__all__ = [ + "BernoulliEnergy", + "BitstringEnergy", + "DirectQuantumCircuit", + "Hamiltonian", + "KOBE", + "Parity", + "PauliMixin", + "QAIA", + "QuantumCircuit", + "SpinsFromBitstrings", + "VariableDot", +] diff --git a/qhbmlib/circuit_model.py b/qhbmlib/models/circuit.py similarity index 99% rename from qhbmlib/circuit_model.py rename to qhbmlib/models/circuit.py index 4b02afec..0c52965f 100644 --- a/qhbmlib/circuit_model.py +++ b/qhbmlib/models/circuit.py @@ -21,7 +21,7 @@ import tensorflow as tf import tensorflow_quantum as tfq -from qhbmlib import circuit_model_utils +from qhbmlib.models import circuit_utils class QuantumCircuit(tf.keras.layers.Layer): @@ -56,7 +56,7 @@ def __init__(self, self._value_layers = value_layers self._value_layers_inputs = value_layers_inputs - raw_bit_circuit = circuit_model_utils.bit_circuit(self.qubits) + raw_bit_circuit = circuit_utils.bit_circuit(self.qubits) bit_symbol_names = list( sorted(tfq.util.get_circuit_symbols(raw_bit_circuit))) self._bit_symbol_names = tf.constant([str(x) for x in bit_symbol_names]) diff --git a/qhbmlib/circuit_model_utils.py b/qhbmlib/models/circuit_utils.py similarity index 100% rename from qhbmlib/circuit_model_utils.py rename to qhbmlib/models/circuit_utils.py diff --git a/qhbmlib/energy_model.py b/qhbmlib/models/energy.py similarity index 93% rename from qhbmlib/energy_model.py rename to qhbmlib/models/energy.py index f7e5adad..787a40b8 100644 --- a/qhbmlib/energy_model.py +++ b/qhbmlib/models/energy.py @@ -20,7 +20,7 @@ import cirq import tensorflow as tf -from qhbmlib import energy_model_utils +from qhbmlib.models import energy_utils class BitstringEnergy(tf.keras.layers.Layer): @@ -51,7 +51,7 @@ def __init__(self, name: Optional name for the model. """ super().__init__(name=name) - self._bits = energy_model_utils.check_bits(bits) + self._bits = energy_utils.check_bits(bits) self._energy_layers = energy_layers @property @@ -140,8 +140,8 @@ def __init__(self, initialize the values of the parameters. name: Optional name for the model. """ - pre_process = [energy_model_utils.SpinsFromBitstrings()] - post_process = [energy_model_utils.VariableDot(initializer=initializer)] + pre_process = [energy_utils.SpinsFromBitstrings()] + post_process = [energy_utils.VariableDot(initializer=initializer)] super().__init__(bits, pre_process + post_process, name) self._post_process = post_process @@ -184,11 +184,11 @@ def __init__(self, initializer: Specifies how to initialize the values of the parameters. name: Optional name for the model. """ - parity_layer = energy_model_utils.Parity(bits, order) + parity_layer = energy_utils.Parity(bits, order) self._num_terms = parity_layer.num_terms self._indices = parity_layer.indices - pre_process = [energy_model_utils.SpinsFromBitstrings(), parity_layer] - post_process = [energy_model_utils.VariableDot(initializer=initializer)] + pre_process = [energy_utils.SpinsFromBitstrings(), parity_layer] + post_process = [energy_utils.VariableDot(initializer=initializer)] super().__init__(bits, pre_process + post_process, name) self._post_process = post_process diff --git a/qhbmlib/energy_model_utils.py b/qhbmlib/models/energy_utils.py similarity index 100% rename from qhbmlib/energy_model_utils.py rename to qhbmlib/models/energy_utils.py diff --git a/qhbmlib/hamiltonian_model.py b/qhbmlib/models/hamiltonian.py similarity index 66% rename from qhbmlib/hamiltonian_model.py rename to qhbmlib/models/hamiltonian.py index 92db0dcc..eea575de 100644 --- a/qhbmlib/hamiltonian_model.py +++ b/qhbmlib/models/hamiltonian.py @@ -19,33 +19,33 @@ import tensorflow as tf import tensorflow_quantum as tfq -from qhbmlib import circuit_model -from qhbmlib import energy_model +from qhbmlib.models import circuit +from qhbmlib.models import energy class Hamiltonian(tf.keras.layers.Layer): """Diagonalized (spectral) representation of a Hermitian operator.""" def __init__(self, - energy: energy_model.BitstringEnergy, - circuit: circuit_model.QuantumCircuit, + input_energy: energy.BitstringEnergy, + input_circuit: circuit.QuantumCircuit, name: Union[None, str] = None): """Initializes a Hamiltonian. Args: - energy: Represents the eigenvalues of this operator. - circuit: Represents the eigenvectors of this operator. + input_energy: Represents the eigenvalues of this operator. + input_circuit: Represents the eigenvectors of this operator. name: Optional name for the model. """ super().__init__(name=name) - if energy.num_bits != len(circuit.qubits): - raise ValueError( - "`energy` and `circuit` must act on the same number of bits.") - self.energy = energy - self.circuit = circuit - self.circuit_dagger = circuit**-1 + if input_energy.num_bits != len(input_circuit.qubits): + raise ValueError("`input_energy` and `input_circuit` " + "must act on the same number of bits.") + self.energy = input_energy + self.circuit = input_circuit + self.circuit_dagger = input_circuit**-1 self.operator_shards = None - if isinstance(self.energy, energy_model.PauliMixin): + if isinstance(self.energy, energy.PauliMixin): self.operator_shards = tfq.convert_to_tensor( self.energy.operator_shards(self.circuit.qubits)) diff --git a/tests/architectures_test.py b/tests/architectures_test.py deleted file mode 100644 index f7d69991..00000000 --- a/tests/architectures_test.py +++ /dev/null @@ -1,245 +0,0 @@ -# Copyright 2021 The QHBM Library Authors. All Rights Reserved. -# -# 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 of the architectures module.""" - -from absl.testing import parameterized -import random - -import cirq -import sympy -import tensorflow as tf -import tensorflow_quantum as tfq - -from qhbmlib import architectures - - -class RPQCTest(tf.test.TestCase, parameterized.TestCase): - """Test RPQC functions in the architectures module.""" - - def test_get_xz_rotation(self): - """Confirm an XZ rotation is returned.""" - q = cirq.GridQubit(7, 9) - a, b = sympy.symbols("a b") - expected_circuit = cirq.Circuit(cirq.X(q)**a, cirq.Z(q)**b) - actual_circuit = architectures.get_xz_rotation(q, a, b) - self.assertEqual(actual_circuit, expected_circuit) - - def test_get_cz_exp(self): - """Confirm an exponentiated CNOT is returned.""" - q0 = cirq.GridQubit(4, 1) - q1 = cirq.GridQubit(2, 5) - a = sympy.Symbol("a") - expected_circuit = cirq.Circuit(cirq.CZ(q0, q1)**a) - actual_circuit = architectures.get_cz_exp(q0, q1, a) - self.assertEqual(actual_circuit, expected_circuit) - - def test_get_xz_rotation_layer(self): - """Confirm an XZ rotation on every qubit is returned.""" - qubits = cirq.GridQubit.rect(1, 2) - layer_num = 3 - name = "test_rot" - expected_circuit = cirq.Circuit() - for n, q in enumerate(qubits): - s = sympy.Symbol("sx_{0}_{1}_{2}".format(name, layer_num, n)) - expected_circuit += cirq.Circuit(cirq.X(q)**s) - s = sympy.Symbol("sz_{0}_{1}_{2}".format(name, layer_num, n)) - expected_circuit += cirq.Circuit(cirq.Z(q)**s) - actual_circuit = architectures.get_xz_rotation_layer( - qubits, layer_num, name) - self.assertEqual(actual_circuit, expected_circuit) - - @parameterized.parameters([{"n_qubits": 11}, {"n_qubits": 12}]) - def test_get_cz_exp_layer(self, n_qubits): - """Confirm an exponentiated CZ on every qubit is returned.""" - qubits = cirq.GridQubit.rect(1, n_qubits) - layer_num = 0 - name = "test_cz" - expected_circuit = cirq.Circuit() - for n, (q0, q1) in enumerate(zip(qubits, qubits[1:])): - if n % 2 == 0: - s = sympy.Symbol("sc_{0}_{1}_{2}".format(name, layer_num, n)) - expected_circuit += cirq.Circuit(cirq.CZ(q0, q1)**s) - for n, (q0, q1) in enumerate(zip(qubits, qubits[1:])): - if n % 2 == 1: - s = sympy.Symbol("sc_{0}_{1}_{2}".format(name, layer_num, n)) - expected_circuit += cirq.Circuit(cirq.CZ(q0, q1)**s) - actual_circuit = architectures.get_cz_exp_layer(qubits, layer_num, name) - self.assertEqual(actual_circuit, expected_circuit) - - @parameterized.parameters([{"n_qubits": 11}, {"n_qubits": 12}]) - def test_get_hardware_efficient_model_unitary(self, n_qubits): - """Confirm a multi-layered circuit is returned.""" - qubits = cirq.GridQubit.rect(1, n_qubits) - name = "test_hardware_efficient_model" - expected_circuit = cirq.Circuit() - this_circuit = architectures.get_xz_rotation_layer(qubits, 0, name) - expected_circuit += this_circuit - this_circuit = architectures.get_cz_exp_layer(qubits, 0, name) - expected_circuit += this_circuit - this_circuit = architectures.get_xz_rotation_layer(qubits, 1, name) - expected_circuit += this_circuit - this_circuit = architectures.get_cz_exp_layer(qubits, 1, name) - expected_circuit += this_circuit - actual_circuit = architectures.get_hardware_efficient_model_unitary( - qubits, 2, name) - self.assertEqual(actual_circuit, expected_circuit) - - def test_get_hardware_efficient_model_unitary_1q(self): - """Confirm the correct model is returned when there is only one qubit.""" - qubits = [cirq.GridQubit(2, 3)] - name = "test_harware_efficient_model_1q" - expected_circuit = cirq.Circuit() - this_circuit = architectures.get_xz_rotation_layer(qubits, 0, name) - expected_circuit += this_circuit - this_circuit = architectures.get_xz_rotation_layer(qubits, 1, name) - expected_circuit += this_circuit - actual_circuit = architectures.get_hardware_efficient_model_unitary( - qubits, 2, name) - self.assertEqual(actual_circuit, expected_circuit) - - -class HEA2dTest(tf.test.TestCase): - """Test 2D HEA functions in the architectures module.""" - - def test_get_2d_xz_rotation_layer(self): - """Confirms the xz rotations are correct on a 2x3 grid.""" - rows = 2 - cols = 3 - name = "test_xz" - layer_num = 7 - expected_circuit = cirq.Circuit() - for r in range(rows): - for c in range(cols): - q = cirq.GridQubit(r, c) - s = sympy.Symbol(f"sx_{name}_{layer_num}_{r}_{c}") - x_gate = cirq.X(q)**s - s = sympy.Symbol(f"sz_{name}_{layer_num}_{r}_{c}") - z_gate = cirq.Z(q)**s - expected_circuit += cirq.Circuit(x_gate, z_gate) - actual_circuit = architectures.get_2d_xz_rotation_layer( - rows, cols, layer_num, name) - self.assertEqual(actual_circuit, expected_circuit) - - def test_get_2d_xz_rotation_layer_small(self): - """Confirms the xz rotation layer on one qubit is just a single xz.""" - name = "test_small_xz" - layer_num = 29 - expected_circuit = cirq.Circuit() - q = cirq.GridQubit(0, 0) - s = sympy.Symbol(f"sx_{name}_{layer_num}_{0}_{0}") - x_gate = cirq.X(q)**s - s = sympy.Symbol(f"sz_{name}_{layer_num}_{0}_{0}") - z_gate = cirq.Z(q)**s - expected_circuit += cirq.Circuit(x_gate, z_gate) - actual_circuit = architectures.get_2d_xz_rotation_layer( - 1, 1, layer_num, name) - self.assertEqual(actual_circuit, expected_circuit) - - def test_get_2d_cz_exp_layer(self): - """Confirms the cz exponentials are correct on a 2x3 grid.""" - name = "test_cz" - layer_num = 19 - expected_circuit = cirq.Circuit() - - # Apply horizontal bonds - s = sympy.Symbol(f"scz_{name}_{layer_num}_row{0}_{0}_{1}") - expected_circuit += cirq.Circuit( - cirq.CZPowGate(exponent=s)(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1))) - s = sympy.Symbol(f"scz_{name}_{layer_num}_row{0}_{1}_{2}") - expected_circuit += cirq.Circuit( - cirq.CZPowGate(exponent=s)(cirq.GridQubit(0, 1), cirq.GridQubit(0, 2))) - s = sympy.Symbol(f"scz_{name}_{layer_num}_row{1}_{0}_{1}") - expected_circuit += cirq.Circuit( - cirq.CZPowGate(exponent=s)(cirq.GridQubit(1, 0), cirq.GridQubit(1, 1))) - s = sympy.Symbol(f"scz_{name}_{layer_num}_row{1}_{1}_{2}") - expected_circuit += cirq.Circuit( - cirq.CZPowGate(exponent=s)(cirq.GridQubit(1, 1), cirq.GridQubit(1, 2))) - - # Apply vertical bonds - s = sympy.Symbol(f"scz_{name}_{layer_num}_col{0}_{0}_{1}") - expected_circuit += cirq.Circuit( - cirq.CZPowGate(exponent=s)(cirq.GridQubit(0, 0), cirq.GridQubit(1, 0))) - s = sympy.Symbol(f"scz_{name}_{layer_num}_col{1}_{0}_{1}") - expected_circuit += cirq.Circuit( - cirq.CZPowGate(exponent=s)(cirq.GridQubit(0, 1), cirq.GridQubit(1, 1))) - s = sympy.Symbol(f"scz_{name}_{layer_num}_col{2}_{0}_{1}") - expected_circuit += cirq.Circuit( - cirq.CZPowGate(exponent=s)(cirq.GridQubit(0, 2), cirq.GridQubit(1, 2))) - - actual_circuit = architectures.get_2d_cz_exp_layer(2, 3, layer_num, name) - self.assertEqual(actual_circuit, expected_circuit) - - def test_get_2d_cz_exp_layer_empty(self): - """On single qubit, no gates should be returned.""" - actual_circuit = architectures.get_2d_cz_exp_layer(1, 1, 1, "") - self.assertEqual(actual_circuit, cirq.Circuit()) - - def test_get_2d_cz_exp_layer_small(self): - """Tests on 2 qubits.""" - name = "small" - layer_num = 51 - s = sympy.Symbol(f"scz_{name}_{layer_num}_row{0}_{0}_{1}") - expected_circuit = cirq.Circuit( - cirq.CZPowGate(exponent=s)(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1))) - actual_circuit = architectures.get_2d_cz_exp_layer(1, 2, layer_num, name) - self.assertEqual(actual_circuit, expected_circuit) - - def test_get_2d_hea(self): - """Confirms the hea is correct on a 2x3 grid.""" - num_layers = 2 - name = "test_hea" - expected_circuit = cirq.Circuit() - for layer in range(num_layers): - xz_circuit = architectures.get_2d_xz_rotation_layer(2, 3, layer, name) - cz_circuit = architectures.get_2d_cz_exp_layer(2, 3, layer, name) - expected_circuit += xz_circuit - expected_circuit += cz_circuit - actual_circuit = architectures.get_2d_hea(2, 3, 2, name) - self.assertEqual(actual_circuit, expected_circuit) - - -class TrotterTest(tf.test.TestCase, parameterized.TestCase): - """Test trotter functions in the architectures module.""" - - def test_get_trotter_model_unitary(self): - """Confirm correct trotter unitary and parameters are returned.""" - n_qubits = 4 - qubits = cirq.GridQubit.rect(1, n_qubits) - p = 7 - hz = cirq.PauliSum() - hx = cirq.PauliSum() - test_name = "test_trotter" - for q in qubits: - hz += cirq.PauliString(random.uniform(-4.5, 4.5), cirq.Z(q)) - hx += cirq.PauliString(cirq.X(q)) - for q0, q1 in zip(qubits[:-1], qubits[1:]): - hz += cirq.PauliString(random.uniform(-4.5, 4.5), cirq.Z(q0), cirq.Z(q1)) - gammas = [ - sympy.Symbol("phi_test_trotter_L{}_H0".format(j)) for j in range(p) - ] - betas = [ - sympy.Symbol("phi_test_trotter_L{}_H1".format(j)) for j in range(p) - ] - expected_circuit = cirq.Circuit() - x_circuit = cirq.PauliSum() - for q in qubits: - x_circuit += cirq.X(q) - for j in range(p): - expected_circuit += tfq.util.exponential([hz], coefficients=[gammas[j]]) - expected_circuit += tfq.util.exponential([x_circuit], - coefficients=[betas[j]]) - actual_circuit = architectures.get_trotter_model_unitary( - p, [hz, hx], test_name) - self.assertEqual(actual_circuit, expected_circuit) diff --git a/tests/quantum_data_test.py b/tests/data/qhbm_data_test.py similarity index 91% rename from tests/quantum_data_test.py rename to tests/data/qhbm_data_test.py index 5e21717e..54c83113 100644 --- a/tests/quantum_data_test.py +++ b/tests/data/qhbm_data_test.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests quantum data sources.""" +"""Tests for qhbmlib.data.qhbm_data""" import cirq import tensorflow as tf -from qhbmlib import quantum_data +from qhbmlib import data from tests import test_util @@ -46,7 +46,7 @@ def test_expectation(self): qubits, num_layers, f"observable_{num_qubits}", self.num_samples) expected_expectation = tf.squeeze(qhbm.expectation(hamiltonian)) - data = quantum_data.QHBMData(qhbm) - actual_expectation = data.expectation(hamiltonian) + actual_data = data.QHBMData(qhbm) + actual_expectation = actual_data.expectation(hamiltonian) self.assertAllClose(actual_expectation, expected_expectation) diff --git a/tests/hamiltonian_test.py b/tests/hamiltonian_test.py deleted file mode 100644 index ea4bec29..00000000 --- a/tests/hamiltonian_test.py +++ /dev/null @@ -1,144 +0,0 @@ -# pylint: skip-file -# Copyright 2021 The QHBM Library Authors. All Rights Reserved. -# -# 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 hamiltonian.py""" - -import cirq -import tensorflow as tf - -from qhbmlib import hamiltonian - - -class QubitGridTest(tf.test.TestCase): - """Test the qubit_grid function.""" - - def test_qubit_grid(self): - """Confirm correct grid.""" - r = 2 - c = 3 - test_qubits = hamiltonian._qubit_grid(r, c) - for test_r, test_row in enumerate(test_qubits): - for test_c, q in enumerate(test_row): - self.assertEqual(cirq.GridQubit(test_r, test_c), q) - - -class HeisenbergTest(tf.test.TestCase): - """Test components related to the 2D Heisenberg Hamiltonian.""" - - def test_heisenberg_bond(self): - """Test heisenberg bonds.""" - q0 = cirq.GridQubit(3, 7) - q1 = cirq.GridQubit(2, 1) - for t, pauli in enumerate([cirq.X, cirq.Y, cirq.Z], start=1): - test_bond = hamiltonian.heisenberg_bond(q0, q1, t) - self.assertEqual( - cirq.PauliSum.from_pauli_strings([pauli(q0) * pauli(q1)]), test_bond) - with self.assertRaises(ValueError): - _ = hamiltonian.heisenberg_bond(q0, q1, 4) - - def test_heisenberg_hamiltonian_shard(self): - """Test heisenberg shards.""" - rows_list = [2, 3, 4, 5] - columns_list = [2, 3, 4, 5] - for rows in rows_list: - for columns in columns_list: - jh = -1.5 - jv = 2.3 - horizontal_qubit_pairs = [] - for this_r in range(rows): - current_qubit_pairs = [] - for this_c in range(columns - 1): - current_qubit_pairs.append(( - cirq.GridQubit(this_r, this_c), - cirq.GridQubit(this_r, this_c + 1), - )) - horizontal_qubit_pairs += current_qubit_pairs - - vertical_qubit_pairs = [] - for this_c in range(columns): - current_qubit_pairs = [] - for this_r in range(rows - 1): - current_qubit_pairs.append(( - cirq.GridQubit(this_r, this_c), - cirq.GridQubit(this_r + 1, this_c), - )) - vertical_qubit_pairs += current_qubit_pairs - - for t, pauli in enumerate([cirq.X, cirq.Y, cirq.Z], start=1): - test_shard = hamiltonian.heisenberg_hamiltonian_shard( - rows, columns, jh, jv, t) - actual_shard = cirq.PauliSum() - for q0, q1 in horizontal_qubit_pairs: - actual_shard += jh * pauli(q0) * pauli(q1) - for q0, q1 in vertical_qubit_pairs: - actual_shard += jv * pauli(q0) * pauli(q1) - self.assertEqual(actual_shard, test_shard) - - -class TFIMTest(tf.test.TestCase): - """Test components related to the 2D Transverse Field Ising Model.""" - - def test_tfim_x_shard(self): - """Test that every qubit gets an X term at critical field value.""" - rows_list = [2, 3, 4, 5] - columns_list = [2, 3, 4, 5] - for rows in rows_list: - for columns in columns_list: - lambda_crit = 3.05 - qubits = cirq.GridQubit.rect(rows, columns) - test_shard = hamiltonian.tfim_x_shard(rows, columns) - actual_shard = cirq.PauliSum() - for q in qubits: - actual_shard += lambda_crit * cirq.X(q) - self.assertEqual(actual_shard, test_shard) - - def test_tfim_zz_shard(self): - """Test that ZZ interactions show up on a toroidal grid.""" - rows_list = [2, 3, 4, 5] - columns_list = [2, 3, 4, 5] - for rows in rows_list: - for columns in columns_list: - horizontal_qubit_pairs = [] - for this_r in range(rows): - current_qubit_pairs = [] - for this_c in range(columns): - current_qubit_pairs.append(( - cirq.GridQubit(this_r, this_c), - cirq.GridQubit(this_r, (this_c + 1) % columns), - )) - horizontal_qubit_pairs += current_qubit_pairs - - vertical_qubit_pairs = [] - for this_c in range(columns): - current_qubit_pairs = [] - for this_r in range(rows): - current_qubit_pairs.append(( - cirq.GridQubit(this_r, this_c), - cirq.GridQubit((this_r + 1) % rows, this_c), - )) - vertical_qubit_pairs += current_qubit_pairs - - test_shard = hamiltonian.tfim_zz_shard(rows, columns) - actual_shard = cirq.PauliSum() - for q0, q1 in horizontal_qubit_pairs: - actual_shard += cirq.Z(q0) * cirq.Z(q1) - for q0, q1 in vertical_qubit_pairs: - actual_shard += cirq.Z(q0) * cirq.Z(q1) - self.assertEqual(actual_shard, test_shard) - - -if __name__ == "__main__": - print("Running hamiltonian_test.py ...") - tf.test.main() diff --git a/tests/energy_infer_test.py b/tests/inference/ebm_test.py similarity index 84% rename from tests/energy_infer_test.py rename to tests/inference/ebm_test.py index 960551ed..b4de21ca 100644 --- a/tests/energy_infer_test.py +++ b/tests/inference/ebm_test.py @@ -12,22 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the energy_infer module.""" +"""Tests for qhbmlib.inference.ebm""" import functools import tensorflow as tf import tensorflow_probability as tfp -from qhbmlib import energy_infer -from qhbmlib import energy_model -from qhbmlib import energy_model_utils +from qhbmlib import inference +from qhbmlib import models from qhbmlib import utils from tests import test_util -class NullEnergy(energy_model.BitstringEnergy): +class NullEnergy(models.BitstringEnergy): """Simple empty energy.""" def __init__(self, bits): @@ -39,7 +38,7 @@ def __init__(self, bits): class EnergyInferenceTest(tf.test.TestCase): """Tests a simple instantiation of EnergyInference.""" - class TwoOutcomes(energy_infer.EnergyInference): + class TwoOutcomes(inference.EnergyInference): """EnergyInference which is independent of the input energy.""" def __init__(self, null_energy, num_expectation_samples, bitstring_1, @@ -90,12 +89,12 @@ def setUp(self): self.bitstring_1 = tf.constant([1, 1, 0, 1, 0], dtype=tf.int8) self.bitstring_2 = tf.constant([0, 0, 0, 1, 1], dtype=tf.int8) self.p_1 = 0.1 - self.energy = NullEnergy(list(range(5))) - self.e_infer = self.TwoOutcomes(self.energy, self.num_samples, + self.actual_energy = NullEnergy(list(range(5))) + self.e_infer = self.TwoOutcomes(self.actual_energy, self.num_samples, self.bitstring_1, self.bitstring_2, self.p_1) - spins_from_bitstrings = energy_model_utils.SpinsFromBitstrings() - parity = energy_model_utils.Parity(list(range(5)), 2) + spins_from_bitstrings = models.SpinsFromBitstrings() + parity = models.Parity(list(range(5)), 2) def test_function(bitstrings): """Simple test function to send to expectation.""" @@ -140,17 +139,17 @@ def test_init(self): bits = [0, 1, 3] order = 2 expected_name = "test_analytic_dist_name" - energy = energy_model.KOBE(bits, order) + actual_energy = models.KOBE(bits, order) expected_bitstrings = tf.constant( [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], dtype=tf.int8) expected_seed = tf.constant([44, 22], tf.int32) - expected_energies = energy(expected_bitstrings) - actual_layer = energy_infer.AnalyticEnergyInference(energy, - self.num_samples, - expected_seed, - expected_name) + expected_energies = actual_energy(expected_bitstrings) + actual_layer = inference.AnalyticEnergyInference(actual_energy, + self.num_samples, + expected_seed, + expected_name) self.assertEqual(actual_layer.name, expected_name) self.assertAllEqual(actual_layer.seed, expected_seed) self.assertAllEqual(actual_layer.all_bitstrings, expected_bitstrings) @@ -163,8 +162,8 @@ def test_sample(self): """Confirms bitstrings are sampled as expected.""" # Single bit test. - one_bit_energy = energy_model.KOBE([0], 1) - actual_layer = energy_infer.AnalyticEnergyInference( + one_bit_energy = models.KOBE([0], 1) + actual_layer = inference.AnalyticEnergyInference( one_bit_energy, self.num_samples, initial_seed=self.tfp_seed) # For single factor Bernoulli, theta=0 is 50% chance of 1. @@ -195,9 +194,9 @@ def test_sample(self): # Three bit tests. # First a uniform sampling test. - three_bit_energy = energy_model.KOBE([0, 1, 2], 3, - tf.keras.initializers.Constant(0.0)) - actual_layer = energy_infer.AnalyticEnergyInference( + three_bit_energy = models.KOBE([0, 1, 2], 3, + tf.keras.initializers.Constant(0.0)) + actual_layer = inference.AnalyticEnergyInference( three_bit_energy, self.num_samples, initial_seed=self.tfp_seed) # Redefine sample wrapper because we made a new AnalyticEnergyInference. @@ -242,9 +241,9 @@ def test_sample(self): def test_samples_seeded(self): """Confirm seeding fixes samples for given energy.""" num_bits = 5 - energy = energy_model.KOBE(list(range(num_bits)), 2) - actual_layer = energy_infer.AnalyticEnergyInference( - energy, self.num_samples, initial_seed=self.tfp_seed) + actual_energy = models.KOBE(list(range(num_bits)), 2) + actual_layer = inference.AnalyticEnergyInference( + actual_energy, self.num_samples, initial_seed=self.tfp_seed) sample_wrapper = tf.function(actual_layer.sample) samples_1 = sample_wrapper(self.num_samples) @@ -328,14 +327,14 @@ def call(self, inputs): num_bits = 3 theta = tf.Variable(tf.random.uniform([], -3, -2), name="theta") energy_layers = [AllOnes(theta)] - energy = energy_model.BitstringEnergy(list(range(num_bits)), energy_layers) + actual_energy = models.BitstringEnergy(list(range(num_bits)), energy_layers) theta_exp = tf.math.exp(-1.0 * theta) partition = tf.math.pow(2.0, num_bits) - 1 + theta_exp partition_inverse = tf.math.pow(partition, -1) prob_x_star = partition_inverse * theta_exp - e_infer = energy_infer.AnalyticEnergyInference( - energy, self.num_samples, initial_seed=self.tfp_seed) + e_infer = inference.AnalyticEnergyInference( + actual_energy, self.num_samples, initial_seed=self.tfp_seed) mu = tf.Variable(tf.random.uniform([], 1, 2), name="mu") f = AllOnes(mu) @@ -420,11 +419,11 @@ def test_expectation_finite_difference(self): order = 2 nonzero_init = tf.keras.initializers.RandomUniform( 1, 2, seed=self.tf_random_seed) - energy = energy_model.KOBE(list(range(num_bits)), order, nonzero_init) - e_infer = energy_infer.AnalyticEnergyInference( - energy, self.num_samples, initial_seed=self.tfp_seed) + actual_energy = models.KOBE(list(range(num_bits)), order, nonzero_init) + e_infer = inference.AnalyticEnergyInference( + actual_energy, self.num_samples, initial_seed=self.tfp_seed) # Only one trainable variable in KOBE. - energy_var = energy.trainable_variables[0] + energy_var = actual_energy.trainable_variables[0] scalar_var = tf.Variable( tf.random.uniform([], 1, 2, tf.float32, self.tf_random_seed)) @@ -487,17 +486,17 @@ def test_log_partition(self): test_thetas = tf.constant([1.5, 2.7, -4.0]) expected_log_partition = tf.math.log(tf.constant(3641.8353)) - energy = energy_model.KOBE([0, 1], 2) - actual_layer = energy_infer.AnalyticEnergyInference(energy, - self.num_samples) - energy.set_weights([test_thetas]) + actual_energy = models.KOBE([0, 1], 2) + actual_layer = inference.AnalyticEnergyInference(actual_energy, + self.num_samples) + actual_energy.set_weights([test_thetas]) log_partition_wrapper = tf.function(actual_layer.log_partition) with tf.GradientTape() as tape: actual_log_partition = log_partition_wrapper() self.assertAllClose(actual_log_partition, expected_log_partition) - old_kernel = energy.post_process[0].kernel.read_value() + old_kernel = actual_energy.post_process[0].kernel.read_value() kernel_len = tf.shape(old_kernel)[0].numpy().tolist() all_bitstrings = tf.constant([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=tf.int8) @@ -505,9 +504,10 @@ def test_log_partition(self): def exact_log_partition(k, delta): """Perturbs the kth variable and calculates the log partition.""" new_kernel = old_kernel + delta * tf.one_hot(k, kernel_len, 1.0, 0.0) - energy.set_weights([new_kernel]) - delta_log_partition = tf.reduce_logsumexp(-1.0 * energy(all_bitstrings)) - energy.set_weights([old_kernel]) + actual_energy.set_weights([new_kernel]) + delta_log_partition = tf.reduce_logsumexp(-1.0 * + actual_energy(all_bitstrings)) + actual_energy.set_weights([old_kernel]) return delta_log_partition derivative_list = [] @@ -518,7 +518,7 @@ def exact_log_partition(k, delta): expected_log_partition_grad = tf.constant([derivative_list]) actual_log_partition_grad = tape.gradient(actual_log_partition, - energy.trainable_variables) + actual_energy.trainable_variables) self.assertAllClose(actual_log_partition_grad, expected_log_partition_grad, self.close_rtol) @@ -528,10 +528,10 @@ def test_entropy(self): test_thetas = tf.constant([1.5, 2.7, -4.0]) expected_entropy = tf.constant(0.00233551808) - energy = energy_model.KOBE([0, 1], 2) - actual_layer = energy_infer.AnalyticEnergyInference(energy, - self.num_samples) - energy.set_weights([test_thetas]) + actual_energy = models.KOBE([0, 1], 2) + actual_layer = inference.AnalyticEnergyInference(actual_energy, + self.num_samples) + actual_energy.set_weights([test_thetas]) entropy_wrapper = tf.function(actual_layer.entropy) actual_entropy = entropy_wrapper() @@ -540,9 +540,8 @@ def test_entropy(self): @test_util.eager_mode_toggle def test_call(self): """Confirms that call behaves correctly.""" - one_bit_energy = energy_model.KOBE([0], 1, - tf.keras.initializers.Constant(0.0)) - actual_layer = energy_infer.AnalyticEnergyInference( + one_bit_energy = models.KOBE([0], 1, tf.keras.initializers.Constant(0.0)) + actual_layer = inference.AnalyticEnergyInference( one_bit_energy, self.num_samples, initial_seed=self.tfp_seed) actual_dist = actual_layer(None) self.assertIsInstance(actual_dist, tfp.distributions.Categorical) @@ -577,10 +576,12 @@ def test_init(self): """Tests that components are initialized correctly.""" bits = [-3, 4, 6] expected_name = "test_bernoulli_dist_name" - energy = energy_model.BernoulliEnergy(bits) + actual_energy = models.BernoulliEnergy(bits) expected_seed = tf.constant([4, 12], dtype=tf.int32) - actual_layer = energy_infer.BernoulliEnergyInference( - energy, self.num_samples, expected_seed, expected_name) + actual_layer = inference.BernoulliEnergyInference(actual_energy, + self.num_samples, + expected_seed, + expected_name) self.assertEqual(actual_layer.name, expected_name) self.assertAllEqual(actual_layer.seed, expected_seed) self.assertIsInstance(actual_layer.distribution, @@ -589,12 +590,12 @@ def test_init(self): @test_util.eager_mode_toggle def test_sample(self): """Confirms that bitstrings are sampled as expected.""" - energy = energy_model.BernoulliEnergy([1]) - actual_layer = energy_infer.BernoulliEnergyInference( - energy, self.num_samples, initial_seed=self.tfp_seed) + actual_energy = models.BernoulliEnergy([1]) + actual_layer = inference.BernoulliEnergyInference( + actual_energy, self.num_samples, initial_seed=self.tfp_seed) # For single factor Bernoulli, theta = 0 is 50% chance of 1. - energy.set_weights([tf.constant([0.0])]) + actual_energy.set_weights([tf.constant([0.0])]) sample_wrapper = tf.function(actual_layer.sample) samples = sample_wrapper(self.num_samples) @@ -610,7 +611,7 @@ def test_sample(self): self.assertAllClose(1.0, counts[0] / counts[1], rtol=self.close_rtol) # Large value of theta pins the bit. - energy.set_weights([tf.constant([1000.0])]) + actual_energy.set_weights([tf.constant([1000.0])]) samples = sample_wrapper(self.num_samples) # check that we got only one bitstring @@ -618,10 +619,10 @@ def test_sample(self): self.assertAllEqual(bitstrings, [[1]]) # Two bit tests. - energy = energy_model.BernoulliEnergy([0, 1], - tf.keras.initializers.Constant(0.0)) - actual_layer = energy_infer.BernoulliEnergyInference( - energy, self.num_samples, initial_seed=self.tfp_seed) + actual_energy = models.BernoulliEnergy([0, 1], + tf.keras.initializers.Constant(0.0)) + actual_layer = inference.BernoulliEnergyInference( + actual_energy, self.num_samples, initial_seed=self.tfp_seed) # New sample wrapper because we have new layer. sample_wrapper = tf.function(actual_layer.sample) @@ -638,7 +639,7 @@ def test_sample(self): ) # Test one pinned, one free bit - energy.set_weights([tf.constant([-1000.0, 0.0])]) + actual_energy.set_weights([tf.constant([-1000.0, 0.0])]) samples = sample_wrapper(self.num_samples) # check that we get 00 and 01. for b in [[0, 0], [0, 1]]: @@ -655,9 +656,9 @@ def test_sample(self): def test_samples_seeded(self): """Confirm seeding fixes samples for given energy.""" num_bits = 5 - energy = energy_model.BernoulliEnergy(list(range(num_bits))) - actual_layer = energy_infer.BernoulliEnergyInference( - energy, self.num_samples, initial_seed=self.tfp_seed) + actual_energy = models.BernoulliEnergy(list(range(num_bits))) + actual_layer = inference.BernoulliEnergyInference( + actual_energy, self.num_samples, initial_seed=self.tfp_seed) sample_wrapper = tf.function(actual_layer.sample) samples_1 = sample_wrapper(self.num_samples) @@ -678,25 +679,28 @@ def test_log_partition(self): dtype=tf.int8) ebm_init = tf.keras.initializers.RandomUniform( -2, -1, seed=self.tf_random_seed) - energy = energy_model.BernoulliEnergy([5, 6, 7], ebm_init) - actual_layer = energy_infer.BernoulliEnergyInference( - energy, self.num_samples, self.tfp_seed) - expected_log_partition = tf.reduce_logsumexp(-1.0 * energy(all_bitstrings)) + actual_energy = models.BernoulliEnergy([5, 6, 7], ebm_init) + actual_layer = inference.BernoulliEnergyInference(actual_energy, + self.num_samples, + self.tfp_seed) + expected_log_partition = tf.reduce_logsumexp(-1.0 * + actual_energy(all_bitstrings)) log_partition_wrapper = tf.function(actual_layer.log_partition) with tf.GradientTape() as tape: actual_log_partition = log_partition_wrapper() self.assertAllClose(actual_log_partition, expected_log_partition) - old_kernel = energy.post_process[0].kernel.read_value() + old_kernel = actual_energy.post_process[0].kernel.read_value() kernel_len = tf.shape(old_kernel)[0].numpy().tolist() def exact_log_partition(k, delta): """Perturbs the kth variable and calculates the log partition.""" new_kernel = old_kernel + delta * tf.one_hot(k, kernel_len, 1.0, 0.0) - energy.set_weights([new_kernel]) - delta_log_partition = tf.reduce_logsumexp(-1.0 * energy(all_bitstrings)) - energy.set_weights([old_kernel]) + actual_energy.set_weights([new_kernel]) + delta_log_partition = tf.reduce_logsumexp(-1.0 * + actual_energy(all_bitstrings)) + actual_energy.set_weights([old_kernel]) return delta_log_partition derivative_list = [] @@ -707,7 +711,7 @@ def exact_log_partition(k, delta): expected_log_partition_grad = tf.constant([derivative_list]) actual_log_partition_grad = tape.gradient(actual_log_partition, - energy.trainable_variables) + actual_energy.trainable_variables) self.assertAllClose(actual_log_partition_grad, expected_log_partition_grad, self.close_rtol) @@ -737,10 +741,10 @@ def test_entropy(self): self.assertAllClose(1.0, tf.reduce_sum(all_probs)) expected_entropy = -1.0 * tf.reduce_sum(all_probs * tf.math.log(all_probs)) - energy = energy_model.BernoulliEnergy([0, 1, 2]) - actual_layer = energy_infer.BernoulliEnergyInference( - energy, self.num_samples) - energy.set_weights([test_thetas]) + actual_energy = models.BernoulliEnergy([0, 1, 2]) + actual_layer = inference.BernoulliEnergyInference(actual_energy, + self.num_samples) + actual_energy.set_weights([test_thetas]) entropy_wrapper = tf.function(actual_layer.entropy) actual_entropy = entropy_wrapper() @@ -749,10 +753,10 @@ def test_entropy(self): @test_util.eager_mode_toggle def test_call(self): """Confirms that calling the layer works correctly.""" - energy = energy_model.BernoulliEnergy([1], - tf.keras.initializers.Constant(0.0)) - actual_layer = energy_infer.BernoulliEnergyInference( - energy, self.num_samples, initial_seed=self.tfp_seed) + actual_energy = models.BernoulliEnergy([1], + tf.keras.initializers.Constant(0.0)) + actual_layer = inference.BernoulliEnergyInference( + actual_energy, self.num_samples, initial_seed=self.tfp_seed) actual_dist = actual_layer(None) self.assertIsInstance(actual_dist, tfp.distributions.Bernoulli) @@ -772,5 +776,5 @@ def test_call(self): if __name__ == "__main__": - print("Running energy_infer_test.py ...") + print("Running ebm_test.py ...") tf.test.main() diff --git a/tests/energy_infer_utils_test.py b/tests/inference/ebm_utils_test.py similarity index 77% rename from tests/energy_infer_utils_test.py rename to tests/inference/ebm_utils_test.py index c60b0127..ebfd0fc2 100644 --- a/tests/energy_infer_utils_test.py +++ b/tests/inference/ebm_utils_test.py @@ -12,15 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the energy_infer_utils module.""" +"""Tests for qhbmlib.inference.ebm_utils""" import random import tensorflow as tf -from qhbmlib import energy_infer -from qhbmlib import energy_infer_utils -from qhbmlib import energy_model +from qhbmlib import inference +from qhbmlib import models from qhbmlib import utils @@ -42,19 +41,19 @@ def test_probabilities(self): tf.keras.layers.Dense(units[i], activation=activations[i])) expected_layer_list.append(tf.keras.layers.Dense(1)) expected_layer_list.append(utils.Squeeze(-1)) - energy = energy_model.BitstringEnergy( + actual_energy = models.BitstringEnergy( list(range(num_bits)), expected_layer_list) num_expectation_samples = 1 # Required but unused - infer = energy_infer.AnalyticEnergyInference(energy, - num_expectation_samples) + infer = inference.AnalyticEnergyInference(actual_energy, + num_expectation_samples) expected_probabilities = infer.distribution.probs_parameter() - probabilities_wrapped = tf.function(energy_infer_utils.probabilities) - actual_probabilities = probabilities_wrapped(energy) + probabilities_wrapped = tf.function(inference.probabilities) + actual_probabilities = probabilities_wrapped(actual_energy) self.assertAllClose(actual_probabilities, expected_probabilities) if __name__ == "__main__": - print("Running energy_infer_utils_test.py ...") + print("Running ebm_utils_test.py ...") tf.test.main() diff --git a/tests/hamiltonian_infer_test.py b/tests/inference/qhbm_test.py similarity index 82% rename from tests/hamiltonian_infer_test.py rename to tests/inference/qhbm_test.py index 697a3890..29a61223 100644 --- a/tests/hamiltonian_infer_test.py +++ b/tests/inference/qhbm_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the hamiltonian_infer module.""" +"""Tests for qhbmlib.inference.qhbm""" import absl from absl.testing import parameterized @@ -24,12 +24,10 @@ import tensorflow_quantum as tfq from tensorflow_quantum.python import util as tfq_util -from qhbmlib import circuit_infer -from qhbmlib import circuit_model -from qhbmlib import energy_infer -from qhbmlib import energy_model -from qhbmlib import hamiltonian_model -from qhbmlib import hamiltonian_infer +from qhbmlib import inference +from qhbmlib import models +from qhbmlib.models import energy +from qhbmlib.models import hamiltonian from qhbmlib import utils from tests import test_util @@ -44,33 +42,31 @@ def setUp(self): # Model hamiltonian num_bits = 3 - self.energy = energy_model.BernoulliEnergy(list(range(num_bits))) - self.expected_e_inference = energy_infer.AnalyticEnergyInference( - self.energy, self.num_samples) + self.actual_energy = models.BernoulliEnergy(list(range(num_bits))) + self.expected_ebm = inference.AnalyticEnergyInference( + self.actual_energy, self.num_samples) # pin first and last bits, middle bit free. - self.energy.set_weights([tf.constant([-23, 0, 17])]) + self.actual_energy.set_weights([tf.constant([-23, 0, 17])]) qubits = cirq.GridQubit.rect(1, num_bits) symbols = set() num_symbols = 20 for _ in range(num_symbols): symbols.add("".join(random.sample(string.ascii_letters, 10))) self.pqc = tfq_util.random_symbol_circuit(qubits, symbols) - circuit = circuit_model.DirectQuantumCircuit(self.pqc) - circuit.build([]) - self.expected_q_inference = circuit_infer.QuantumInference(circuit) + actual_circuit = models.DirectQuantumCircuit(self.pqc) + self.expected_qnn = inference.QuantumInference(actual_circuit) # Inference self.expected_name = "nameforaQHBM" - self.actual_qhbm = hamiltonian_infer.QHBM(self.expected_e_inference, - self.expected_q_inference, - self.expected_name) + self.actual_qhbm = inference.QHBM(self.expected_ebm, self.expected_qnn, + self.expected_name) self.tfp_seed = tf.constant([5, 1], tf.int32) def test_init(self): """Tests QHBM initialization.""" - self.assertEqual(self.actual_qhbm.e_inference, self.expected_e_inference) - self.assertEqual(self.actual_qhbm.q_inference, self.expected_q_inference) + self.assertEqual(self.actual_qhbm.e_inference, self.expected_ebm) + self.assertEqual(self.actual_qhbm.q_inference, self.expected_qnn) self.assertEqual(self.actual_qhbm.name, self.expected_name) @test_util.eager_mode_toggle @@ -80,8 +76,8 @@ def test_circuits(self): actual_circuits, actual_counts = circuits_wrapper(self.num_samples) # Circuits with the allowed-to-be-sampled bitstrings prepended. - u = tfq.from_tensor(self.actual_qhbm.hamiltonian.circuit.pqc)[0] - qubits = self.actual_qhbm.hamiltonian.circuit.qubits + u = tfq.from_tensor(self.actual_qhbm.q_inference.circuit.pqc)[0] + qubits = self.actual_qhbm.q_inference.circuit.qubits expected_circuits_deserialized = [ cirq.Circuit( cirq.X(qubits[0])**0, @@ -117,19 +113,20 @@ def test_circuits(self): def test_circuit_param_update(self): """Confirm circuits are different after updating energy model parameters.""" num_bits = 2 - energy = energy_model.BernoulliEnergy(list(range(num_bits))) - e_infer = energy_infer.BernoulliEnergyInference(energy, self.num_samples) + actual_energy = models.BernoulliEnergy(list(range(num_bits))) + e_infer = inference.BernoulliEnergyInference(actual_energy, + self.num_samples) qubits = cirq.GridQubit.rect(1, num_bits) pqc = cirq.Circuit(cirq.Y(q) for q in qubits) - circuit = circuit_model.DirectQuantumCircuit(pqc) - q_infer = circuit_infer.QuantumInference(circuit) + actual_circuit = models.DirectQuantumCircuit(pqc) + q_infer = inference.QuantumInference(actual_circuit) - h_infer = hamiltonian_infer.QHBM(e_infer, q_infer) + h_infer = inference.QHBM(e_infer, q_infer) circuits_wrapper = tf.function(h_infer.circuits) # Pin Bernoulli to [0, 1] - energy.set_weights([tf.constant([-1000, 1000])]) + actual_energy.set_weights([tf.constant([-1000, 1000])]) expected_circuits_1 = tfq.from_tensor( tfq.convert_to_tensor( [cirq.Circuit(cirq.X(qubits[0])**0, cirq.X(qubits[1])) + pqc])) @@ -138,7 +135,7 @@ def test_circuit_param_update(self): self.assertAllEqual(actual_circuits_1, expected_circuits_1) # Change pin to [1, 0] - energy.set_weights([tf.constant([1000, -1000])]) + actual_energy.set_weights([tf.constant([1000, -1000])]) expected_circuits_2 = tfq.from_tensor( tfq.convert_to_tensor( [cirq.Circuit(cirq.X(qubits[0]), @@ -208,8 +205,8 @@ def test_expectation_pauli(self): @parameterized.parameters({ "energy_class": energy_class, "energy_args": energy_args, - } for energy_class, energy_args in zip( - [energy_model.BernoulliEnergy, energy_model.KOBE], [[], [2]])) + } for energy_class, energy_args in zip([models.BernoulliEnergy, models.KOBE], + [[], [2]])) @test_util.eager_mode_toggle def test_expectation_modular_hamiltonian(self, energy_class, energy_args): """Confirm expectation of modular Hamiltonians works.""" @@ -221,9 +218,8 @@ def test_expectation_modular_hamiltonian(self, energy_class, energy_args): energy_h = energy_class(*([list(range(num_bits))] + energy_args)) energy_h.build([None, num_bits]) raw_circuit_h = cirq.testing.random_circuit(qubits, n_moments, act_fraction) - circuit_h = circuit_model.DirectQuantumCircuit(raw_circuit_h) - circuit_h.build([]) - hamiltonian_measure = hamiltonian_model.Hamiltonian(energy_h, circuit_h) + circuit_h = models.DirectQuantumCircuit(raw_circuit_h) + hamiltonian_measure = models.Hamiltonian(energy_h, circuit_h) # unitary num_layers = 3 @@ -251,5 +247,5 @@ def test_expectation_modular_hamiltonian(self, energy_class, energy_args): if __name__ == "__main__": - absl.logging.info("Running hamiltonian_infer_test.py ...") + absl.logging.info("Running qhbm_test.py ...") tf.test.main() diff --git a/tests/hamiltonian_infer_utils_test.py b/tests/inference/qhbm_utils_test.py similarity index 77% rename from tests/hamiltonian_infer_utils_test.py rename to tests/inference/qhbm_utils_test.py index c9f978b6..f770a721 100644 --- a/tests/hamiltonian_infer_utils_test.py +++ b/tests/inference/qhbm_utils_test.py @@ -12,15 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the hamiltonian_infer_utils module.""" +"""Tests for qhbmlib.inference.qhbm_utils""" import cirq import tensorflow as tf -from qhbmlib import circuit_model -from qhbmlib import energy_model -from qhbmlib import hamiltonian_model -from qhbmlib import hamiltonian_infer_utils +from qhbmlib import inference +from qhbmlib import models from tests import test_util @@ -33,22 +31,22 @@ def test_density_matrix(self): # Check density matrix of Bell state. num_bits = 2 - energy = energy_model.BernoulliEnergy(list(range(num_bits))) - energy.build([None, num_bits]) - energy.set_weights([tf.constant([-10.0, -10.0])]) # pin at |00> + actual_energy = models.BernoulliEnergy(list(range(num_bits))) + actual_energy.build([None, num_bits]) + actual_energy.set_weights([tf.constant([-10.0, -10.0])]) # pin at |00> qubits = cirq.GridQubit.rect(1, num_bits) test_u = cirq.Circuit([cirq.H(qubits[0]), cirq.CNOT(qubits[0], qubits[1])]) - circuit = circuit_model.DirectQuantumCircuit(test_u) - circuit.build([]) - model = hamiltonian_model.Hamiltonian(energy, circuit) + actual_circuit = models.DirectQuantumCircuit(test_u) + actual_circuit.build([]) + model = models.Hamiltonian(actual_energy, actual_circuit) expected_dm = tf.constant( [[0.5, 0, 0, 0.5], [0, 0, 0, 0], [0, 0, 0, 0], [0.5, 0, 0, 0.5]], tf.complex64, ) - density_matrix_wrapper = tf.function(hamiltonian_infer_utils.density_matrix) + density_matrix_wrapper = tf.function(inference.density_matrix) actual_dm = density_matrix_wrapper(model) self.assertAllClose(actual_dm, expected_dm) @@ -71,9 +69,9 @@ def test_fidelity_self(self): model, _ = test_util.get_random_hamiltonian_and_inference( qubits, num_layers, "test_fidelity", num_samples) - density_matrix_wrapper = tf.function(hamiltonian_infer_utils.density_matrix) + density_matrix_wrapper = tf.function(inference.density_matrix) model_dm = density_matrix_wrapper(model) - fidelity_wrapper = tf.function(hamiltonian_infer_utils.fidelity) + fidelity_wrapper = tf.function(inference.fidelity) actual_fidelity = fidelity_wrapper(model, model_dm) expected_fidelity = 1.0 @@ -102,15 +100,15 @@ def direct_fidelity(rho, sigma): num_samples = 1 # required but unused h, _ = test_util.get_random_hamiltonian_and_inference( qubits, num_layers, identifier, num_samples) - h_dm = hamiltonian_infer_utils.density_matrix(h) + h_dm = inference.density_matrix(h) expected_fidelity = direct_fidelity(h_dm, sigma) - fidelity_wrapper = tf.function(hamiltonian_infer_utils.fidelity) + fidelity_wrapper = tf.function(inference.fidelity) actual_fidelity = fidelity_wrapper(h, sigma) self.assertAllClose( actual_fidelity, expected_fidelity, rtol=self.close_rtol) if __name__ == "__main__": - print("Running hamiltonian_infer_utils_test.py ...") + print("Running qhbm_utils_test.py ...") tf.test.main() diff --git a/tests/qmhl_loss_test.py b/tests/inference/qmhl_loss_test.py similarity index 85% rename from tests/qmhl_loss_test.py rename to tests/inference/qmhl_loss_test.py index 7d15e113..d7a4e29e 100644 --- a/tests/qmhl_loss_test.py +++ b/tests/inference/qmhl_loss_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the QMHL loss and gradients.""" +"""Tests for qhbmlib.inference.qmhl_loss""" import functools import math @@ -22,14 +22,9 @@ import tensorflow as tf import tensorflow_probability as tfp -from qhbmlib import energy_infer -from qhbmlib import energy_model -from qhbmlib import circuit_infer -from qhbmlib import circuit_model -from qhbmlib import hamiltonian_infer -from qhbmlib import hamiltonian_infer_utils -from qhbmlib import qmhl_loss -from qhbmlib import quantum_data +from qhbmlib import data +from qhbmlib import inference +from qhbmlib import models from tests import test_util @@ -53,7 +48,7 @@ def setUp(self): def test_self_qmhl(self): """Confirms known value of the QMHL loss of a model against itself.""" num_layers = 1 - qmhl_wrapper = tf.function(qmhl_loss.qmhl) + qmhl_wrapper = tf.function(inference.qmhl) for num_qubits in self.num_qubits_list: qubits = cirq.GridQubit.rect(1, num_qubits) data_h, data_infer = test_util.get_random_hamiltonian_and_inference( @@ -66,7 +61,7 @@ def test_self_qmhl(self): initializer_seed=self.tf_random_seed) # Set data equal to the model data_h.set_weights(model_h.get_weights()) - data = quantum_data.QHBMData(data_infer) + actual_data = data.QHBMData(data_infer) # Trained loss is the entropy. expected_loss = model_infer.e_inference.entropy() @@ -76,7 +71,7 @@ def test_self_qmhl(self): ] with tf.GradientTape() as tape: - actual_loss = qmhl_wrapper(data, model_infer) + actual_loss = qmhl_wrapper(actual_data, model_infer) actual_loss_derivative = tape.gradient(actual_loss, model_h.trainable_variables) @@ -89,18 +84,18 @@ def test_hamiltonian_qmhl(self): """Tests derivatives of QMHL with respect to the model.""" # TODO(#171): Delta function seems generalizable. - def delta_qmhl(k, var, data, model_qhbm, delta): + def delta_qmhl(k, var, actual_data, model_qhbm, delta): """Calculates the qmhl loss with the kth entry of `var` perturbed.""" num_elts = tf.size(var) old_value = var.read_value() var.assign(old_value + delta * tf.one_hot(k, num_elts, 1.0, 0.0)) - delta_loss = qmhl_loss.qmhl(data, model_qhbm) + delta_loss = inference.qmhl(actual_data, model_qhbm) var.assign(old_value) return delta_loss - qmhl_wrapper = tf.function(qmhl_loss.qmhl) + qmhl_wrapper = tf.function(inference.qmhl) - def qmhl_derivative(variables_list, data, model_qhbm): + def qmhl_derivative(variables_list, actual_data, model_qhbm): """Approximately differentiates QMHL wih respect to the inputs.""" derivatives = [] for var in variables_list: @@ -108,7 +103,7 @@ def qmhl_derivative(variables_list, data, model_qhbm): num_elts = tf.size(var) # Assumes variable is 1D for n in range(num_elts): this_derivative = test_util.approximate_derivative( - functools.partial(delta_qmhl, n, var, data, model_qhbm)) + functools.partial(delta_qmhl, n, var, actual_data, model_qhbm)) var_derivative_list.append(this_derivative.numpy()) derivatives.append(tf.constant(var_derivative_list)) return derivatives @@ -123,7 +118,7 @@ def qmhl_derivative(variables_list, data, model_qhbm): self.num_samples, initializer_seed=self.tf_random_seed, ebm_seed=self.tfp_seed) - data = quantum_data.QHBMData(data_qhbm) + actual_data = data.QHBMData(data_qhbm) model_h, model_qhbm = test_util.get_random_hamiltonian_and_inference( qubits, @@ -135,12 +130,12 @@ def qmhl_derivative(variables_list, data, model_qhbm): # Make sure variables are trainable self.assertGreater(len(model_h.trainable_variables), 1) with tf.GradientTape() as tape: - actual_loss = qmhl_wrapper(data, model_qhbm) + actual_loss = qmhl_wrapper(actual_data, model_qhbm) actual_derivative = tape.gradient(actual_loss, model_h.trainable_variables) - expected_derivative = qmhl_derivative(model_h.trainable_variables, data, - model_qhbm) + expected_derivative = qmhl_derivative(model_h.trainable_variables, + actual_data, model_qhbm) # Changing model parameters is working if finite difference derivatives # are non-zero. Also confirms that model_h and data_h are different. tf.nest.map_structure( @@ -168,9 +163,9 @@ def test_loss_value_x_rot(self): # EBM ebm_init = tf.keras.initializers.RandomUniform( minval=ebm_const / 4, maxval=ebm_const, seed=self.tf_random_seed) - energy = energy_model.BernoulliEnergy(list(range(num_qubits)), ebm_init) - e_infer = energy_infer.BernoulliEnergyInference( - energy, self.num_samples, initial_seed=self.tfp_seed) + actual_energy = models.BernoulliEnergy(list(range(num_qubits)), ebm_init) + e_infer = inference.BernoulliEnergyInference( + actual_energy, self.num_samples, initial_seed=self.tfp_seed) # QNN qubits = cirq.GridQubit.rect(1, num_qubits) @@ -179,10 +174,10 @@ def test_loss_value_x_rot(self): cirq.rx(r_s)(q) for r_s, q in zip(r_symbols, qubits)) qnn_init = tf.keras.initializers.RandomUniform( minval=q_const / 4, maxval=q_const, seed=self.tf_random_seed) - circuit = circuit_model.DirectQuantumCircuit(r_circuit, qnn_init) - q_infer = circuit_infer.QuantumInference(circuit) - qhbm_infer = hamiltonian_infer.QHBM(e_infer, q_infer) - model = qhbm_infer.hamiltonian + actual_circuit = models.DirectQuantumCircuit(r_circuit, qnn_init) + q_infer = inference.QuantumInference(actual_circuit) + qhbm_infer = inference.QHBM(e_infer, q_infer) + model = qhbm_infer.modular_hamiltonian # Confirm qhbm_model QHBM test_thetas = model.energy.trainable_variables[0] @@ -195,7 +190,7 @@ def test_loss_value_x_rot(self): actual_log_partition, expected_log_partition, rtol=self.close_rtol) # Confirm qhbm_model modular Hamiltonian for 1 qubit case if num_qubits == 1: - actual_dm = hamiltonian_infer_utils.density_matrix(model) + actual_dm = inference.density_matrix(model) actual_log_dm = tf.linalg.logm(actual_dm) actual_ktp = -actual_log_dm - tf.eye( 2, dtype=tf.complex64) * tf.cast(actual_log_partition, tf.complex64) @@ -213,8 +208,8 @@ def test_loss_value_x_rot(self): self.tf_random_seed) y_rot = cirq.Circuit( cirq.ry(r.numpy())(q) for r, q in zip(alphas, qubits)) - data_circuit = circuit_model.DirectQuantumCircuit(y_rot) - data_q_infer = circuit_infer.QuantumInference(data_circuit) + data_circuit = models.DirectQuantumCircuit(y_rot) + data_q_infer = inference.QuantumInference(data_circuit) data_probs = tf.random.uniform([num_qubits], dtype=tf.float32, seed=self.tf_random_seed) @@ -223,7 +218,7 @@ def test_loss_value_x_rot(self): self.num_samples, seed=self.tfp_seed) # Load target data into a QuantumData class - class FixedData(quantum_data.QuantumData): + class FixedData(data.QuantumData): """Contains a fixed quantum data set.""" def __init__(self, samples, q_infer): @@ -236,16 +231,17 @@ def expectation(self, observable): raw_expectations = self.q_infer.expectation(self.samples, observable) return tf.math.reduce_mean(raw_expectations) - data = FixedData(data_samples, data_q_infer) - qmhl_wrapper = tf.function(qmhl_loss.qmhl) + actual_data = FixedData(data_samples, data_q_infer) + qmhl_wrapper = tf.function(inference.qmhl) with tf.GradientTape() as loss_tape: - actual_loss = qmhl_wrapper(data, qhbm_infer) + actual_loss = qmhl_wrapper(actual_data, qhbm_infer) # TODO(zaqqwerty): add way to use a log QHBM as observable on states expected_expectation = tf.reduce_sum(test_thetas * (2 * data_probs - 1) * tf.math.cos(alphas) * tf.math.cos(test_phis)) with tf.GradientTape() as expectation_tape: - actual_expectation = data.expectation(qhbm_infer.hamiltonian) + actual_expectation = actual_data.expectation( + qhbm_infer.modular_hamiltonian) self.assertAllClose(actual_expectation, expected_expectation, self.close_rtol) diff --git a/tests/circuit_infer_test.py b/tests/inference/qnn_test.py similarity index 91% rename from tests/circuit_infer_test.py rename to tests/inference/qnn_test.py index 92bd14e2..04cc4d67 100644 --- a/tests/circuit_infer_test.py +++ b/tests/inference/qnn_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the circuit_infer module.""" +"""Tests for qhbmlib.inference.qnn""" from absl import logging from absl.testing import parameterized @@ -28,11 +28,8 @@ import tensorflow_quantum as tfq from tensorflow_quantum.python import util as tfq_util -from qhbmlib import circuit_infer -from qhbmlib import circuit_model -from qhbmlib import circuit_model_utils -from qhbmlib import energy_model -from qhbmlib import hamiltonian_model +from qhbmlib import inference +from qhbmlib import models from tests import test_util @@ -54,7 +51,7 @@ def setUp(self): self.raw_qubits = cirq.GridQubit.rect(1, self.num_bits) p_param = sympy.Symbol("p") p_circuit = cirq.Circuit(cirq.X(q)**p_param for q in self.raw_qubits) - self.p_qnn = circuit_model.DirectQuantumCircuit( + self.p_qnn = models.DirectQuantumCircuit( p_circuit, initializer=tf.keras.initializers.RandomUniform( minval=-1.0, maxval=1.0, seed=self.tf_random_seed), @@ -65,7 +62,7 @@ def test_init(self): expected_backend = "noiseless" expected_differentiator = None expected_name = "TestOE" - actual_exp = circuit_infer.QuantumInference( + actual_exp = inference.QuantumInference( self.p_qnn, backend=expected_backend, differentiator=expected_differentiator, @@ -113,7 +110,7 @@ def test_expectation(self): """ # Build inference object - exp_infer = circuit_infer.QuantumInference(self.p_qnn) + exp_infer = inference.QuantumInference(self.p_qnn) # Get all the bitstrings multiple times. initial_states_list = 5 * list( @@ -198,7 +195,7 @@ def test_expectation_cirq(self): random_values = tf.Variable(initial_random_values) all_bitstrings = list(itertools.product([0, 1], repeat=self.num_bits)) - bitstring_circuit = circuit_model_utils.bit_circuit(qubits) + bitstring_circuit = models.circuit_utils.bit_circuit(qubits) bitstring_symbols = sorted(tfq.util.get_circuit_symbols(bitstring_circuit)) total_circuit = bitstring_circuit + raw_circuit @@ -228,10 +225,10 @@ def delta_expectations_func(k, var, delta): return delta_expectations # hamiltonian model and inference - circuit = circuit_model.QuantumCircuit( + actual_circuit = models.QuantumCircuit( tfq.convert_to_tensor([raw_circuit]), qubits, tf.constant(symbols), [random_values], [[]]) - q_infer = circuit_infer.QuantumInference(circuit) + q_infer = inference.QuantumInference(actual_circuit) # calculate expected values expected_expectations = delta_expectations_func(0, random_values, 0) @@ -261,9 +258,9 @@ def expectations_derivative(variables_list): return derivatives expected_expectations_derivative = tf.transpose( - tf.squeeze(expectations_derivative(circuit.trainable_variables))) + tf.squeeze(expectations_derivative(actual_circuit.trainable_variables))) actual_expectations_derivative = tf.squeeze( - tape.jacobian(actual_expectations, circuit.trainable_variables)) + tape.jacobian(actual_expectations, actual_circuit.trainable_variables)) self.assertNotAllClose( expected_expectations_derivative, @@ -277,8 +274,8 @@ def expectations_derivative(variables_list): @parameterized.parameters({ "energy_class": energy_class, "energy_args": energy_args, - } for energy_class, energy_args in zip( - [energy_model.BernoulliEnergy, energy_model.KOBE], [[], [2]])) + } for energy_class, energy_args in zip([models.BernoulliEnergy, models.KOBE], + [[], [2]])) @test_util.eager_mode_toggle def test_expectation_modular_hamiltonian(self, energy_class, energy_args): """Confirm expectation of modular Hamiltonians works.""" @@ -300,23 +297,23 @@ def test_expectation_modular_hamiltonian(self, energy_class, energy_args): raw_circuits, _ = tfq_util.random_symbol_circuit_resolver_batch( qubits, symbols, batch_size, n_moments=n_moments, p=act_fraction) raw_circuit_h = raw_circuits[0] - circuit_h = circuit_model.DirectQuantumCircuit(raw_circuit_h) + circuit_h = models.DirectQuantumCircuit(raw_circuit_h) circuit_h.build([]) initial_random_values = tf.random.uniform([len(symbols)], -1, 1, tf.float32, self.tf_random_seed) circuit_h.set_weights([initial_random_values]) - hamiltonian_measure = hamiltonian_model.Hamiltonian(energy_h, circuit_h) + hamiltonian_measure = models.Hamiltonian(energy_h, circuit_h) raw_shards = tfq.from_tensor(hamiltonian_measure.operator_shards) # set up the circuit to measure against model_raw_circuit = cirq.testing.random_circuit(qubits, n_moments, act_fraction) - model_circuit = circuit_model.DirectQuantumCircuit(model_raw_circuit) - model_infer = circuit_infer.QuantumInference(model_circuit) + model_circuit = models.DirectQuantumCircuit(model_raw_circuit) + model_infer = inference.QuantumInference(model_circuit) # bitstring injectors all_bitstrings = list(itertools.product([0, 1], repeat=self.num_bits)) - bitstring_circuit = circuit_model_utils.bit_circuit(qubits) + bitstring_circuit = models.circuit_utils.bit_circuit(qubits) bitstring_symbols = sorted(tfq.util.get_circuit_symbols(bitstring_circuit)) total_circuit = bitstring_circuit + model_raw_circuit + raw_circuit_h**-1 @@ -411,9 +408,9 @@ def test_sample_basic(self): list(itertools.product([0, 1], repeat=self.num_bits)), dtype=tf.int8) counts = tf.random.uniform([tf.shape(bitstrings)[0]], 100, 1000, tf.int32) - ident_qnn = circuit_model.DirectQuantumCircuit( + ident_qnn = models.DirectQuantumCircuit( cirq.Circuit(cirq.I(q) for q in self.raw_qubits), name="identity") - q_infer = circuit_infer.QuantumInference(ident_qnn) + q_infer = inference.QuantumInference(ident_qnn) sample_wrapper = tf.function(q_infer.sample) test_samples = sample_wrapper(bitstrings, counts) for i, (b, c) in enumerate(zip(bitstrings, counts)): @@ -421,9 +418,9 @@ def test_sample_basic(self): for j in range(c): self.assertAllEqual(test_samples[i][j], b) - flip_qnn = circuit_model.DirectQuantumCircuit( + flip_qnn = models.DirectQuantumCircuit( cirq.Circuit(cirq.X(q) for q in self.raw_qubits), name="flip") - q_infer = circuit_infer.QuantumInference(flip_qnn) + q_infer = inference.QuantumInference(flip_qnn) sample_wrapper = tf.function(q_infer.sample) test_samples = sample_wrapper(bitstrings, counts) for i, (b, c) in enumerate(zip(bitstrings, counts)): @@ -438,11 +435,11 @@ def test_sample_basic(self): self.raw_qubits[0])**ghz_param) + cirq.Circuit( cirq.CNOT(q0, q1) for q0, q1 in zip(self.raw_qubits, self.raw_qubits[1:])) - ghz_qnn = circuit_model.DirectQuantumCircuit( + ghz_qnn = models.DirectQuantumCircuit( ghz_circuit, initializer=tf.keras.initializers.Constant(value=0.5), name="ghz") - q_infer = circuit_infer.QuantumInference(ghz_qnn) + q_infer = inference.QuantumInference(ghz_qnn) sample_wrapper = tf.function(q_infer.sample) test_samples = sample_wrapper( tf.expand_dims(tf.constant([0] * self.num_bits, dtype=tf.int8), 0), @@ -460,9 +457,9 @@ def test_sample_uneven(self): """Check for discrepancy in samples when count entries differ.""" max_counts = int(1e7) counts = tf.constant([max_counts // 2, max_counts]) - test_qnn = circuit_model.DirectQuantumCircuit( + test_qnn = models.DirectQuantumCircuit( cirq.Circuit(cirq.H(cirq.GridQubit(0, 0)))) - test_infer = circuit_infer.QuantumInference(test_qnn) + test_infer = inference.QuantumInference(test_qnn) sample_wrapper = tf.function(test_infer.sample) bitstrings = tf.constant([[0], [0]], dtype=tf.int8) _, samples_counts = sample_wrapper(bitstrings, counts) @@ -472,5 +469,5 @@ def test_sample_uneven(self): if __name__ == "__main__": - logging.info("Running circuit_infer_test.py ...") + logging.info("Running qnn_test.py ...") tf.test.main() diff --git a/tests/circuit_infer_utils_test.py b/tests/inference/qnn_utils_test.py similarity index 87% rename from tests/circuit_infer_utils_test.py rename to tests/inference/qnn_utils_test.py index 64fe54bd..d08a19f8 100644 --- a/tests/circuit_infer_utils_test.py +++ b/tests/inference/qnn_utils_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the circuit_infer_utils module.""" +"""Tests for qhbmlib.inference.qnn_utils""" from absl import logging import random @@ -23,8 +23,8 @@ import tensorflow_quantum as tfq from tensorflow_quantum.python import util as tfq_util -from qhbmlib import circuit_infer_utils -from qhbmlib import circuit_model +from qhbmlib import inference +from qhbmlib import models class UnitaryTest(tf.test.TestCase): @@ -57,18 +57,18 @@ def test_unitary(self): self.tf_random_seed).numpy().tolist() resolver = dict(zip(symbols, random_values)) - circuit = circuit_model.QuantumCircuit( + actual_circuit = models.QuantumCircuit( tfq.convert_to_tensor([raw_circuit]), qubits, tf.constant(symbols), [tf.Variable([resolver[s] for s in symbols])], [[]]) resolved_circuit = cirq.protocols.resolve_parameters(raw_circuit, resolver) expected_unitary = resolved_circuit.unitary() - unitary_wrapper = tf.function(circuit_infer_utils.unitary) - actual_unitary = unitary_wrapper(circuit) + unitary_wrapper = tf.function(inference.unitary) + actual_unitary = unitary_wrapper(actual_circuit) self.assertAllClose(actual_unitary, expected_unitary, rtol=self.close_rtol) if __name__ == "__main__": - logging.info("Running circuit_infer_utils_test.py ...") + logging.info("Running qnn_utils_test.py ...") tf.test.main() diff --git a/tests/vqt_loss_test.py b/tests/inference/vqt_loss_test.py similarity index 91% rename from tests/vqt_loss_test.py rename to tests/inference/vqt_loss_test.py index 9f8e4fe3..dc6d3647 100644 --- a/tests/vqt_loss_test.py +++ b/tests/inference/vqt_loss_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the VQT loss and gradients.""" +"""Tests for qhbmlib.inference.vqt_loss""" import functools @@ -21,12 +21,8 @@ import tensorflow as tf import tensorflow_quantum as tfq -from qhbmlib import circuit_infer -from qhbmlib import circuit_model -from qhbmlib import energy_infer -from qhbmlib import energy_model -from qhbmlib import hamiltonian_infer -from qhbmlib import vqt_loss +from qhbmlib import inference +from qhbmlib import models from tests import test_util @@ -69,7 +65,7 @@ def test_self_vqt(self): data_h.set_weights(model_h.get_weights()) beta = 1.0 # Data and model are only the same if beta == 1 - vqt = tf.function(vqt_loss.vqt) + vqt = tf.function(inference.vqt) # Trained loss is minus log partition of the data. expected_loss = -1.0 * data_infer.e_inference.log_partition() @@ -103,7 +99,7 @@ def delta_vqt(k, var, model_infer, data_h, beta, delta): return delta_loss delta = 1e-1 - vqt = tf.function(vqt_loss.vqt) + vqt = tf.function(inference.vqt) def vqt_derivative(variables_list, model_infer, data_h, beta): """Approximately differentiates VQT with respect to the inputs""" @@ -178,15 +174,15 @@ def test_loss_value_x_rot(self): qubit losses, and the gradients are the the per-qubit gradients. """ - vqt = tf.function(vqt_loss.vqt) + vqt = tf.function(inference.vqt) for num_qubits in self.num_qubits_list: # model definition ebm_init = tf.keras.initializers.RandomUniform( minval=-2.0, maxval=2.0, seed=self.tf_random_seed) - energy = energy_model.BernoulliEnergy(list(range(num_qubits)), ebm_init) - e_infer = energy_infer.BernoulliEnergyInference( - energy, self.num_samples, initial_seed=self.tfp_seed) + actual_energy = models.BernoulliEnergy(list(range(num_qubits)), ebm_init) + e_infer = inference.BernoulliEnergyInference( + actual_energy, self.num_samples, initial_seed=self.tfp_seed) qubits = cirq.GridQubit.rect(1, num_qubits) r_symbols = [sympy.Symbol(f"phi_{n}") for n in range(num_qubits)] @@ -194,12 +190,12 @@ def test_loss_value_x_rot(self): cirq.rx(r_s)(q) for r_s, q in zip(r_symbols, qubits)) qnn_init = tf.keras.initializers.RandomUniform( minval=-1, maxval=1, seed=self.tf_random_seed) - circuit = circuit_model.DirectQuantumCircuit(r_circuit, qnn_init) - q_infer = circuit_infer.QuantumInference(circuit) - qhbm_infer = hamiltonian_infer.QHBM(e_infer, q_infer) + actual_circuit = models.DirectQuantumCircuit(r_circuit, qnn_init) + q_infer = inference.QuantumInference(actual_circuit) + qhbm_infer = inference.QHBM(e_infer, q_infer) # TODO(#171): code around here seems like boilerplate. - model = qhbm_infer.hamiltonian + model_h = qhbm_infer.modular_hamiltonian # Generate remaining VQT arguments test_h = tfq.convert_to_tensor( @@ -209,9 +205,9 @@ def test_loss_value_x_rot(self): # Compute losses # Bernoulli has only one tf.Variable - test_thetas = model.energy.trainable_variables[0] + test_thetas = model_h.energy.trainable_variables[0] # QNN has only one tf.Variable - test_phis = model.circuit.trainable_variables[0] + test_phis = model_h.circuit.trainable_variables[0] actual_expectation = qhbm_infer.expectation(test_h)[0] expected_expectation = tf.reduce_sum( tf.math.tanh(test_thetas) * tf.math.sin(test_phis)) diff --git a/tests/circuit_model_test.py b/tests/model/circuit_test.py similarity index 93% rename from tests/circuit_model_test.py rename to tests/model/circuit_test.py index 00592129..67d7e22f 100644 --- a/tests/circuit_model_test.py +++ b/tests/model/circuit_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the circuit_model module.""" +"""Tests for qhbmlib.models.circuit""" import absl import itertools @@ -24,7 +24,7 @@ from tensorflow.python.framework import errors import tensorflow_quantum as tfq -from qhbmlib import circuit_model +from qhbmlib import models from qhbmlib import utils from tests import test_util @@ -54,10 +54,12 @@ def setUp(self): self.expected_symbol_values = value_layer_2( value_layer_1(value_layer_0(self.expected_value_layers_inputs[0]))) self.expected_name = "TestOE" - self.actual_layer = circuit_model.QuantumCircuit( - self.expected_pqc, self.expected_qubits, self.expected_symbol_names, - self.expected_value_layers_inputs, self.expected_value_layers, - self.expected_name) + self.actual_layer = models.QuantumCircuit(self.expected_pqc, + self.expected_qubits, + self.expected_symbol_names, + self.expected_value_layers_inputs, + self.expected_value_layers, + self.expected_name) def test_init(self): """Tests initialization.""" @@ -121,7 +123,7 @@ def test_add(self): [self.expected_symbol_values, other_value_layers_inputs[0][0]], 0) other_name = "the_other_layer" expected_name = self.expected_name + "_" + other_name - other_layer = circuit_model.QuantumCircuit( + other_layer = models.QuantumCircuit( tfq.convert_to_tensor([other_pqc]), other_pqc.all_qubits(), tf.constant([str(s) for s in other_symbols]), other_value_layers_inputs, other_value_layers, other_name) @@ -139,12 +141,12 @@ def test_add(self): # Confirm that tf.Variables in the sum are the same as in the addends. var_1 = tf.Variable([5.0]) pqc_1 = cirq.Circuit(cirq.X(cirq.GridQubit(0, 0))**sympy.Symbol("a")) - qnn_1 = circuit_model.QuantumCircuit( + qnn_1 = models.QuantumCircuit( tfq.convert_to_tensor([pqc_1]), pqc_1.all_qubits(), tf.constant(["a"]), [var_1], [[]]) var_2 = tf.Variable([-7.0]) pqc_2 = cirq.Circuit(cirq.X(cirq.GridQubit(0, 0))**sympy.Symbol("b")) - qnn_2 = circuit_model.QuantumCircuit( + qnn_2 = models.QuantumCircuit( tfq.convert_to_tensor([pqc_2]), pqc_2.all_qubits(), tf.constant(["b"]), [var_2], [[]]) actual_sum = qnn_1 + qnn_2 @@ -159,12 +161,12 @@ def test_trace_add(self): """Confirm addition works under tf.function tracing.""" var_1 = tf.Variable([5.0]) pqc_1 = cirq.Circuit(cirq.X(cirq.GridQubit(0, 0))**sympy.Symbol("a")) - qnn_1 = circuit_model.QuantumCircuit( + qnn_1 = models.QuantumCircuit( tfq.convert_to_tensor([pqc_1]), pqc_1.all_qubits(), tf.constant(["a"]), [var_1], [[]]) var_2 = tf.Variable([-7.0]) pqc_2 = cirq.Circuit(cirq.X(cirq.GridQubit(0, 0))**sympy.Symbol("b")) - qnn_2 = circuit_model.QuantumCircuit( + qnn_2 = models.QuantumCircuit( tfq.convert_to_tensor([pqc_2]), pqc_2.all_qubits(), tf.constant(["b"]), [var_2], [[]]) @@ -188,11 +190,11 @@ def test_add_error(self): symbols_2 = [sympy.Symbol(s) for s in symbol_names_2] pqc_1 = cirq.Circuit([cirq.X(qubit_1)**s for s in symbols_1]) pqc_2 = cirq.Circuit([cirq.Y(qubit_2)**s for s in symbols_2]) - qnn_1 = circuit_model.QuantumCircuit( + qnn_1 = models.QuantumCircuit( tfq.convert_to_tensor([pqc_1]), pqc_1.all_qubits(), tf.constant([str(s) for s in symbol_names_1]), [tf.random.uniform([len(symbol_names_1)], dtype=tf.float32)], [[]]) - qnn_2 = circuit_model.QuantumCircuit( + qnn_2 = models.QuantumCircuit( tfq.convert_to_tensor([pqc_2]), pqc_2.all_qubits(), tf.constant([str(s) for s in symbol_names_2]), [tf.random.uniform([len(symbol_names_2)], dtype=tf.float32)], [[]]) @@ -219,7 +221,7 @@ def test_pow(self): # Confirm that tf.Variables in the inverse are the same as self. var_1 = tf.Variable([2.5]) pqc_1 = cirq.Circuit(cirq.X(cirq.GridQubit(0, 0))**sympy.Symbol("a")) - qnn_1 = circuit_model.QuantumCircuit( + qnn_1 = models.QuantumCircuit( tfq.convert_to_tensor([pqc_1]), pqc_1.all_qubits(), tf.constant(["a"]), [var_1], [[]]) actual_inverse = qnn_1**-1 @@ -250,7 +252,7 @@ def test_init(self): init_const = tf.random.uniform([len(raw_symbol_names)], dtype=tf.float32) expected_value_layers_inputs = [tf.Variable(init_const)] expected_value_layers = [[]] - actual_qnn = circuit_model.DirectQuantumCircuit( + actual_qnn = models.DirectQuantumCircuit( expected_pqc, initializer=tf.keras.initializers.Constant(init_const)) self.assertAllEqual(actual_qnn.qubits, expected_qubits) self.assertAllEqual(actual_qnn.symbol_names, expected_symbol_names) @@ -308,8 +310,7 @@ def test_init(self): expected_symbol_values += ( ([eta_const * theta_const] * len(classical_h_terms)) + ([gamma_const] * len(quantum_h_terms))) - actual_qnn = circuit_model.QAIA(quantum_h_terms, classical_h_terms, - num_layers) + actual_qnn = models.QAIA(quantum_h_terms, classical_h_terms, num_layers) self.assertAllEqual(actual_qnn.qubits, expected_qubits) self.assertAllEqual(actual_qnn.symbol_names, expected_symbol_names) self.assertAllEqual( @@ -327,5 +328,5 @@ def test_init(self): if __name__ == "__main__": - absl.logging.info("Running circuit_model_test.py ...") + absl.logging.info("Running circuit_test.py ...") tf.test.main() diff --git a/tests/circuit_model_utils_test.py b/tests/model/circuit_utils_test.py similarity index 86% rename from tests/circuit_model_utils_test.py rename to tests/model/circuit_utils_test.py index eb1b2a4e..8f30f5c5 100644 --- a/tests/circuit_model_utils_test.py +++ b/tests/model/circuit_utils_test.py @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the circuit_model module.""" +"""Tests for qhbmlib.models.circuit_utils""" import cirq import sympy import tensorflow as tf import tensorflow_quantum as tfq -from qhbmlib import circuit_model_utils +from qhbmlib import models from tests import test_util @@ -39,7 +39,7 @@ def test_bit_circuit(self): cirq.GridQubit(2, 2) ] identifier = "build_bit_test" - test_circuit = circuit_model_utils.bit_circuit(my_qubits, identifier) + test_circuit = models.circuit_utils.bit_circuit(my_qubits, identifier) test_symbols = list(sorted(tfq.util.get_circuit_symbols(test_circuit))) expected_symbols = list( sympy.symbols( @@ -48,3 +48,8 @@ def test_bit_circuit(self): [cirq.X(q)**s for q, s in zip(my_qubits, expected_symbols)]) self.assertAllEqual(_pystr(test_symbols), _pystr(expected_symbols)) self.assertEqual(test_circuit, expected_circuit) + + +if __name__ == "__main__": + absl.logging.info("Running circuit_utils_test.py ...") + tf.test.main() diff --git a/tests/energy_model_test.py b/tests/model/energy_test.py similarity index 93% rename from tests/energy_model_test.py rename to tests/model/energy_test.py index ef608092..1e238533 100644 --- a/tests/energy_model_test.py +++ b/tests/model/energy_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the energy_model module.""" +"""Tests for qhbmlib.models.energy""" import itertools import random @@ -20,7 +20,7 @@ import cirq import tensorflow as tf -from qhbmlib import energy_model +from qhbmlib import models from qhbmlib import utils from tests import test_util @@ -34,7 +34,7 @@ def test_init(self): expected_bits = [5, 17, 22, 30, 42] expected_name = "init_test" expected_energy_layers = [tf.keras.layers.Dense(1), utils.Squeeze(-1)] - actual_b = energy_model.BitstringEnergy( + actual_b = models.BitstringEnergy( expected_bits, expected_energy_layers, name=expected_name) self.assertEqual(actual_b.num_bits, expected_num_bits) self.assertAllEqual(actual_b.bits, expected_bits) @@ -50,7 +50,7 @@ def test_call(self): 1, kernel_initializer=tf.keras.initializers.Constant(1)), utils.Squeeze(-1) ] - test_b = energy_model.BitstringEnergy(list(range(num_bits)), energy_layers) + test_b = models.BitstringEnergy(list(range(num_bits)), energy_layers) @tf.function def test_b_wrapper(bitstrings): @@ -88,7 +88,7 @@ def layer_wrapper(layer, bitstrings): tf.keras.layers.Dense(units[i], activation=activations[i])) expected_layer_list.append(tf.keras.layers.Dense(1)) expected_layer_list.append(utils.Squeeze(-1)) - actual_mlp = energy_model.BitstringEnergy(bits, expected_layer_list) + actual_mlp = models.BitstringEnergy(bits, expected_layer_list) expected_mlp = tf.keras.Sequential(expected_layer_list) expected_mlp.build([None, num_bits]) @@ -118,7 +118,7 @@ def test_energy_simple(self): Then the derivative of the energy with respect to the thetas vector is $$\partial / \partial \theta E_\theta(b) = [(1-2b_i) for b_i in b]$$ """ - test_b = energy_model.BernoulliEnergy([1, 2, 3]) + test_b = models.BernoulliEnergy([1, 2, 3]) test_vars = tf.constant([1.0, 1.7, -2.8], dtype=tf.float32) test_b.build([None, 3]) test_b.set_weights([test_vars]) @@ -166,7 +166,7 @@ def layer_wrapper(layer, bitstrings): for _ in range(num_tests): bits = random.sample(range(1000), num_bits) thetas = tf.random.uniform([num_bits], -100, 100, tf.float32) - test_b = energy_model.BernoulliEnergy(bits) + test_b = models.BernoulliEnergy(bits) test_b.build([None, num_bits]) test_b.set_weights([thetas]) @@ -184,7 +184,7 @@ def layer_wrapper(layer, bitstrings): def test_operator_shards(self): """Confirms operators are single qubit Z only.""" num_bits = 10 - test_b = energy_model.BernoulliEnergy(list(range(num_bits))) + test_b = models.BernoulliEnergy(list(range(num_bits))) qubits = cirq.GridQubit.rect(1, num_bits) actual_ops = test_b.operator_shards(qubits) expected_ops = [cirq.PauliSum.from_pauli_strings(cirq.Z(q)) for q in qubits] @@ -195,7 +195,7 @@ def test_operator_expectation(self): """Tests combining expectations of operators in energy.""" # Build Bernoulli num_bits = 3 - test_b = energy_model.BernoulliEnergy(list(range(num_bits))) + test_b = models.BernoulliEnergy(list(range(num_bits))) @tf.function def operator_expectation_wrapper(sub_expectations): @@ -236,7 +236,7 @@ def test_energy(self): order = 2 test_thetas = tf.constant([1.5, 2.7, -4.0]) expected_energies = tf.constant([0.2, 2.8, 5.2, -8.2]) - test_k = energy_model.KOBE(bits, order) + test_k = models.KOBE(bits, order) @tf.function def test_k_wrapper(bitstrings): @@ -251,7 +251,7 @@ def test_k_wrapper(bitstrings): def test_operator_shards(self): """Confirms correct operators for a simple Boltzmann.""" num_bits = 3 - test_k = energy_model.KOBE(list(range(num_bits)), 2) + test_k = models.KOBE(list(range(num_bits)), 2) qubits = cirq.GridQubit.rect(1, num_bits) test_ops = test_k.operator_shards(qubits) ref_ops = [ @@ -270,7 +270,7 @@ def test_operator_expectation(self): """Confirms the expectations combine to the correct total energy.""" # Build simple Boltzmann num_bits = 3 - test_b = energy_model.KOBE(list(range(num_bits)), 2) + test_b = models.KOBE(list(range(num_bits)), 2) @tf.function def operator_expectation_wrapper(sub_expectations): @@ -303,5 +303,5 @@ def operator_expectation_wrapper(sub_expectations): if __name__ == "__main__": - print("Running energy_model_test.py ...") + print("Running energy_test.py ...") tf.test.main() diff --git a/tests/energy_model_utils_test.py b/tests/model/energy_utils_test.py similarity index 86% rename from tests/energy_model_utils_test.py rename to tests/model/energy_utils_test.py index e05178fd..adf199aa 100644 --- a/tests/energy_model_utils_test.py +++ b/tests/model/energy_utils_test.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the energy_model_utils module.""" +"""Tests for qhbmlib.models.energy_utils""" import tensorflow as tf -from qhbmlib import energy_model_utils +from qhbmlib import models from tests import test_util @@ -27,19 +27,19 @@ class CheckHelpersTest(tf.test.TestCase): def test_check_bits(self): """Confirms bad inputs are caught and good inputs pass through.""" expected_bits = [1, 6, 222, 13223] - actual_bits = energy_model_utils.check_bits(expected_bits) + actual_bits = models.energy_utils.check_bits(expected_bits) self.assertAllEqual(actual_bits, expected_bits) with self.assertRaisesRegex(ValueError, expected_regex="must be unique"): - _ = energy_model_utils.check_bits([1, 1]) + _ = models.energy_utils.check_bits([1, 1]) @test_util.eager_mode_toggle def test_check_order(self): """Confirms bad inputs are caught and good inputs pass through.""" expected_order = 5 - actual_order = energy_model_utils.check_order(expected_order) + actual_order = models.energy_utils.check_order(expected_order) self.assertEqual(actual_order, expected_order) with self.assertRaisesRegex(ValueError, expected_regex="greater than zero"): - _ = energy_model_utils.check_order(0) + _ = models.energy_utils.check_order(0) class SpinsFromBitstringsTest(tf.test.TestCase): @@ -48,7 +48,7 @@ class SpinsFromBitstringsTest(tf.test.TestCase): @test_util.eager_mode_toggle def test_layer(self): """Confirms the layer outputs correct spins.""" - test_layer = energy_model_utils.SpinsFromBitstrings() + test_layer = models.SpinsFromBitstrings() @tf.function def wrapper(inputs): @@ -68,7 +68,7 @@ def test_layer(self): """Confirms the layer dots with inputs correctly.""" inputs = tf.constant([[1.0, 5.0, 9.0]]) constant = 2.5 - actual_layer_constant = energy_model_utils.VariableDot( + actual_layer_constant = models.VariableDot( tf.keras.initializers.Constant(constant)) @tf.function @@ -89,7 +89,7 @@ def test_layer(self): inputs = tf.constant([[-1, 1, -1, -1]]) bits = [1, 2, 3, 4] order = 3 - actual_layer = energy_model_utils.Parity(bits, order) + actual_layer = models.Parity(bits, order) expected_indices = [[0], [1], [2], [3], [0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3], [0, 1, 2], [0, 1, 3], [0, 2, 3], @@ -111,5 +111,5 @@ def wrapper(inputs): if __name__ == "__main__": - print("Running energy_model_utils_test.py ...") + print("Running energy_utils_test.py ...") tf.test.main() diff --git a/tests/hamiltonian_model_test.py b/tests/model/hamiltonian_test.py similarity index 76% rename from tests/hamiltonian_model_test.py rename to tests/model/hamiltonian_test.py index 35aa6cca..6e060f73 100644 --- a/tests/hamiltonian_model_test.py +++ b/tests/model/hamiltonian_test.py @@ -1,3 +1,4 @@ +# pylint: skip-file # Copyright 2021 The QHBM Library Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the hamiltonian_model module.""" +"""Tests for qhbmlib.models.hamiltonian""" import absl @@ -21,9 +22,7 @@ import tensorflow as tf import tensorflow_quantum as tfq -from qhbmlib import circuit_model -from qhbmlib import energy_model -from qhbmlib import hamiltonian_model +from qhbmlib import models class HamiltonianTest(tf.test.TestCase): @@ -34,18 +33,18 @@ def setUp(self): super().setUp() self.expected_name = "this_IS_theTestHam42" self.num_bits = 3 - self.expected_energy = energy_model.BernoulliEnergy( - list(range(self.num_bits))) + self.expected_energy = models.BernoulliEnergy(list(range(self.num_bits))) self.expected_energy.build([None, self.num_bits]) qubits = cirq.GridQubit.rect(1, self.num_bits) symbols = [sympy.Symbol(str(n)) for n in range(self.num_bits)] pqc = cirq.Circuit(cirq.X(q)**s for q, s in zip(qubits, symbols)) - self.expected_circuit = circuit_model.DirectQuantumCircuit(pqc) + self.expected_circuit = models.DirectQuantumCircuit(pqc) self.expected_circuit.build([]) self.expected_operator_shards = self.expected_energy.operator_shards( self.expected_circuit.qubits) - self.actual_hamiltonian = hamiltonian_model.Hamiltonian( - self.expected_energy, self.expected_circuit, self.expected_name) + self.actual_hamiltonian = models.Hamiltonian(self.expected_energy, + self.expected_circuit, + self.expected_name) def test_init(self): """Tests Hamiltonian initialization. @@ -70,19 +69,19 @@ def test_init(self): # check None operator shards. pqc = cirq.Circuit(cirq.X(cirq.GridQubit(0, 0))**sympy.Symbol("a")) - qnn = circuit_model.DirectQuantumCircuit(pqc) - energy = energy_model.BitstringEnergy([1], []) - actual_ham = hamiltonian_model.Hamiltonian(energy, qnn) + qnn = models.DirectQuantumCircuit(pqc) + actual_energy = models.BitstringEnergy([1], []) + actual_ham = models.Hamiltonian(actual_energy, qnn) self.assertIsNone(actual_ham.operator_shards) def test_init_error(self): """Confirms initialization fails for mismatched energy and circuit.""" - small_energy = energy_model.BernoulliEnergy(list(range(self.num_bits - 1))) + small_energy = models.BernoulliEnergy(list(range(self.num_bits - 1))) with self.assertRaisesRegex( ValueError, expected_regex="same number of bits"): - _ = hamiltonian_model.Hamiltonian(small_energy, self.expected_circuit) + _ = models.Hamiltonian(small_energy, self.expected_circuit) if __name__ == "__main__": - absl.logging.info("Running hamiltonian_model_test.py ...") + absl.logging.info("Running hamiltonian_test.py ...") tf.test.main() diff --git a/tests/test_util.py b/tests/test_util.py index 2e6ef14a..93800c4e 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -20,17 +20,57 @@ import cirq import numpy as np import scipy - +import sympy import tensorflow as tf import tensorflow_probability as tfp -from qhbmlib import architectures -from qhbmlib import circuit_infer -from qhbmlib import circuit_model -from qhbmlib import energy_infer -from qhbmlib import energy_model -from qhbmlib import hamiltonian_infer -from qhbmlib import hamiltonian_model +from qhbmlib import inference +from qhbmlib import models + + +def get_xz_rotation(q, a, b): + """Two-axis single qubit rotation.""" + return cirq.Circuit(cirq.X(q)**a, cirq.Z(q)**b) + + +def get_cz_exp(q0, q1, a): + """Exponent of entangling CZ gate.""" + return cirq.Circuit(cirq.CZPowGate(exponent=a)(q0, q1)) + + +def get_xz_rotation_layer(qubits, layer_num, name): + """Apply two-axis single qubit rotations to all the given qubits.""" + circuit = cirq.Circuit() + for n, q in enumerate(qubits): + sx, sz = sympy.symbols("sx_{0}_{1}_{2} sz_{0}_{1}_{2}".format( + name, layer_num, n)) + circuit += get_xz_rotation(q, sx, sz) + return circuit + + +def get_cz_exp_layer(qubits, layer_num, name): + """Apply parameterized CZ gates to all pairs of nearest-neighbor qubits.""" + circuit = cirq.Circuit() + for n, (q0, q1) in enumerate(zip(qubits[::2], qubits[1::2])): + a = sympy.symbols("sc_{0}_{1}_{2}".format(name, layer_num, 2 * n)) + circuit += get_cz_exp(q0, q1, a) + shifted_qubits = qubits[1::] + for n, (q0, q1) in enumerate(zip(shifted_qubits[::2], shifted_qubits[1::2])): + a = sympy.symbols("sc_{0}_{1}_{2}".format(name, layer_num, 2 * n + 1)) + circuit += get_cz_exp(q0, q1, a) + return circuit + + +def get_hardware_efficient_model_unitary(qubits, num_layers, name): + """Build our full parameterized model unitary.""" + circuit = cirq.Circuit() + for layer_num in range(num_layers): + new_circ = get_xz_rotation_layer(qubits, layer_num, name) + circuit += new_circ + if len(qubits) > 1: + new_circ = get_cz_exp_layer(qubits, layer_num, name) + circuit += new_circ + return circuit def get_random_hamiltonian_and_inference(qubits, @@ -47,19 +87,18 @@ def get_random_hamiltonian_and_inference(qubits, num_qubits = len(qubits) ebm_init = tf.keras.initializers.RandomUniform( minval=minval_thetas, maxval=maxval_thetas) - energy = energy_model.KOBE(list(range(num_qubits)), num_qubits, ebm_init) - e_infer = energy_infer.AnalyticEnergyInference( - energy, num_samples, name=identifier, initial_seed=ebm_seed) + actual_energy = models.KOBE(list(range(num_qubits)), num_qubits, ebm_init) + e_infer = inference.AnalyticEnergyInference( + actual_energy, num_samples, name=identifier, initial_seed=ebm_seed) qnn_init = tf.keras.initializers.RandomUniform( minval=minval_phis, maxval=maxval_phis) - unitary = architectures.get_hardware_efficient_model_unitary( - qubits, num_layers, identifier) - circuit = circuit_model.DirectQuantumCircuit(unitary, qnn_init) - q_infer = circuit_infer.QuantumInference(circuit, name=identifier) - qhbm = hamiltonian_infer.QHBM(e_infer, q_infer) + unitary = get_hardware_efficient_model_unitary(qubits, num_layers, identifier) + actual_circuit = models.DirectQuantumCircuit(unitary, qnn_init) + q_infer = inference.QuantumInference(actual_circuit, name=identifier) + random_qhbm = inference.QHBM(e_infer, q_infer) - return qhbm.hamiltonian, qhbm + return random_qhbm.modular_hamiltonian, random_qhbm def get_random_pauli_sum(qubits): diff --git a/tests/test_util_test.py b/tests/test_util_test.py index 6ccd4477..92bc423c 100644 --- a/tests/test_util_test.py +++ b/tests/test_util_test.py @@ -12,13 +12,88 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the test_util module.""" +"""Tests for tests.test_util""" +from absl.testing import parameterized + +import cirq +import sympy import tensorflow as tf from tests import test_util +class RPQCTest(tf.test.TestCase, parameterized.TestCase): + """Test RPQC functions in the test_util module.""" + + def test_get_xz_rotation(self): + """Confirm an XZ rotation is returned.""" + q = cirq.GridQubit(7, 9) + a, b = sympy.symbols("a b") + expected_circuit = cirq.Circuit(cirq.X(q)**a, cirq.Z(q)**b) + actual_circuit = test_util.get_xz_rotation(q, a, b) + self.assertEqual(actual_circuit, expected_circuit) + + def test_get_cz_exp(self): + """Confirm an exponentiated CNOT is returned.""" + q0 = cirq.GridQubit(4, 1) + q1 = cirq.GridQubit(2, 5) + a = sympy.Symbol("a") + expected_circuit = cirq.Circuit(cirq.CZ(q0, q1)**a) + actual_circuit = test_util.get_cz_exp(q0, q1, a) + self.assertEqual(actual_circuit, expected_circuit) + + def test_get_xz_rotation_layer(self): + """Confirm an XZ rotation on every qubit is returned.""" + qubits = cirq.GridQubit.rect(1, 2) + layer_num = 3 + name = "test_rot" + expected_circuit = cirq.Circuit() + for n, q in enumerate(qubits): + s = sympy.Symbol("sx_{0}_{1}_{2}".format(name, layer_num, n)) + expected_circuit += cirq.Circuit(cirq.X(q)**s) + s = sympy.Symbol("sz_{0}_{1}_{2}".format(name, layer_num, n)) + expected_circuit += cirq.Circuit(cirq.Z(q)**s) + actual_circuit = test_util.get_xz_rotation_layer(qubits, layer_num, name) + self.assertEqual(actual_circuit, expected_circuit) + + @parameterized.parameters([{"n_qubits": 11}, {"n_qubits": 12}]) + def test_get_cz_exp_layer(self, n_qubits): + """Confirm an exponentiated CZ on every qubit is returned.""" + qubits = cirq.GridQubit.rect(1, n_qubits) + layer_num = 0 + name = "test_cz" + expected_circuit = cirq.Circuit() + for n, (q0, q1) in enumerate(zip(qubits, qubits[1:])): + if n % 2 == 0: + s = sympy.Symbol("sc_{0}_{1}_{2}".format(name, layer_num, n)) + expected_circuit += cirq.Circuit(cirq.CZ(q0, q1)**s) + for n, (q0, q1) in enumerate(zip(qubits, qubits[1:])): + if n % 2 == 1: + s = sympy.Symbol("sc_{0}_{1}_{2}".format(name, layer_num, n)) + expected_circuit += cirq.Circuit(cirq.CZ(q0, q1)**s) + actual_circuit = test_util.get_cz_exp_layer(qubits, layer_num, name) + self.assertEqual(actual_circuit, expected_circuit) + + @parameterized.parameters([{"n_qubits": 11}, {"n_qubits": 12}]) + def test_get_hardware_efficient_model_unitary(self, n_qubits): + """Confirm a multi-layered circuit is returned.""" + qubits = cirq.GridQubit.rect(1, n_qubits) + name = "test_hardware_efficient_model" + expected_circuit = cirq.Circuit() + this_circuit = test_util.get_xz_rotation_layer(qubits, 0, name) + expected_circuit += this_circuit + this_circuit = test_util.get_cz_exp_layer(qubits, 0, name) + expected_circuit += this_circuit + this_circuit = test_util.get_xz_rotation_layer(qubits, 1, name) + expected_circuit += this_circuit + this_circuit = test_util.get_cz_exp_layer(qubits, 1, name) + expected_circuit += this_circuit + actual_circuit = test_util.get_hardware_efficient_model_unitary( + qubits, 2, name) + self.assertEqual(actual_circuit, expected_circuit) + + class EagerModeToggle(tf.test.TestCase): """Tests eager_mode_toggle.""" diff --git a/tests/utils_test.py b/tests/utils_test.py index a6999be4..ad3bed87 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for the utils module.""" +"""Tests for qhbmlib.utils""" from absl.testing import parameterized